此笔记写于我在大一时期的笔记,先存储于博客上,自己的理解偏多,不对的地方欢迎各位指正
JAVA学习笔记
变量
局部变量和全局变量
全局变量有默认值,而局部变量即方法中的变量无默认值,因此方法中的变量必须进行复制才能引用
局部变量前面不能加修饰符(public,private,等)
变量类型注意
- JAVA中默认的小数常量为double类型,若需要float类型,则在数字末尾添加f或者F
- 当类型为double或是float时,0.***中的0可省略,例如 0.1 可写成 .1
自动转换类型
低精度到高精度可自动转换
转换规则:
char->int->long->float->double
byte->short->int->long->float->double
eg:
int num = "a"; //a是由char类型转换为int类型,从低精度转换为高精度 char -> int
double d = 80 // int -> double
自由转换数据类型
转为字符串
只需要在数值后面加上 "" 即可
eg:
int a = 10;
String b = a + "";
String类型转其它
需要引入Integer类
eg:
//转为Int
String a = "aa";
int b = Integer.parseInt(a);
//转为double
double c = Double.parseDouble(a);
//以此类推即可
运算符
+-*/不再赘述
%取余
a % b => a - a / b * b
前++和后++
作为独立语句时
i++和++i作为独立语句时据等价于i = i + 1
作为非独立语句时
i++表示先赋值后运算
++i表示先运算后赋值
eg:
int a = 1;
int b = a++; //等价于 b = a,a = a + 1
int c = 1;
int d = ++c; //等价于 d = c + 1,c = d或c = c + 1
三元运算符
基本语法
条件表达式?表达式1:表达式2
运算规则
1.若条件表达式返回为True,则执行表达式2 2.若条件表达式返回为False,则执行表达式2
int a = 1;
int b = 2;
int result = a > b?a++:b--;
// 返回result=1
// 这里原来的a,b变量仍然不会改变
Switch
如下代码,表达式中返回一个结果,case中匹配常量和结果,若相同,则执行同级case下的语句,否则匹配下个case,若没有相同的常量,则默认执行default下的语句
表达式的返回值必须是 byte short int srting char enum
case后的语句必须是常量或是 常量表达式,不能是变量
当执行完一个case语句没有break语句时,会顺序执行到switch结尾,直到遇到break,注意,期间不管case是否匹配,都会执行下面的语句
switch(表达式){
case 表达式结果等于这里的常量时:
// 语句1
break;
case 常量2:
// 语句2
break;
default:
// 语句三
break;
}
do...while
与while不同的是,while是先判断在执行循环体,do...while是先执行循环体在判断
while不一定执行循环体,do...while至少执行一次循环体
int count = 0;
do{
count++;
System.out.println(count);
}while(count<=10);
break
细节
通过lable可控制具体跳出的循环体
若不指定跳出的循环体,则默认跳出离break最近的循环体
lable1:{}
break lable1;
lable1:
for(){
lable2:
while(){
lable3 do{
}while();
break lable1;
break lable2;
break lable3;
}
}
递归函数
强烈推荐使用内存分析法
下面是案例分析
阶乘案例
class A{
public int factorial(int num){
if n == 1{
return 1;
}else{
return factorial(num - 1) * num;
}
}
}
A test = new A();
int ret = test.facaorial(5);
System.out.println(ret); // 返回结果是120
分析
首先传入参数5,进入到第6行的factorial函数中,然后再在栈中新开辟一个内存空间假设为t1,t1的sum为4,然后再在执行factorial函数,在开辟t2,t2sum为3,....t4sum为1,所以t4返回值为1,t4空间销毁回收,返回给t3,即1sum,t3的sum为2,即1*2,返回给t2,返回值为2,然后2*3返回为6,t1是6*4返回给第一次计算的空间即24*5,所以最后结果为120
可变参数
当一个方法接收的参数类型一样且为多个时,可以用可变参数接收
public void static main(String[] args){
class sum{
public int sum(int...nums){
# nums的数据类型相当于数组
System.out.println(nums.length);
# 求和
int res = 0;
for(int i = ;i < nums.length ; i++){
res += nums[i];
}
}
}
}
注意
- 可变参数可以接受数组
- int...nums 中int为数据类型,... 不可动,nums为形参
- 普通形参和可变参数放在一起时,可变参数要放在普通形参后面
- 一个方法中只能有一个可变参数
构造器
注意事项
- 格式: [修饰符] 类名(形参){}
- 不能有return语句
- 构造器可以重载
- 构造器中如果有This语句,则This语句必须放在第一条
class Person(){
String name;
int age;
public Person(String pname,int page){
name = pname;
age = page
}
}
Person p = new Person("名字",10);
System.out.println(p.name + p.age);
# 名字10
This,Super关键字
Super只能在构造器中使用,且This和Super不能同时使用
This相当于Python整的self,在类中则代表当前对象
Java示例如下
class Dog{
String name;
int age;
public Dog(String name,int age){
this.name = name;
this.age = age;
}
public void t1(){
System.out.println("t1()方法被调用");
}
public void t2(){
// 调用t1的方法
this.t1();
}
}
Python示例如下
class Dog:
def __init__(self,name, age):
self.name = name
self.age = age
def t1():
print("t1()方法被调用")
def t2():
# 调用t1方法
self.t1()
访问构造器
注意
- 访问构造器语法 this(); 必须放在构造器第一行
class Dog{
String name;
int age;
public Dog(){
this("小明", 20);
}
public Dog(String name, int age){
this.name = name;
this.age = age;
System.out.println(this.name + this.age);
}
}
Dog d = new Dog();
// 输出 小明20
this(name)
在继承中代表访问一个参数只有name的构造器
class Person {
public static void prt(String s) {
System.out.println(s);
}
Person() {
prt("父类·无参数构造方法: "+"A Person.");
}//构造方法(1)
Person(String name) {
prt("父类·含一个参数的构造方法: "+"A person's name is " + name);
}//构造方法(2)
}
public class test extends Person {
test() {
super(); // 调用父类构造方法(1)
prt("子类·调用父类无参数构造方法: "+"A chinese coder.");
}
test(String name) {
super(name);// 调用父类具有相同形参的构造方法(2)
prt("子类·调用父类含一个参数的构造方法: "+"his name is " + name);
}
test(String name, int age) {
this(name);// 调用具有相同形参的构造方法(3)
prt("子类:调用子类具有相同形参的构造方法:his age is " + age);
}
public static void main(String[] args) {
test cn = new test();
// cn = new test("codersai");
cn = new test("codersai", 18);
}
}
// 输出如下
//父类·无参数构造方法: A Person.
//子类·调用父类无参数构造方法: A chinese coder.
//父类·含一个参数的构造方法: A person's name is codersai
//子类·调用父类含一个参数的构造方法: his name is codersai
//子类:调用子类具有相同形参的构造方法:his age is 18
Super
super.属性
可以访问跟父类的属性,但不能访问父类的私有属性
spuer.方法名(参数列表)
可以访问父类的方法,但是不能访问父类私有的方法
super()
必须放在子类构造器的第一行,且不能与this同时存在
访问修饰符
类中的变量,函数都可用访问修饰符
类也可以用访问修饰符,不过只能同 public 和 默认
继承
关键词 extends
注意事项
- 子类继承了所有的属性和方法,但是私有属性和方法不能在子类直接访问,要通过公用的方法去访问
- 当创建子类对象时,不管子类使用哪个构造器,默认情况下会去调用父类的无参构造器,如果父类中没有无参构造器,则必须在子类的构造器中用
super()
去指定父类哪个构造器来完成对父类的初始化工作,否则编译不通过。
多态
多态因为可以理解为多种形态
方法的多态
方法的重载,重写实际上就是多态的一种
对象的多态
1.一个对象的编译类型和运行类型可以不一致 2.编译类型在定义对象时,就确定了,不能改变了 3.运行类型是可以改变的 4.编译类型看定义时 = 号 的左边,运行类型看 = 号 的右边
class Animal{ //父类
private String Name;
public void setName(String Name){
this.Name = Name;
}
public String getName(){
return Name;
}
}
class Dog extends Animal{ //子类
private String Name = "Dog";
public String getName(){
return Name;
}
}
class Cat extends Animal{ //子类
private String Name = " Cat";
public String getName(){
return Name;
}
}
public static void main(String[] args){
Animal dog = new Dog(); //dog的编译类型是Animal,运行类型是Dog
dog = new Cat(); //此时dog的编译类型还是Animal,运行类型变成了Cat
}
对象的多态,就是在调用类的时候传入的参数可以是一个对象,通过对象去调用其子类或本类的方法 eg:
public static void main(String[] args){
Animal dog = new Dog();
Animal cat = new Cat();
call(dog); //输出Dog is runing
call(cat); //输出Cat is runing
}
public void call(Animal name){
System.out.println(name.getName + " is runing");
}
向上转型
向上转型就是Animal dog = new Dog()
dog去调用Dog类和Animal类的方法,弊端是假如说Dog中有A方法可以调用,Animal中的A方法为私有方法或者没有此方法,那么就无法编译通过。虽然是从子类到父类去寻找方法,但是由于Java特性,无法编译通过,于是有了向下转型
向下转型
书接上回,dog如何调用Dog类中独有的方法呢?就用到了向下转型,也可以理解为强转,转的 不是编译类型,因为编译类型确定后就无法改变,转的是运行类型。
Animal dog = new Dog();
Dog dog1 = (Dog) dog;
这样dog1对象就能调用Dog类的方法了。
注意
- 语法 子类类型 引用名 = (子类类型)父类引用;
- 要求父类的引用必须指向的是当前目标类型的对象(也就是说上方代码第二行最后一个的dog的运行类型必须是dog1的编译类型)
- 变量不能重写,以向上转型为例,
Animal animal = new Dog(); System.out.print(amimal.name)
假设父类Animal和子类Dog都有公共的变量name,则以编译类型的变量为主,打印出来的就是Animal的name。 - 属性看遍历类型,方法看运行类型。
instanceof
语法: 对象A instanceof 类B 返回布尔值
判断对象A的运行类型的类是否为类B或类B的子类,若是则为True,反之为False
动态绑定机制
- 当调用对象方法时候,该方法会和该对象的内存地址/运行类型绑定
- 当调用对象属性时,没有动态的绑定机制,哪里声明哪里使用。
class A { //父类
public int i =10;
public int sum(){
return getI + 10;
}
public int sum1(){
return i + 10;
}
public int getI(){
return i;
}
}
class B extends A { //子类
public int i = 20;
public int sum(){
return i + 20;
}
public int getI(){
return i;
}
public int sum1(){
return i + 10;
}
}
// main方法
A a = new B(); //向上转型
System.out.println(a.sum()); //40
System.out.println(a.sum1()); //30
!!!
如果删除掉子类B的sum()方法,则调用a.sum()
时候会继承A的sum()方法,返回的时getI() + 10
,而由于动态绑定机制,getI()指向的是a的运行类型也就是B类中的getI()方法,所以此时返回的是30
多态数组
定义:数组的定义为父类类型,厘米那的实际元素类型为子类类型。
代码块
代码块和方法不同没有方法名,没有返回值,没有参数,只有方法体,而且不用通过对象或类显式调用,而是在加载类时,,或创建类时隐式调用
基本语法
[修饰符]{
Code
};
注意
- 修饰符为可选项,要写的话,也只能写static
- 代码块分为两类,静态代码块即用static修饰的代码块和普通代码块即没有修饰符的
- 末尾;可写可不写
代码块什么时候被加载
- 创建对象实例时
- 创建子对象实例时候,父类的代码块也会被加载(父类的代码块先加载,接着加载子类的代码块)
- 使用类的静态成员时(静态方法,静态变量)ps:使用类的静态成员时普通代码块即非静态代码块不会被执行
静态代码块只会被执行1次,非静态的没创建一次就会被执行一次
优先级
- 静态代码块和静态属性的优先级一样
- 倘若有多个静态代码块和多个静态属性,则按照书写顺序加载
class A{
private static int num = getNum();
static {
System.out.println("静态代码块初始化");
}
public int getNum(){
System.out.println("getNum方法被调用");
return 100;
}
}
new A();
!!!
当实例化A类时候,优先初始化第一个静态属性即num
,此时num
调用了getNum
方法,所以优先输出getNum方法被调用
,然后再初始化静态代码块
- 静态代码块和静态属性的优先级高于普通代码块和普通属性
- 普通代码块和普通属性的优先级一样
- 构造器的优先级是最低的class AAA{
public AAA(){
//super();
//调用本类的普通代码块
System.out.println("AAA类的构造器被调用");
}
}
class BBB extends AAA{
{
System.out.println("BBB的普通代码块被调用");
}
public BBB(){
//super();
//调用本类的普通代码块
System.out.println("BBB类的构造器被调用");
}
}
new BBB();每个类的构造器下都有隐藏的代码,- 调用
super()
- 调用本类的普通代码块所以上述代码的运行结果就是 1 AAA类构造器 2 BBB普通代码块 3 BBB构造器
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性(优先级一样,按定义顺序执行)
- 父类的构造方法
- 子类的普通代码块和普通属性(优先级一样,按定义顺序执行)
- 子类的构造方法
- 调用
final关键字
结论
不能被继承,重写,修改里面的值
三种赋值方式
- 定义时
- 构造器中
- 代码块中如果final修饰的属性是静态的,那么只能在代码块和定义时赋值。
final修饰的类不能被继承,一般final修饰类后,方法就不用加final修饰了
final和static
final和static同时使用不会创建类、
class Test{
public final static int num = 555;
static{
System.out.println("静态代码块被调用");
}
}
假如只有static没有final,那么调用Test.num
时候静态代码块也会被调用,若加上final,则不会调用静态代码块。
抽象类 abstract
抽象类就是没有方法体的方法,方便子类继承重写方法,抽象类和抽象方法的定义需要用到abstract
关键字
当一个类中存在抽象方法时,该类必须声明为抽象类
abstract class Animal{
abstract public void eat();
}
细节
- 抽象类不能被实例化
- 抽象类不一定要有抽象方法
- abstract关键字只能修饰类和方法,不能修饰属性
- 如果一个类继承了抽象类,则必须实现抽象类的所有抽象方法,否则子类也要声明为抽象类
- 抽象方法不能用
private
,static
,final
关键字修饰,因为这些关键字是与重写相违背的
接口 interFace
基本语法
定义接口
//接口类中可省略abstract和public
public interface Method{
//属性
//方法(1.抽象方法 2.默认default方法 3.静态static方法)
void job(); //接口类中可省略abstract和public
}
//实现(重写接口中的方法)
public class Mian implenets Method{
public void job(){
System.out.println("Hello World");
}
}
public class T1(){
public void run(interface Method){
Method.job();
}
}
//调用
public class void T2{
public static void main(String[] args){
T1 tt = new T1();
Main mm = new Main();
tt.run(mm);
}
}
注意
- 如果一个类实现了一个接口方法,即 一个类 implenets 接口 则这个类必须实现这个接口的所有的抽象方法
- 在JDK8之后,可以有静态方法
- 如果用到非抽象方法,需要加上default的关键字
- 接口不能被实例化
- 一个类可以同时实现多个接口
- 接口中的属性,只能是final的,且为 public static final,假设我写了一个int x = 1,那么它就是public static final int x = 1;(必须初始化)
- 接口不能继承类,但是接口可以继承extends多个接口
内部类
局部内部类(有类名)
- 局部内部类可以直接访问外部类的所有成员对象,包括私有的
- 内部类不能用除final的修饰符修饰
- 如果外部类和局部内部类的成员名重名时,遵循就近原则,如果要调用外部类则需要 外部类名.this.成员名
匿名内部类(没有类名)
基于接口
首先定义一个接口innerTest,使其拥有一个test方法
以下是内部匿名类的用法,通过实例化一个接口并重写其方法来实现匿名内部类
下图是上图的底层源码
匿名内部类实际上是有名字的,我们看不到,系统自动去分配,而他的底层源码就是上图所示的形式
上图的编译类型是接口的innerTest类型,而他的运行类型则是个底层代码的te的类型
基于类
跟基于接口的类似
很牛逼的使用匿名内部类
匿名内部类可以直接当作一个参数使用
interface IA{
void test();
}
class main{
public static void method(IA i){
i.test();
}
public static void main(String[] args){
method(new IA(){
public void test(){
System.out.println("世界名画!");
}
});
}
}
// 输出 世界名画
细节
- 匿名类有两种调用模式,第一种就是上面使用的的,利用可动态绑定机制,第二种可以直接去调用class outer{
new test(){
public void test(){
System.out.println("我重写了test函数");
}
}.test();
}
class test{
public void test(){
System.out.println("test");
}
} - 外部其他类不能访问内部类
成员内部类
- 成员内部类是定义在外部类的成员位置
- 成员内部类可以访问外部类的所有成员,包括私有的
- 可以添加任意的访问修饰符,因为它本身就是一个成员
- 如果外部类和成员内部类的方法或者属性变量重名,依然遵守就近原则,访问外部类:外部类.this.方法名/属性名
外部其他类调用成员内部类
方法一
// 假设outer为外部类,inner为成员内部类
outer.inner test = new outer.inner();
方法二
在外部类中写一个方法返回内部类的对象
静态内部类
说明:静态内部类仍然定义在外部类的成员位置,只是多了个static修饰
- 可以直接访问外部类的静态成员,包括私有的,但是不能直接访问非静态的
- 因为他也是成员内部类,所以可以使用修饰符
- (不太一样,无this)如果外部类和成员内部类的方法或者属性变量重名,依然遵守就近原则,访问外部类:外部类.方法名/属性名
调用方法同上
枚举
自定义枚举
- 构造器私有化
- 创建一个静态成员new一个所需的对象,用static final修饰
class test{
public int i;
public static final test CREATEST1 = new test(1);
public static final test CREATEST2 = new test(2);
public static final test CREATEST3 = new test(3);
private test(int i){
this.i = i;
}
}
// static 和 final 一起使用时候会使类不会被加载
// 调用就直接 test.CREATEST1.XXX
// 枚举对象名一般大写
关键字enum
优化后的代码
enum test{
CREATEST1(1),CREATEST2(2),CREATEST3(3),WHAT;
public int i;
private test(int i){
this.i = i;
}
private test(){
}
}
注意
- 使用关键字enum代替class
- public static final test CREATEST1 = new test(1);直接使用CREATEST1(1)代替,若有多个则用逗号隔开
- CREATEST1(1) 括号里面是实参,对应构造器的形参
- CREATEST1(1)必须写在最前面
- 如果使用无参构造器,则可以省略 ( ) 直接写常量对象名
- 使用enum定义枚举时候,就不能在继承其他类了,因为它已经隐式的继承了enum类了,然仍然能实现接口
注解/元数据
@Override
该注解表示方法重写了父类的某个方法
该注解只能用在某个方法上
如果写了@Override注解,java编译器在编译时候会寻找父类中是否含有该方法,如果真的被重写,则编译通过。否则就抛出异常
@Deprecated
表示程序元素(类,方法)已经过时了
可以修饰 类 方法 参数 包 字段等等
过时并不能代表不能用
@SuppressWarnings
抑制编译器警告
用法
@SuppressWarnings{""}
{" "}里面写抑制的类型,all 代表一直所有警告
@Target
该注解是修饰注解的注解,称为元注解
集合
List
基本方法(实现了Collection的接口方法)
- add:添加单个元素
- remove:删除元素(可指定索引或指定删除的对象)
- contains:查找元素是否存在
- size:获取元素个数
- isEmpty:判断是否为空
- clear:清空
- addAll:添加多个元素(可以放实现了Collection接口的对象,比如说集合)
- containsAll:查找多个元素是否都存在(可以放实现了Collection接口的对象,比如说集合)
- removeAll:删除多个元素(可以放实现了Collection接口的对象,比如说集合)
迭代器遍历
Iterator iterator = coll.iterator() //得到一个集合的迭代器
.hasNext()是否还有下一个元素 编译类型是Object
.next() 显示当前元素内容,并对指针进行下移
第二次遍历需要重置迭代器,否则就会抛出异常
iterator = coll.iterator() //重置迭代器
IDEA快速生成whlie循环遍历的快捷键:itit
IDEA快速生成增强for循环遍历的快捷键:I
增强for循环遍历
for(元素类型(Object) name:col){
System.out.println(clo);
}
Set
Set接口介绍
- 无序的(添加和取出的顺序不一样),没有索引
- 不允许有重复的元素,所以最多包含一个null
注意:取出的顺序虽然是无序的,但是是固定的,底层算法决定的
方法说明
因为set没有索引,故不能用get方法取出,遍历的话同上实现了List的接口的类一样,增强for循环和迭代器
- add方法说明,add方法在这里有一个布尔类型的返回值,用来判断是否添加成功
- remove 删除指定对象
Map
key:value
方法说明
put(key,value)
添加:key的值是唯一的不能重复,value的值可以重复,假如说key的值重复了,那么则会替换先前存在key的value的值(Hashtable的key和value都不能为空)remove(key)
根据键删除对应的映射关系get(key)
获取对应key的value 返回值类型为objectsize()
返回k-v的个数isEmpty()
判断是否为空clear()
清空键值对containsKey(key)
查找键是否存在keySet()
返回一个set集合,集合里面是所有的key
遍历的六大方式
(比List和Set稍微复杂点,但是基本原理相同)
-
- 利用
keySet()
方法获取所有的key,然后用增强for循环get到每一个value
- 利用
Map map = new HashMap();
map.put("no1","Lin");
map.put("no2","Kenen");
map.put("no3","f");
map.put("no4","z");
Set set = map.keySet();
for(Object i:set){
System.out.println(map.get(i));
}
-
- 迭代器
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(map.get(key));
}
- 增强for 同上
- 迭代器 同上
- entry
for (Object i:set){
Map.Entry m = (Map.Entry) i;
System.out.println(m.getKey()+"-"+m.getValue());
}
Collections工具类
Collections类能对Set,List,Map等集合进行操作
常用方法
- reverse(List) :反转列表
- shuffle(List) :对列表进行随机排序
- sort(List) :排序
- sort(List,Comparatoe) :根据Comparator产生的顺序对列表排序
- swap(List,int,int) :将指定List的集合的两个元素进行交换位置
- copy(desp,list) :将list的内容拷贝到desp中。注意:desp的长度必须>=list的长度,都则会抛出异常
- boolean replaceALL(List,old,new) :将List集合中的old值全部替换为new的值
- frequency(Collection,Object) :返回指定集合中指定元素出现的次数
泛型
泛型的通配和继承
- 泛型不具备继承机制
List<Object> list = new list<String>(); //错误
<?>
支持任意泛型<? extends A>
支持A类及A类的子类,规定了泛型的上限<? super A>
支持A类及A类的父类,规定了泛型的下线
举例
public void test(List<?> list){
for(Object o:list){
System.out.println(o);
}
}
public void test(List<? extends AA> list){
for(Object o:list){
System.out.println(o);
}
}
public void test(List<? super AA> list){
for(Object o:list){
System.out.println(o);
}
}
多线程
创建线程的两种方式
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
*为什么是开启新线程是start方法而不是run方法?
进入start方法可以看到,最主要开启的新的线程的方法是start0()
方法,这个是JVM的内部方法,底层是c/c++编写,实际就是由底层创建了一个新的线程去调用了run方法
代码示例
//继承实现
public class Cat extends Thread{
@Override
public void run(){
int count = 0;
while (true){
System.out.println("我是小猫,喵喵喵"+ (++count));
if (count > 8){
break;
}
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
//实现Runnable接口实现
public class Dog implements Runnable{
@Override
public void run(){
int count = 0;
while (true){
System.out.println("我是小狗,汪汪汪"+ (++count));
if (count > 8){
break;
}
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
//主方法
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
cat.start();
Thread thread = new Thread(dog);
thread.start();
}
}
总结
继承了Thread的类可以直接调用start方法,但由于Java是单继承机制,所以可以实现Runnable接口,此时开启新线程就需要在创建一个新的Thread的类,并传入实例化的对象,然后调用其start的方法
区别
- 从Java的设计来看,继承Thread和实现Runnable没有本质的区别
- 实现Runnable的接口方式更适合多个线程共享一个资源的情况,并避免了单继承机制的限制
class Te implements Runnable{
public void run(){}
}
class main{
public static void main(String[] args){
Te t0 = new Te();
Thread t1 = new Thread(t0);
Thread t2 = new Thread(t0);
t1.start;
t2.start;
}
}
线程常用方法
- setName:设置线程名称
- getName:获取线程名称
- start:开始执行线程
- run:调用线程run方法
- setPriority:更改线程优先级
- setPriority:获取线程优先级
- sleep:休眠
- interrput:中断线程,不是结束线程 一般用于zheng'zai
线程插队
- yield:线程的礼让,让出CPU,让其他线程执行,但是礼让时间不确定,所以礼让不一定成功
- join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程的所以任务(会阻塞线程(主线程也会被阻塞))
守护线程
引入
当子线程是一个无限循环的线程时,主线程退出,子线程并不会退出,为了此时让子线程也退出,就用到了守护线程
解决办法
线程名.setDaemon(true);
Synchronized
线程同步机制
- 在多线程编程,一些敏感的数据不允许被多个线程同时访问,此时就是使用同步访问技术,保证数据在任何同一时刻,最多只有一个线程访问,以保证数据的完整性。
- 也可以理解成:线程同步:即当有一个线程对内存进行操作时,其他线程都不可以对这个内存进行操作,知道该线程完成操作,其他线程才能对该内存进行操作
实现同步的具体方法-Synchronized
- 同步代码块
synchronized (对象){
// 这里放需要被同步的代码
}
public void m1(){
// Code
synchronized (this){
// Code
}
}
- Synchronized放在方法声明中,表示整个方法为同步方法
public synchronized void m1(){
// 这里放需要被同步的代码
}
注意
选择同步代码块的对象 this 是指本对象,如果多个new对象对其进行start,那么是锁不住的,所以推荐采用实现Runnable接口的方法实现多线程
线程的死锁
介绍
多个线程占用了对面的资源,但不肯相让,导致了死锁。
释放锁
会释放锁的操作
- 当前线程的同步方法,同步代码块执行完毕
- 当前线程在同步方法,同步代码块中遇到了return或break
- 当前线程在同步方法,同步代码块中遇到了Error或Exception,导致异常结束
- 当前线程在同步方法,同步代码块中执行了wait()方法,当前线程暂停,并释放锁
不会释放锁的操作
- 当前线程在同步方法,同步代码块中执行了
Thread.sleep() 或者 Thread.yield()
方法 - 当前线程在同步方法,同步代码块时,其他线程调用了该线程的suspend()方法,该线程将会被挂起,不会被释放
四大核心函数式接口
函数式接口 | 称谓 | 参数类型 | 用途 |
---|---|---|---|
Consumer<T> | 消费性接口 | T | 对类型为T的对象应用操作,包含方法void accept(T t) |
Supplier<T> | 供给型接口 | 无 | 返回类型为T的对象,包含方法T get() |
Function<T,R> | 函数型接口 | T | 对类型为T的对象应用操作,并返回结果,结果是R类型的对象,包含方法R apply(T t) |
Predicate<T> | 判断型接口 | T | 确定类型为T的对象是否满足约束,并返回boolean 值,包含方法boolean test(T t) |
public class MyTest {
// Consumer Interface Demo
// 和 JavaScript 的 CallBack 有异曲同工之妙
public void consumer(Integer num, Consumer<Integer> consumer) {
consumer.accept(num);
}
@Test
public void testConsumer() {
// 可以这么理解 consumer(x,callback)
consumer(500,num -> System.out.println("The number is " + num));
}
// Supplier Interface Demo
public <T> T supplier(Supplier<T> supplier) {
return supplier.get();
}
public String hello(){
return "hello";
}
@Test
public void testSupplier() {
System.out.println(supplier(() -> hello()));
}
// Function Interface Demo
public <T,R> R function(T t,Function<T,R> function){
return function.apply(t);
}
@Test
public void testFunction() {
String function = function(100, num -> String.valueOf(num));
System.out.println(function);
}
// Predicate Interface Demo
public <T> void predicate(T t,Predicate<T> predicate) {
System.out.println(predicate.test(t));
}
@Test
public void testPredicate() {
predicate(100,num -> num == 1000);
}
}
Comments NOTHING