JDK:JAVA开发工具箱
JRE:JAVA运行环境
JVM:JAVA虚拟机
JDK包括JRE,JRE包括JVM
源代码JAVA虚拟机不会执行
编译就是让源文件 .java 文件成为JVM所认识的字节码文件 .class
源文件不参与程序的执行,所以源文件删除对程序的运行没有任何影响
一个源文件可以编译成多个.class文件,最终运行的是 .class文件
Javac 负责编译 java负责运行
一个java源文件可以定义多个类class
public的类不是必须的,可以没有
一个java只能有一个public修饰的类而且必须与文件名相同
凡是个人有权利命名的的单词都为标识符
类名、接口名首字母大写,后面每个字母的首字母同样大写
变量名、方法名首字母小写,后面每个单词的首字母大写
所有常量名全部大写,且单词与单词之间用下划线进行连接
java语言中的所有关键字都是全部小写
在方法体当中声明的变量为局部变量
在方法体之外,类体内声明的变量为成员变量
注:局部变量只在方法体当中有效,方法体执行完毕该变量的内存被释放掉
第一种:基本数据类型,可分为四大类八小类
第一类:整数型
第二类:浮点型
第三类:布尔型
第四类:字符型
八小类:
byte 1Byte,short 2Byte,int 4Byte,long 8Byte
float,doule
true,false
char
字符串型String属于引用数据类型
String字符串不属于基本数据类型范畴
java中除了基本数据类型之外,剩下的都是引用数据类型
1比特为一个1或0
1Byte=8bit 1字节---->8比特/位
1KB=1024Byte
1MB=1024KB
1GB=1024MB
1TB=1024GB
-----------------------------------
byte 1
short 2
int 4
long 8
float 4
double 8
boolean 1
char 2
byte是1个字节,是8个比特位,所有byte可以存储的最大数值为:01111111
注:在计算机中,一个二进制最左边的数字表示的是正负,0位正数
byte最大值为:2的7次方-1 (从2的0次方开始,不是从1)
byte:[-128~127]
short:[-32768~32767]
int:[-2147483648~2147483647]
char:[65535]
char为两个字节,short为两个字节,所以这两个表示的情况数量相同
不过char能表示更大的数字,因为char表示的文字,文字没有负数
JAVA默认的数据类型是int类型,所以当数据 大于 2147483647后需要手动在数据后方写上 L ,告诉虚拟机这个一个long类型的数据,防止编译报错
任何一个浮点型都比整数型空间大。
多种数据类型做混合运算的时候,最终的结果类型是“最大容量”对应的类型
注意:char+shor+byte这个除外
因为char+shor+byte混合运算的时候会各自先转换成int再做运算
java中规定,任何一个浮点型数据默认被当做double来处理,如果想当做float后面加上F/f
所有关系运算符的运算结果都是布尔类型,不是true就是false,不可能是其他值
注意:当一个表达式当中有多个加号的时候,遵循“自左向右”的顺序依次执行,若左边两个为数字第三个为字符串,那么结果就是前两个的数字之和与第三个字符创连接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Xe6vvHe-1602207826267)(img\运算规则.png)]
java.util.Scanner s=new java.util.Scanner(System.in);
System.out.println("请输入:");
int num=s.nextInt();
for(int h=1;h<=num;h++){
//完成打印出数字的行数
/*
第一行打印一个
第二行打印三个
第三行打印五个
第四行打印七个
第五行打印九个 ------> 2 X 行数 - 1 ==打印出的符号个数
以5行为例子
1行4个空格
2行3个空格
3行2个空格
4行1个空格
5行0个空格
*/
for(int k=0;k<(num-h);k++){
System.out.print(" ");
}
for(int f=0;f<2*h-1;f++){
System.out.print("*"); //这个控制富豪数量
}
System.out.println();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-syyceATb-1602207826273)(img\1000内完数.png)]
方法定义位置没有特别要求,定义在类体当中即可,先后顺序没有要求
局部变量的特点是:方法结束以后局部变量的内存空间会被释放掉,如果局部变量的内存空间不被释放掉,则再次调用时,会再次声明同一名称的变量,这是错误的
private: 同一个类
default: 同一个类,同一个包
protected: 同一个类,同一个包,子类
public: 所有
返回值类型可以为任何类型,java中合法的数据类型都可以,例如基本数据类型和引用数据类型
返回值通常是方法执行结束后的结果,结果通常是一个数据,所以称之为“值”,而且也叫做“返回值”.
调用者调用方法,返回值返回给调用者,没有返回值的方法不能使用变量进行数值接收
返回值可以选择不接受,但是不接受的话没有任何意义,返回之后内存被释放掉,因为没有变量接收
类名.方法名.[形式参数列表];
[]不一定非要存在
跨类调用方法使用类名.方法名,同一个类中可以省略类名
return ; 终止当前的方法
class fff{
public static void main(String [] arsg){
int x=aaa.m2(false); ---->使用变量对返回值进行接收
System.out.println(x);
}
}
class aaa{
public static int m2(boolean flag){
return flag ? 1:0; ----->名字随意,形参内部名字必须相同
}
}
方法调用:压栈 , 分配空间
方法结束:弹栈 , 释放空间
1.在同一个类中
2.方法名相同
3.参数列表不同 参数的顺序不同也可以
参数列表起作用的是类型而不是参数的名字
面向对象的三个术语:
OOA:面向对象分析
OOD:面向对象设计
OOP:面向对象编程
整个软件开发的过程,都是采用OO进行贯穿
在java语言中,得到“对象”之前,必须先定义“类”,“对象”是通过“类”这个模板创造出来的
类为一个模板:类中描述的是所有对象的“共同特征信息”
对象就是通过类创建出的个体
对象也称之为:实例
通过类这个模板创建对象的过程,叫做:实例化
类–【实例化】–>对象(实例)
对象—【抽象】–>类
类 = 属性 + 方法
属性就是成员变量,又称为实例变量
java中所有的类都是引用类型
局部变量必须先声明,后赋值才能访问,成员变量系统可以赋予默认值
注意:对于成员变量来说,没有手动赋值时,系统默认赋值
类型 默认值
--------------------------------------
byte 0
short 0
int 0
long 0L
float 0.0
double 0.0
boolean false
char \u0000
String null
--------------------------------------
JVM内存包括:栈、堆内存、方法区
栈内存存放的为局部变量和方法所需要的内存空间
堆内存存储对象,以及对象的实例变量
堆内存存放的是new的对象,new对象内部存放实例变量(成员变量)
方法区存放代码片段、代码字节码(.class),此区最先有数据,因为类最先被加载
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RRdPDGkZ-1602207826275)(img\JvmRam.png)]
实例对象一个对象一份,所有必须创建对象才能访问实例对象
java无法直接操作堆内存中的数据,只能通过引用来访问
被引用类型的数据修饰的变量必须用引用数据类型的数据赋值,可以二层引用
成员变量不能用类名直接访问,只能用对象来访问
**对象是存在堆内存中的,通过new关键字创建**
引用是保存内存地址的变量
java的一种规定,规定参数传递的时候和类型无关,不管是基本数据类型还是引用数据类型,统一都是将保存在盒子中的值复制一份传递下去
内存地址也是一个值,也是盒子中保存的一种数值
java中关于方法调用时参数的传递实际上只有一个规则:
不管你是基本数据类型,还是引用数据类型,实际上在传递的时候都是讲变量中保存的那个“值”复制一份,传过去
eg: int x=1;
int y=x; // 把x中保存的数值1复制一份传给y
Person p1=0x1234;
Person p2=p1; //把p1中保存的值-->地址复制一份传给了p2
引用指向的对象里面有多少对象实例,就可以 .(点)多少个对象实例
构造方法支持重载,构造方法功能是初始化值
如果没有主动写构造方法,则系统默认创建一个无参构造方法,若主动写出了构造方法,则系统默认的无参构造方法不会生效
实例变量只有在对象创建之后才会存在,对象没有创建,实例变量不会出现,调用构造方法必须使用关键字 new +类名+()
实例变量的默认值不是在类加载时候赋值,而是在构造方法被调用时赋的值,也就是创建对象时系统给实例变量赋予默认值
为了防止默认构造器需要使用时失效,可以手动写出来,则永久不会失效
有static修饰的方法可以通过类名.方法名进行调用
没有static修饰的方法称为实例方法,实例变量为对象变量,实例方法为对象方法,实例相关的都需要先new对象,通过“引用”进行调用
- 实例方法:对象相关的方法,对象级别的方法,应该是一个对象级别的行为
- 实例相关:实例变量+实例方法
- 封装后的实例变量不能访问,只能通过get、set方法进行访问与修改
- 可以在set方法里设置关卡防止数据故意破坏
方法体内声明的变量:局部变量
方法体外声明的变量:成员变量
成员变量又可以分为:
实例变量
静态变量
不需要对象参与即可访问的没有空指针的异常
需要对象参与的有可能存在空指针异常的情况
静态变量在类加载时初始化,不需要new对象,静态变量的空间就开出来
静态变量存储在方法区。
如果这个类型的所有对象的某个属性值都是一样的,不建议定义为实例变量,浪费内存空间。建议定义为类级别的特征,定义为静态变量,在方法区 中只保存一份,节省内存开销
实例的:一定需要使用“引用”来访问
静态的:建议使用“类名.”来访问,但是“引用”来访问也可以(不建议使用,容易产生误会),但是系统还是会使用“类名.”来访问
结论:只有空指针引用实例相关的,都会产生空指针异常
当方法中访问了实例变量,这个方法一定是实例方法
如果一个方法需要实例参与,则这个方法为实例方法
一个人一个情况的方法使用实例方法,所有的人一个情况的方法用静态方法与对象相关的方法使用实例方法
静态代码块:类加载时执行,别切只执行一次 ----->不常用
注:静态代码块在类加载时执行,且在main方法执行之前执行,执行遵循从上而下,实例变量在构造方法执行后才会开辟空间
静态代码块和静态变量都在类加载类加载后运行,具体谁先谁后代码顺序决定
1.对于一个方法来说,方法体中代码是有顺序,遵循自上而下执行
2.静态代码块1和静态代码块2是有先后顺序
3.静态代码块和静态变量有先后顺序
实例语句块在类加载时没有执行,在构造方法执行之前自动执行,mian方法后执行
1.是一个变量,是一个引用。this保存当前对象的内存地址,指向自身。所有严格意义上说,this代表的就是“当前对象”,this存储在堆内存当中对象的内部
2.this只能使用在是实例方法中,谁调用这个实例方法,this就是谁,所以this代表的是:当前对象,this可以在构造方法中使用
3.this大部分情况下可以省略,只要是调用了实例变量和实例方法,那么肯定是“引用.”+名称,不过这个“引用”为this.被省略掉,但是区分局部变量和实例变量的时候不能省略
4.可以在一个构造方法中使用this调用另一个构造方法,注:这两个构造方法必须在同一个类中才能做到代码复用
this()调用语法只能出现在构造方法的首行,并且只能一行
实例方法中调用实例方法不需要使用 “this.”
实例方法可以调用静态方法,静态方法调用实例方法需要先创建对象
所有实例相关的,都是通过“引用.”进行访问所有静态相关的,都是通过“类名.”进行访问
main()方法是静态方法,所以main()方法中没有this
只要调用的方法a和被调用的方法b在同一个类中:
this.可以省略
类名.可以省略
—>将父类中的代码复制,粘贴到子类当中
本质上,子类继承父类以后,是将父类继承过来的方法归为自己所有,实际上调用的也不是父类的方法,是他子类自己的方法(因为已经继承过来了)
凡是使用“is a”能描述的就可以进行继承
当打印“引用”时,println会自动调用 toString()方法
1.两个类要有继承关系
2.重写之后的方法和之前的方法具有:相同的返回值类型、相同的方法名、相同的形式参数类别
3.访问权限不能更低,可以更高
4.重写之后的方法不能比之前的方法抛出更多的异常,可以更少(父类抛出异常,子类可以不抛或者抛出少于父类抛出个数的异常)
1.方法覆盖与方法有关,与属性无关
2.私有方法无法覆盖
3.构造方法不能被继承,所以构造方法不能被覆盖
4.方法覆盖只是针对于实例方法,静态方法覆盖没有意义
java中允许向上转型,也允许向下转型
无论是向上转型还是向下转型,两种转型之间必须有继承关系
向上转型: 子---->父 (自动类型转换)
向下转型: 父---->子 (强制类型转换)
访问子类中特有的方法需要强制转换(向下转型)
Animal a = new Cat();
a.move();
编译器阶段:对于编译器来说,只知道a的类型为Animal,所有编译器在检查语法的时候会去Animal.class字节码文件中寻找move()方法,并且进行绑定,静态绑定
运行阶段:实际上是在堆内存中创建的java对象时Cat对象,所以在move的时候,真正参与move的对象是一只猫,所以运行阶段为执行Cat对象的move()方法,这个过程属于运行阶段绑定,动态绑定
具有两种状态称之为多态
Animal a = new Cat(); //编译阶段,a是父类,运行阶段,a是子类
a.move();//运行至此,a为子类,子类中有move()方法
Cat b =(Cat)a;//将引用a强制向下转型
b.catchMouth();//此时将a引用赋予了b,b属于子类,并包含此方法
如果想访问的方法是子类对象中特有的方法,父类中没有,就必须强制向下转型
erhu e = new erhu(); // 创建二胡对象
pinao p1 = new pinao();//创建钢琴对象
violin v = new violin();//创建小提琴对象
person p = new person();// 创建用户对象
p.makeSound(e);//用户引用调用自己的makeSound方法,并传入 p.makeSound(p1);//instrument的子类,向上转型实现了多态
p.makeSound(v);//
方法覆盖必须和多态机制了联合使用才有意义
若是没有多态机制:方法覆盖可有可无,也可以完全没有,当子类无法满足父类业务要求,完全可以重新定义新方法
静态不谈论覆盖,私有不能覆盖
一般重写直接复制粘贴即可
对于返回值类型是基本数据类型来说,必须一致
对于返回值类型是引用数据类型来说,重写之后返回值类型可以变的更小(意义不大)
子类构造方法执行之前会先执行父类的构造方法,因为子类构造方法第一行省略了super()方法,默认执行,无法避免。此机制模拟现实先有父后有子
若父类构造方法为有参数的构造方法,子类super()无参数,编译器报错,即使没有写super()同样报错
恰当的时间使用:构造方法中使用,构造方法中使用实际上调用了父类的构造方法为属性进行赋值
super()代表的就是“当前对象”的那部分父亲类型的特征,只创建了一个对象,并且特征也同样属于自己的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U6nVOXoa-1602207826278)(img\继承.png)]
父中有,子中有,如果想在子类中访问父类属性或者方法,则super关键字不能省略
super不是引用,super也不保存内存地址,super也不指向任何对象。
super只是代表当前对象内部的那一块父类型的特征
super.属性名 访问父类的属性
super.方法名(参数) 访问父类的方法
super(实参) 访问父类的构造方法
继承会将父类中所有的属性和方法继承过来(暂时不考虑构造方法和私有属性和方法),那么”this.”实际上访问了复制父类的属性和方法,“super.”访问了父类中的属性和方法,若是子类和父类含有相同名称的方法名和属性名,则“this.”实际上访问的是子类本身的属性和方法,“super.”访问的是父类中的属性和方法
可以修饰变量、方法和类,表示最终的、不可更改的。
final修饰的类无法继承;
fina修饰的方法无法被覆盖;
final修饰的变量只能赋一次值;
final修饰的引用只能赋一次值(赋一次内存地址,即使创建对象;所初始化的值相同,且new新对象跟初始化值无关)
局部变量没有初始值,实例变量有初始值
被final修饰的引用从始至终无法在指向其他对象,也无法赋值null,也不会被垃圾回收机制回收,直到此方法直接结束,才会释放空间,但是对象内部的实例变量的值可以重新赋值
final修饰的实例变量,系统不会赋予默认值,要求程序员手动进行赋值,程序员可以直接在实例变量之后赋值,也可以在构造方法中赋值(若没有自己写构造方法,系统会自动创建构造方法并对实例变量进行赋值,而final修饰的实例变量不允许系统赋默认值,所以会报错。程序员只要在系统赋予默认值之前–也就是构造方法中直接赋值–直接赋值,则不会报错)
final修饰的变量一般添加static修饰
1.final修饰的类无法继承
2.final修饰的方法无法覆盖
3.final修饰的变量只能赋一次值
4.final的引用一旦指向某个对象,则不能重新指向其他对象,但该引用指向的对象内部的数据可以修改
5.final修饰的实例变量必须手动初始化,不能采用系统默认值
6.final修饰的变量实例一般和static联合使用,成为常量,也可加public
静态变量和静态方法都是“类名.”名称就可以访问,若静态变量放在了方法中,“类名.”还能找得到么,需要再次“.”一次么,还是说静态变量必须放在方法体外?
方法内部声明的变量一般是局部变量,方法结束后变回摧毁空间,同样也便于系统对内存进行各种分配。在人性化的设计中,静态变量大都用来供外界访问或类中各个方法共享。你在一个方法中定义了一个静态变量,那对于其他方法来说,前者内部是不可见的。且对于外界来说同样也是不可见的。这样便毫无意义。
main方法内部不能创建静态变量,静态方法内部不能创建静态变量,原因是变量加上static后属于整个类,是公共变量,而方法内部的变量是局部变量,服务于本方法,在本方法内创建了公共的变量,这一做法没有任何的意义
静态的域(数据成员)、静态的方法都是属于类一级的。
加了static的变量是类一级数据成员,有什么理由放在一个方法里面。方法里面的应该是局部的只有本方法才会用的,在方法里面声明一个类一级的数据成员有何意义了,相反,对以后代码的查错和再读都不利。所以JAVA不允许这样做
**不仅静成方法中不能声明静态数据成员,就算不是静态方法,其中照样不能声明静态数据成员。**
static、final 修饰的为常量,全部大写,单词与单词之间用下划线进行连接,static、final这两个单词在声明时顺序随意
常量与静态变量的一样都是存储在方法区,都在类加载时初始化,区别在于常量的值不能改变,常量一般公开的
因为static属于类级别,属于共有,若某一个属性所有的对象都一样且不需要再次更改,例如国籍都是中国不需要更改且每个人都一样,那么此属 性声明为类级别节省空间,再加上final不允许修改较为合理 ------->常量
类可以创建对象,因为对象时实际存在的,抽象类不能创建对象,因为类实际上不存在
类与类之间的共同特征,将这些共同特征抽象出来就是抽象类
抽象类也属于引用数据类型,不是基本数据类型
[修饰符列表] abstract class 类名{
类体;
}
抽象类不能创建对象,无法实例化,所以抽象类是用来被子类继承的
抽象类的子类也可以是抽象类或者非抽象类
抽象类不能被final修饰,因为抽象类天生是为了继承,而final不能被继承;抽象类虽不能实例化,但是可以有构造方法,供子类使用
1.抽象类没有实现的方法,没有方法体(大括号的地方为“;”结尾)
2.抽象法的返回值前面有abstract
抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中
非抽象类继承抽象类,必须实现所有的抽象方法(也就是将抽象方法复制过来,删掉分号换成大括号,删掉abstract关键字);如果子类是抽象类,则不需要去重写、覆盖、实现抽象方法
这里的覆盖或者重写可以写成实现(对抽象方法的实现)
面对抽象编程(引用的类型为抽象类的类名—>向上转型,多态机制)
1.抽象类定义就是在class前面加上abstract关键字
2.抽象类无法实例化,无法创建对象,所以抽象类生来就是被继承
3.final和abstract关键字不能联合使用
4.抽象类的子类可以使抽象类,也可是非抽象类
5.抽象类虽不能被实例化,但是抽象类有构造方法,这个方法供子类使用
6.抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中
7.一个非抽象的类继承抽象类,必须对所有抽象方法进行覆盖、重写、实现
java中凡是没有方法体的都是抽象类
错误,java源码中有许多方法没有abstract关键字,也没有方法体
抽象类的作用就是为了减少实现接口的难度
抽象类实现接口,可以选择不实现接口中的所有方法,那么一个类继承此抽象类,则必须实现此抽象类继承接口中的所有方法;子类想选择性重写接口中的方法,那么抽象类只需要重写子类不想要重写的方法
/*
定义的接口,其中有四个方法
*/
interface Window{
void test01();
void test02();
void test03();
void test04();
}
/*
抽象类实现了接口中的test01、test02方法
*/
abstract class mid implements Window{
public void test01() {
}
public void test02() {
}
}/*
继承抽象类,必须实现父抽象类没有重写接口中的方法
*/
class tesT extends mid{
@Override
public void test03() {
}
@Override
public void test04() {
}
}
非抽象类实现接口,必须重写接口中的所有方法,子类继承非抽象类,可以选择重写父类中的方法,也可以选择不重写
/*
定义的接口,其中有四个方法
*/
interface Window{
void test01();
void test02();
void test03();
void test04();
}
/*
非抽象类实现此接口,重写其中所有的方法
*/
class mid implements Window{
public void test01() {
}
public void test02() {
}
@Override
public void test03() {
}
@Override
public void test04() {
}
}/*
继承非抽象类,可以选择不重写,也可以重写需要的
*/
class tesT extends mid{
}
1.一种引用数据类型,编译之后也是 .class 文件
2.接口完全抽象,或者也可以说接口是特殊的抽象类
3.接口支持多继承,一个接口可以继承多个接口
4.接口包含两部分内容,一部分常量,一部分抽象方法,再无其他
5.接口中所有的元素都是public修饰的,公开的
6.接口中的 public abstract关键字可以省略(编译器编译时默认全部加上)
7.接口中的方法都是抽象方法,因此不能有方法体
8.public abstract final可以省略,因为接口里只有抽象方法和常量,访问常量就是“接口名.”,接口也是类
9.在接口中随意写的数值都是常量,且不可再次更改
非抽象类继承接口必须实现接口中的全部方法,原因是非抽象的类继承(实现)接口,接口也是一种抽象类,所以非抽象类就必须实现其中的方法
子类重写父类的方法,权限不能比父类更低,所以非抽象类继承接口重写其中的所有方法的修饰符必须是public
即使父类为抽象类或者是一个接口,都可以使用多态,即向上转型
继承不需要强制实现父类的方法,而非抽象类继承抽象类、非抽象类实现接口则必须实现其中所有的抽象方法,并对其重写、覆盖、实现(已经验证多次)
接口名 引用 = new 子类名() //面向接口编程
抽象类名 引用 = new 子类名() //面向抽象编程
一个类可以同时实现多个接口,弥补了单继承的缺点
接口与接口之间强制类型转换时候没有继承关系也可以转换,但是运行的时候还是会出现ClassCastException,最终和之前一样需要instanceof进行判断
接口通常模拟行为动作,可以使用接口引用指向子类型,形成多态
接口在开发中的作用,类似于多态在开发中的作用
Cat is a Animal,但凡满足is a的表示都可以设置为继承
Customer has a FoodMEenu,但凡满足has a的表示都以属性的形式存在
抽象类是半抽象的,接口是完全抽象的
抽象类中有构造方法,接口中没有构造方法
接口与接口之间支持多继承(不需要强制重写父接口的方法),类和类之间只能单继承
一个类可以同时实现多个接口,一个抽象类只能继承一个类(单继承)
接口中只允许出现常量和抽象方法
接口与接口之间可以多继承,即一个接口可以继承多个接口,注意:接口只能继承接口,不能继承类;普通类只有单继承
如果接口A继承了接口B、接口C,则一个普通类实现接口A则必须重写三个接口中的所有方法
is a 、 has a 、 like a
is a:
Cat is a Animal
凡是能够满足is a的表示“继承关系”
has a:
I has a Pen
凡是能够满足has a关系的表示“关联关系”
关联关系通常以“属性”的形式存在
like a:
Cooker like a FoodMenu
凡是能够满足like a关系的表示“实现关系”
实现关系通常是:类实现接口
完整的类名带有包名的
属性(4个都可以使用)
方法(4个都可以使用)
类 (public和默认都能用,其他不行)
接口(public和默认都能用,其他不行)
Object类中equals方法判断两个对象是否相等,功能是否够用?
判断两个基本数据类型的数值是否相等使用“==”,比较的是其中的值
判断两个引用数据类型使用“==”,比较的是引用所保存的内存地址
所以判断两个java对象是否相等,不能使用“”,因为“”比较的是两个对象的内存地址
Object中的equals方法内部默认使用的是“==”判断两个对象是否相等,明显这个方法不太够用,所以需要重写equals方法,否则系统默认调用Object中的方法
重写父类的equals方法不要修改参数列表,使用向下转型
String类已经重写equals方法,比较字符串可以直接使用此方法,不要使用“==”
String类已经重写了toString方法
java中基本数据类型比较是否相等,使用“==”
java中所有的引用数据类型比较,统一只用equals方法来判断是否相等
1.是否为空或者无法向下转型,返回false
2.比较的对象是否内存地址相同,返回true
3.如果属性值相同返回true,否则返回false
SUN公司已经重写了String字符串的比较方法,可以直接使用,自己创建类的比较方法需要重写,因为所有的类默认继承Object的equals方法,这种默认是“==”的比较方法是不准确的,所以重写要彻底
finalize()方法实际上是SUN公司为java程序员准备的一个时机,垃圾销毁时机。如果希望在对象销毁时机执行一段代码的话,这段代码就要卸载 finalize()方法当中,和静态代码块功能类似。此方法不需要手动调用,GC垃圾回收机制会自动调用
垃圾太少,finalize方法可能不会启动,这是可以使用 System.gc建议GC启动,只是建议,启动概率变高(静态方法)
以上不太重要
1.在类的内部又定义了一个新的类,被称为内部类
2.分类: 静态内部类:类似于静态变量
实例内部类:类似于实例变量
局部内部类:类似于局部变量(不经常使用)
3.可读性很差,尽量不使用
4.匿名内部类是局部内部类的一种,因为没有名字所以叫做匿名内部类
匿名内部类就是没有类名
new Computer()就是有名字,Computer就是名字
匿名内部类可以省去实现类,表面上是对接口进行new,实际上是接口后面的“{ 、}”对接口进行实现,实现的代码在大括号内部实现
mm.mySum(new Interface(){
//这段代码本身是一个调用的方法
public int Sum(int x,int y){
return x+y;
}
} a-->形参1, b-->形参2)
1.Java语言中的数组是一种引用数据类型,不属于基本数据类型,数组父类是Object
2.数组实际上是一个容器,可以同时容纳多个元素
3.数组可以存储“基本数据类型”的数据,也可以存储“引用数据类型”的数据
4.数组因为是引用类型,所以数组对象是堆内存当中的(数组是存储在堆当中的)
5.数组当中如果存储的是“java对象”的话,实际上存储的是对象的“引用(或者叫内存地址)”,数组中不能直接存储java对象
6.数组一旦创建,在java中规定,长度不可变(在java中数组长度不可变)
7.数组分为:一维数组、二维数组、三维数组、多维数组(后面两个很少)
8.所有的数组对象都有Length属性(java自带),用来获取数组中元素的个数
9.java中的数组要求数组中元素的类型统一,比如int类型数组只能存储int型,不能存储其他,存储类型要求统一
10.数组中里面元素的内存地址是连续的
11.所有的数组都是拿第一个小方框的内存地址作为整个数组对象的内存地址
数组的存储地址在空间上是连续的
每一个数组类型相同,所以占用大小一样
知道第一个数组的内存地址,知道每个元素占用的大小,又知道下标,所以可以通过数学表达式算出某个元素的内存地址,直接通过内存地址定位元 素,所以效率最高
增加删除效率低
不能存储大容量数据,因为很难在空间上找到一块很大的连续的空间
注:最后一个增加效率不影响
int [] array = {
100,200,55};
int [] array = new int [5]; //数字表示数组元素个数,初始化一个5个长度int数组,默认值0
String [] names = new String[6]; //初始化6个长度数组,默认值null
java的toString方法是在输出对象的时候自动调用此方法
当创建数组时候,确定数组中存储哪些具体的元素时,采用静态初始化方式
当创建数组时候,不确定将来数组中存储哪些数据,可以采用动态初始化方式预先分配空间
数组只能放相同引用(就是类)数据类型的数据,也可以放子类型数据实现多态
Object[] arr = new {
new Husband(),new Person(),”123”,345}
Object是所有类的超类,字符串也是java对象,属于String类型;“123” 这是一个字符串对象,不需要new对象
String ss =”123” / String ss = new String(“123”);
String本身就是一个类,是引用数据类型
新创建一个更大容量的数组,然后将小容量数组汇总数据一个一个拷贝到大的数组当中,数组扩容效率较低
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tUjos0nx-1602207826282)(img\数组拷贝(对象)].png)
二维数组其实是一种特殊的一维数组,特殊在这个一维数组当中的每一个元素是一个一维数组
实例变量可以写在构造方法中进行赋值,也可以写在构造方法之外进行直接赋值,没有什么区别,因为实例变量的“=”赋值总是在调用构造方法时进 行赋值
属性的默认值可以在无参构造方法里面进行赋予默认值
学会灵活运用三目运算符
相邻两个数字进行比较,将最大值放到后面
for(int j =0;j<n.length;j++){
//将内层循环从头做到尾(效率不高)
for(int i=0;i<n.leng-1;j++){
//最后一个数字不用比较
if(若后面的数字小于前面的数字){
二者元素位置调换
}
}
}
拿出最小的数值,与开始的数值进行交换
相对于冒泡排序,选择排序的每一次交换位置都是有意义的
选择排序比冒泡排序效率高,选择排序交换位置有意义
假设有n个数字进行排序
1 2 3 4 5 6 7 8 9 或者 9 8 7 6 5 4 3 2 1
若min值没有改变,则不要交换位置
min的值一旦改变,说明它已经不是最小值
若min的值等于后面“一堆数字比较的结果”,与开始假设不同,交换位置
/*
i的值正好是最左边的位置的下标
最外层的循环只需要执行n-1次即可
*/
for(int i =0,i<n.length-1;i++){
int min=i; //假设最小值,并且赋予最小值的下标数字
/*
内层需要选出最小的值,所以最后一个数字也需要进行比较
*/
for(int j =i+1;j<n.length;j++){
if(arrays[j]<arrays[min]){
min=j; //如果这里一次也没有执行,则min的值一直没有变
}
}
/*
执行到这里说明,除了第一个数字,后面所有的数字已经进行过比较且,选出了最小的值并获得下标
再与当时假设的最小元素的下标进行比较,下标相同,说明假设正确不 需要进行交换位置,下标不同,说明开始假设最小元素错误,交换两 个元素位置
*/
if(min!=i){
进行交换;
}
}
一直折半,知道中间那个元素恰好是被查找的元素
int begin=0;
int end=n.length;
int mid=(begin+end)/2;
/*
循环条件结束条件是开始元素大于了结尾元素就停止
或者mid中间元素等于目标数字
*/
while(begin<=end){
if(arrays[mid]==desNumber){
打印查到目标数字;
}else if(arrays[mid]>desNumber){
/*
mid中间数字大于目标数字,说明目标数字在mid中间数字左边,则开始元素不需要改变,结尾元素需要mid -1 ,然后重新计算mid中间数字值
*/
end=mid-1;
}else if(arrays[mid]<desNumber){
/*
mid中间数字小于目标数字,说明目标数字在mid中间数字右边,则结尾元素不需要改变,开始元素需要mid+1 ,然后重新计算mid中间数字值
*/
begin=mid+1;
}
}
return -1;
1.String表示字符串类型,属于引用数据类型,不属于基本数据类型
2.在java中随便使用双引号括起来都是String对象
3.java中规定,双引号括起来的字符串不可变,从始至终不可变
4.在JDK中双引号括起来的字符串,例如“abc”都是直接存储在“方法区”的“字符串常量区”中的。字符串使用频率较高,为了执行效率所以把字符串放到了方法区的常量池中
垃圾回收器不会释放常量
字符串比较可能会产生空指针异常:
String k=null;
System.out.println(“xyz”.equasl(k)); //建议使用此方法
String构造方法常用:
1.String =”” /String ss = new String(“”);
2.new String(char[]数组) / new String(char[],起始位置,长度)
3.new String(byte[]数组) / new String(byte[],起始位置,长度)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hbgNlEHA-1602207826284)(img\Java面试.png)]
//将字符串转换为字节数组。相当于编码,字符串-->字节
byte[] bytes ="HELLO WORLD".getBytes();
for (int i = 0; i <bytes.length ; i++) {
System.**out**.print(bytes[i]+" ");
}
判断数组长度是Length属性,判断字符串长度是Length()方法,属性不带“()”,方法带上“()”
//将字符串转换成char数组
char []aa = "我是中国人".toCharArray();
作用: 可以去除字符串左右的空白,中间的空白无法消除
字符串的拼接建议使用append()方法,不要使用“+”连接
“+”连接字符串,会在方法区的常量池中不停的创建对象,浪费空间
String修饰的字符串不能被修改是因为底层中 byte[]数组已经被final修饰,引用无法指向其他的内存地址,且数组长度一旦确定不可改变,所以String修饰的字符串从始至终已经不可改变
StringBuffer可以拼接字符串是因为底层中的byte[]数组没有被final修饰,当需要拼接字符串时会调用数组的拷贝方法,创建更大的数组并将数据拷贝到其中,引用重新指向新的数组,完成字符串的拼接功能,而原来的字符串对象没有引用指向它而被垃圾回收机制回收
在创建StringBuffer的时候尽可能给定一个初始化容量,最好减少底层数组的扩容次数,预估计并给一个大一些的容量
StringBuffer方法都有synchronized关键字修饰,线程安全
StringBuild没有被关键字修饰,线程不安全
二者都可以进行字符串的拼接
toString()方法返回的是String类型,所以返回int等基本数据类型需要valueOf()方法
java中为8中基本数据类型准备了8中包装类,8中包装类属于引用数据类型,原因是8中基本数据类型不够用
某些时刻调用方法时需要传入参数,而某些参数是引用数据类型,基本数据类型无法传进去,这个时候需要相对应的包装类传进去
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p42wVRkX-1602207826285)(img\包装类.png)]
/*
123这个基本数据类型通过构造方法的包装:基本数据类型--->引用数据类型
*/
Integer i = new Integer(123); (装箱)
//Ingeger父类为Number,所以继承并且调用父类中的float方法
//引用数据类型-->基本数据类型
float f =i.floatValue(); (拆箱)
//将引用数据类型-->基本数据类型
int retValue = i.intValue();
JDK9以后已过时:
使用构造方法将基本数类型转换为引用数据类型称之为装箱
使用父类中的方法将引用数据类型转换为基本数据类型称之为拆箱
其他基本相同
String被定义之后不可改变不是说引用指向不能改变,而是后面的字符串对象不可改变
/*
不可改变指的是“ ”括起来的字符串不可改变
不是引用不能改变,引用可以重新指向,字符串不能改变从始至终
*/
String s="ABC";
System.**out**.println(s); //打印出来是 ABC
s="cbd";
System.**out**.println(s); //打印出来是 cbd
“+、-、*、/”自动拆箱,“==”比较时不会自动拆箱
1.数值相同且范围在 [-128 – 127]范围内,对象内存地址相同
2.数值相同范围超过[-128 – 127],对象内存地址不同
3.数值相同且范围没有超过,使用构造方法创建对象,内存地址不同
4.数值不同且在范围内,对象内存地址不同(看内存即可明白)
/*
二者比较为false,因为这是自动装箱,有例外情况,如上
*/
Integer x = 1000;
Integer y = 1000;
java中为了提高程序的执行效率,会在类加载时在方法区创建缓存,缓存大小为[-127–128],当用户定义的数值大小在这个范围之内,系统可以直接调用整数型常量池中的数据而不用重新创建对象,当取值范围超过时会重新创建对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XZLWbYE6-1602207826287)(img\Integer内存.png)]
Integer方法中有一个静态方法 parseInt,将字符串数字转换成int类型数,其他包装类型都有此静态方法,使用“类名.”直接调用即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fVdb9PNw-1602207826288)(img\String、Integer、int转换.png)]
Date类中的默认时间格式需要改变,如果不习惯的情况下:
1.可以使用SimpleDateFormat的构造方法定义时间格式
2.调用SimpleDateFormat中的format方法传入时间对象
3.format会返回一个字符串String
//先创建时间对象
Date nowDate = new Date();
//定义时间格式
SimpleDateFormat ss = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
//调用format()方法将时间对象转换为String字符串
String nowTime = ss.format(nowDate)
//输出时间
System.**out**.println(nowTime);
//定义一个字符串
String time ="2008-8-8 15:08:59";
//定义时间格式,格式必须与字符创中的格式相同,否则报错
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//调用parse()方法将字符串转换为时间对象
Date times = sdf.parse(time);
System.**out**.println(times);
System中的静态方法currentTimeMillis()可以计算从1970-1-1 00:00:00 000至今所有的毫秒数,这个方法可以用于计算程序的执行时间,在需要执行程序前后调用此方法相减可得
Date时间的构造方法的参数单位是毫秒,不加参数默认输出此时此刻的时间,加上参数是输出“默认时间+毫秒数”后的时间
在财务软件中,Decimal是不够用的,需要用到精度极高的BigDecimal,不是基本数据类型,属于java对象(引用数据类型),SUN公司提供,专门用在财务软件当中
int num = random.nextInt(); //随机产生int范围内的随机数字
int num = random.nextInt(10); //随机产生在0~9范围内的随机数字nextInt翻译为:下一个int类型的数据是10,表示只能取到9
public static void testRandom(){
Random random = new Random();
int[] arrays= new int[10]; //声明数组的个数
for (int i = 0; i <arrays.length ; i++) {
arrays[i] = -1; //为数组中的数组赋予默认值
}
int index = 0; //定义初始下标
/*
循环次数不一定是10次,因为只有生成的随机数不存在
index的数值才会 +1 ,最终才会不满足循环条件从而终止循环
若没有将数组初始值赋为-1,那么此程序将会成为死循环
因为最后一个数值一定是0,而初始值与随机值一直相同
index的值永远无法 +1 ,永远不满足循环条件而成为死循环
*/
while (index <arrays.length){
int num = random.nextInt(10); //赋予num生成的随机数
if (!**compare**(arrays,num)){
//判断是否存在
arrays[index++]= num ; //将随机数放入数组内不
}
}
//打印出数组内容
for (int i = 0; i <arrays.length ; i++) {
System.**out**.print(arrays[i]+" ");
}
}
public static boolean compare(int [] args,int key){
//循环遍历数组,判断随机生成数组是否存在
for (int i = 0; i <args.length ; i++) {
if (args[i]==key)
return true;
}
return false;
}
一枚一枚可以列举出来,建议使用枚举类型
枚举编译之后生成class文件,也是一种引用数据类型,枚举中的每一个值可以看做是常量
enum 枚举名{
枚举名1,枚举名2…
}
异常是类,可以被创建对象并被打印到控制台
java中,异常是以类的形式存在,每一个异常都可以创建一个异常对象
运行过程中遇到异常,JVM会自己创建异常对象并打印出,这个看不到
Object下有Throwable(可抛出的)
Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,若不处理编译报错,因此得名编译时异常)
RuntimeException:运行时异常(在编写程序阶段程序员可以预先处理,也可以不管)
编译时异常和运行时异常,都是发生在运行阶段,编译阶段异常是不会发生的
因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错
所有异常都是在运行阶段发生的,因为只有程序运行阶段才可以new对象
因为异常的发生就是new异常对象
编译时异常和运行时异常区别:
编译时异常一般发生的概率比较高(或叫受检异常,受控异常)
运行时异常一般发生的概率比较低(未受检异常,非受控异常)
不同类型的异常抛出不可用,抛出异常的父类可用,建议对异常捕捉
throws抛出异常的方式:异常位置的以及后面的代码不会继续执行
try…catch方式:异常捕捉后程序继续向后执行,另外注意**,try语句块中某一行出现异常,该行后面的代码不会执行**
一个方法体当中出现异常使用上报,这个方法体结束,类似return;
try…catch不会结束整个方法体,只会让try语句块结束运行,不会让整个方法体结束运行 catch可以写多个,但异常顺序必须为从小到大,括号内也可以“|”表明多个异常类型
getMessage:获得异常信息,放回字符串,可以打印件简单的提示
printStack:打印异常的堆栈信息(一般使用次方法)
1.finally字句中的代码是最后执行并且一定会执行,即使try语句块中出现异常,finally中的语句也会执行
2.catch可以省略不写。try语句块中即使写return,finally也会执行,不过写在try语句块后面的代码无法执行且报错
3.try不可以单独使用,try和finally可以连用
public static int number(){
int i = 100;
try {
/*
代码必须从上而下,return必须最后执行,反编译结果,程序会自己
创建一个临时变量j并将i的值赋予j,然后 i++,最后输出j的值
*/
return i;
}finally {
++i; //i++
}
}
final:是一个关键字,表示最后的、最终的、不可改变的,一般修饰常量
finally:也是一个关键字,与try联合使用,使用在异常机制中,finally语句块中的代码必须执行
finalize():是Object类中的一个方法,JDK9后被启用,作用是对象毁灭前的提示,与GC垃圾回收机制一起使用
1.编写一个类继承Exception或者RuntimeException
2.提供两个构造方法,一个有参一个无参
使用方法:定义方法并抛出自定义异常的类名,throw出自定义异常对象
类在强制类型转换过程中,如果类转换成接口类型,那么类和接口之间不需要存在接口关系
/*
往数组中增加数据,此算法很精辟
*/
public void addWeapon(Weapon wa){
//传入一个引用数据类型参数
for (int i = 0; i <arrays.length ; i++) {
//循环数组
if(null == arrays[i]){
//判断当前位置是否为空
arrays[i] = wa ; //不为空则将数组中i的数据放入此位置
return; //结束此方法
}
}
}
数组其实就是一个集合。集合实际上就是一个容器,可以来容纳其他类型的数据
集合不能直接存储基本数据类型,另外集合不能直接存储java对象,集合当中存储的是java对象的内存地址(或者说集合存储的是引用)
list.add(100); //自动装箱Integer
集合在java中本身就是一个容器,是一个对象
集合中任何时候存储的都是“引用”
在java中,每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构当中
使用不同的集合等同于使用了不同的数据结构
new ArrayList(); // 创建了一个集合,底层是数组
new LinkedList(); // 创建了一个集合对象,底层是链表
new TreeSet(); // 创建了一个集合对象,底层是二叉树
1.单个方式存储元素:
单个方式存储元素,这一类集合中超级父接口:java.util.Collection;
2.键值对的方式存储元素:
键值对方式存储元素,这一类集合中超级父接口:java.util.Map;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GWg0suGO-1602207826289)(img\Collection01.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eyVxIAkZ-1602207826290)(img\Collection02.png)]
Collection对象调用Iterable父接口中的iterabor()方法,获得迭代器,获得迭代器后通过使用Iterabor接口中的三个方法对Collection集合中元素的迭代
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yZ4ZIPQq-1602207826291)(img\Itertor.png)]
//后面的集合随便算写,无关大雅
Collection cc = new ArrayList();
cc.add(123);
cc.add("abc");
cc.add(new Object());
//1.获取集合对象的迭代器对象Iterator
Iterator it = cc.iterator();
//2.通过以上获取的迭代器对象开始迭代/遍历集合
while (it.hasNext()){
Object object = it.next();
System.**out**.println(object);
}
迭代器是一个对象,这个对象有两个方法hasNext()和next()
注意:集合结构只要发生改变,迭代器必须重新获取(向集合中添加元素后需要重新获取迭代器),若集合中没有数据而获取迭代器,之后添加数据进行迭代,运行结果会出现异常
Iterator it = cc.iterator();
/*
remove删除了集合中的元素,集合的结构发生了改变,迭代器
需要重新绑定一次,否则会出现ConcurrentModificationException异常
这个例子运行出现了异常
*/
while (it.hasNext()){
Object obj = it.next();
cc.remove(100);
System.**out**.println(obj);
}
public static void test01(){
/*
使用了迭代器自带的remove方法,不会出现异常
且最后集合中的元素个数已经清零
*/
Collection cc = new ArrayList();
cc.add(100);
cc.add(200);
cc.add(300);
Iterator it = cc.iterator();
while (it.hasNext()){
Object objs = it.next();
it.remove();//删除的是迭代器指定的当前元素
System.**out**.println(objs);
}
System.**out**.println(cc.size()); //0
}
获取的迭代器对象遍历了集合,此时相当于对当前集合的状态拍了一个快照,迭代器迭代的时候会参照这个快照进行迭代,集合对象调用remove方法,没有通知迭代器,导致迭代器的快照与原集合状态不同,从而报错;迭代器自带的remove方法则会在快照中删除指定的元素,同时也会删除结合中的指定元素,从而不会运行报错
迭代器是一个对象,这个对象有两个方法hasNext()和next()
ArrayList(类):集合底层采用数组数据结构,线程不安全
LinkList(类):集合底层使用双向链表数据结构
Vector(类):集合底层使用数组数据结构,线程安全,效率较低不常用
HashSet(类):HashSet集合在new的时候,底层实际上new了一个HashMap集合。向HashSet集合中存储元素,实际上存储到HashMap集合中
HashMap集合是一个哈希表数据结构
SortSet(接口):SortSet集合存储元素的特点,由于继承了Set集合,所以它的特点也是无序不可重复,但是放在SortSet集合中的元素可以自动排序,我们称为可排序结合,放到该集合中的元素是自动按照大小顺序排序
SortSet接口–>TreeSet类(实现SortSet接口)
TreeSet(类):TreeSet集合底层实际上是TreeMap,new TreeSet集合的时候,底层实际上new了一个TreeMap集合,向TreeSet集合中存放数据的时 候,实际上是将数据放到TreeMap集合中
TreeMap集合底层采用了二叉树数据结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Fjpixh6-1602207826292)(img\A_MAP.png)]
1.Map集合和Collection集合没有关系
2.Map集合以key和Value的这种键值对的方式存储元素
3.key和value都是存储java对象的内存地址
4.所有Map集合的key特点:无序不可重复
Map集合的key和Set集合存储元素特点相同
ArrayList:底层是数组
LinkedList:底层是双向链表
Vector:底层是数组,线程安全,效率低,使用较少
HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分
TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分
HashMap:底层是哈希表
Hashtable :底层也是哈希表,只不过线程安全,效率较低,使用较少
**Properties:线程安全,并且key和value只能存储字符串String**
**TreeMap:底层是二叉树,TreeMap集合的key可以自动按照大小顺序排序**
有序可重复
有序,存进去的顺序和取出的顺序相同,每一个元素都有下标
可重复,存进去1,可以再存储一个1
无序不可重复
无序,存进去的顺序和取出的顺序不一定相同另外Set集合中元素没有下标
不可重复,存进去1,不能再存储1
首先是无需不可重复,但是SortedSet集合中的元素是可排序的
无序,存进去的顺序和取出的顺序不一定相同,Set集合中元素没有下标
不可重复,存进去1,不能再存储1
可排序,可以按照大小排序
Map集合的key就是一个set集合
往Set集合中放数据,实际上放到了Map集合的key部分(源码)
泛型这种语法机制只是在程序运行编译阶段起作用,只是给编译器参考(运行阶段泛型没有用)
没有使用“泛型”之前,Collection可以存放Object的所有子类型(集合不能直接存储基本数据类型,也不能存放java对象,只是存储java对象的内存地址)
使用“泛型”之后,Collectio只能存放某个具体类型
1.集合中存储的元素类型统一了
2.从集合中取出的元素类型是泛型指定的类型,不需要进行大量“向下转型”
导致集合中存储的元素缺乏多样性
<>里面只是一个标识符,随便写,new对象的时候定义了什么类型,使用含有<>的方法返回值类型是new对象时定义的类型
类有泛型而不选择使用,默认为Object类型
public class GenericTest02 {
public static void main(String[] args) {
//创建对象时没有使用泛型,默认为Object类型
Zoo zz = new Zoo();
//方法参数应该传入默认Object类型参数
zz.doome(zz);
//创建对象时使用了泛型并定义了String字符串类型
Zoo<String> z = new Zoo<>();
//调用方法时应该传入创建对象时定义的类型
z.doome("z");
}
}
/*
<>里的标识符随便写,自己定义
*/
class Zoo<sss>{
//方法中的泛型标识符应该与类的标识符一样
public void doome(sss a){
System.**out**.println("泛型测试"+a);
}
}
Connection集合中的contains方法底层调用的是Object的equals方法,比较的是内容,所以即使集合中没有存放需要比较的对象,只要常量相同,contains就会返回true,若比较的对象没有重写equals方法,则使用“==”,比较的是内存地址
放在集合里的元素需要重写equals方法,若不重写,contains比较的是对象的内存地址;若重写,比较的是内容
public static void test(){
Collection cc = new ArrayList();
String s1 = new String("jack");
String s2 = new String("jack");
cc.add(s1);
/*
contains底层调用的是equals方法,而String类重写了equls方法,所以比较的是内容
s1和s2的字符串内容一样,所以JVM认为集合cc有s2
*/
System.**out**.println(cc.contains(s2)); //true
}
public static void test01(){
Collection cc = new ArrayList();
Student s1 = new Student("jack");
Student s2 = new Student("jack");
cc.add(s1);
/*
contains调用了equals方法,而类student没有重写此方法,所以调用了Object的equals方法,比较的是内存地址
*/
System.**out**.println(cc.contains(s2)); //false
}
Connection中的remove方法底层调用的也是Object的equals方法
public static void test(){
Collection cc = new ArrayList();
String s1 = new String("hello");
String s2 = new String("hello");
cc.add(s1);
/*
remove底层调用了equals方法,String重写了equals方法,因此
比较的是内容,JVM认为s1和s2是一样的,删除s2就是删除了s1
*/
cc.remove(s2);
System.**out**.println(cc.size()); //0
}
public static void test01(){
Collection cc = new ArrayList();
Student s1 = new Student("jack");
Student s2 = new Student("jack");
cc.add(s1);
/*
remove调用了equals方法,而类student没有重写此方法,所以调用 了Object的equals方法,比较的是内存地址
JVM认为s1与s2不一样,所以删除 s2并不会删除s1
*/
cc.remove(s2);
System.**out**.println(cc.size()); //1
}
注:size()方法是获得当前集合中元素的个数不是获取集合容量
1.默认初始化为10
2.集合底层是一个Object[]数组
3.ArrayList的扩容是增长到原容量的1.5倍, >>1,除以2、<<1,乘2,每次扩容大小:原容量 / 2
建议给定一个预估计的初始化容量,减少数组的扩容
单链表:节点是单向链表中基本的单元,每一个节点Node都有两个属性,一个属性是存储的数据,另一个数据是存储下一个节点的内存地址
链表上的元素在空间存储上内存地址不连续,所以删除元素时不会有大量元素位移,因此随机增删效率高,开发中,遇到随机删除集合元素业务较多是建议使用LinkedList
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,知道查到为止,所以LinkList集合检索/查找效率较低
1.java中的length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了length这个属性.
2.java中的length()方法是针对字符串String说的,如果想看这个字符串的长度则用到length()这个方法.
3.java中的size()方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看
默认容量是10
每次扩容到原来容量的2倍
*
将线程不安全的ArrayList转化成线程安全
使用Collections工具类即可
*/
ArrayList arrayList = new ArrayList(); //此时线程不安全
//使用工具类中的静态方法,传入ArrayList的对象
Collections.**synchronizedCollection**(arrayList);arrayList.add(10);
arrayList.add(20);
arrayList.add(30);
foreach有一个缺点:没有下标,在需要下标的循环中,不建议使用增强for循环
List有下标,Set没有下标
Map中的entrySet()方法将Map集合转换成Set集合,返回Set类型数据,即将key和value撮合在一起(key=map),成为一种数据类型Map.Entry(k,v)类型,而entrySet()方法返回Set集合属性类型的泛型为Map.Entry(k,v),最终写出来的格式为:Set
Map方法中contains方法中底层调用的都是equals进行对比,所以自定义类需要重写equals方法
/*
* 对Map的遍历,一种是使用keySet()方法获取所有的key值****
* 另一种是使用增强for循环对set进行遍历****
*/
Map<Integer,String> map = new HashMap<>();
map.put(1,"zhngsan");
map.put(2,"lisi");
map.put(3,"wangwu");
map.put(4,"zhaoliu");
//拿到所有的key值,Map中所有的key值其实就是Set值
Set<Integer> keys = map.keySet();
Iterator<Integer> it = keys.iterator();
while (it.hasNext()){
Integer ii = it.next();
String value = map.get(ii);
System.**out**.println(ii+"-------->"+value);
}
//keys是Map集合中所有的key汇总
for(Integer in: keys){
System.**out**.println(in+"--->"+map.get(in));
}
Map<Integer,String> map = new HashMap<>();
map.put(1,"zhngsan");
map.put(2,"lisi");
map.put(3,"wangwu");
map.put(4,"zhaoliu");
//第三种遍历方法,使用entrySet()
//entrySet类型就是key和value糅杂在一起
Set<Map.Entry<Integer,String>> set = map.entrySet();
Iterator<Map.Entry<Integer,String>>it = set.iterator();
while (it.hasNext()){
Map.Entry<Integer,String> node = it.next();
Integer key = node.getKey();
String value = node.getValue();
System.**out**.println(key+"--->"+value);
}
//使用加强for循环对集合进行遍历,效率最高
for(Map.Entry node:set){
System.**out**.println(node.getKey()+"-->"+node.getValue());
}
哈希表的原理:一个一维数组,数组中的每个值都是单链表
1.先将k,v封装到Node对象当中
2.底层会调用k的hashCode()方法得出hash值
3.通过哈希函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到此位置上,如果说下标对应的位置上有链表,此时会拿着k和链表上的每一个节点中的k进行equals,如果所有的equals方法返回的都是false,那么新节点将被添加到链表的末尾,如果其中一个equals返回 true,则新增加的链表中的value会替换掉被比较的value值
1.先调用k的hashCode()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上
2.如果此位置什么也没有,返回null,如果这个位置上有单向链表,那么会拿着参数上的k和单向链表上的每一个节点中的k进行equals,如果所有的equals返回的都是false,则get()方法返回null,若果有其中一个equals返回的是true,那么这个节点上的value就是我们要找的value
放在HashMap集合key部分的元素实际上放到了HashSet集合中
放在HashSet集合中的元素也需要同时重写hashCode()+equals()方法
重点:通过讲解可以得出HashMap集合的key,会先调用两个方法,一个方法是hashCode(),一个方法是equals(),那么这两个方法都需要重写
注:同一个单向链表上所有节点的哈希值相同,因为它们的数组下标是一样的,但是同一个链表上k与k的equals方法肯定返回的是false,都不相等
若哈希值全都一样,则是单链表;哈希值全都不一样,就是数组
HashMap集合默认容量为16,默认加载因子为0.75
重点:HashMap集合初始化容量必须是2的倍数,也是官方推荐,这是因为达到散列均匀,为了提高HashMap集合的存取效率这是必须的
向Map集合中存取,都是先调用key的hashCode方法,在调用equals方法
equals方法可能调用,也可能不掉用:
(1)put(k,v)不调用情况:k.hashCode()方法返回哈希值,哈希值通过哈希算法转换为数组下标,下标位置上如果是null,equals不需要执行
(2)get(k)不调用情况:k.hashCode()方法返回哈希值,哈希值通过哈希算法转换为数组下标,下标位置上如果是null,equals不需要执行
如果一个类的equals方法重写了,那么hashCode()方法必须重写,并且equals方法返回如果是true,hashCode()方法返回的值也必须一样
解释:equals相等,说明在同一个单向链表上比较,在同一个单链表上的节点来说,哈希数值相等,所以hashCode()方法返回值也应该相同
放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法
对于哈希表数据结构来说:
1.如果o1和o2的hash值相同,一定是放到同一个单向链表上
2.如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”
HashMap集合中key部分允许null,但null值只能有一个
HashTable集合的key和value都不能为null
HashMap集合的key和value都可以为null
Hashtable方法为线程安全的,效率较低线程安全有其他方案,所以导致此使用较少
List、Set集合添加元素使用add()方法,Map集合添加元素使用put()方法
向其中存放数据,无序不可重复,但是在存放数据是会进行大小比较排序;自定义类无法进行比较,因为源码显示比较时会进行强制转换成Comparable, 从而报错(也就是没有实现Comparable接口,强制类转换就会报错),实现接口就可以进行排序
TreeSet/TreeMap是自平衡二叉树,遵循左小右大的原则存放;
TreeSet/TreeMap遍历Iterator迭代器遍历采用中序遍历方式
1.自定义类实现接口
2.使用比较器:自定义类的比较器(本质还是重写写一个规则,源码上执行的是执行器不是空的分支),构造方法参数里传入自定义比较器对象
最终结论:
放到TreeSet或者TreeMap集合key部分的元素要做到排序包括两种方式:
1.放在集合中的元素实现java.Lang.Comparable接口
2.在构造TreeSet或者TreeMap集合的时候传给一个比较器对象、
当比较规则不会发生改变的时候,或者说当比较规则只有一个的时候,建议实现Comparable接口;如果比较规则有多个,并且需要多个比较规则之间频 繁切换,建议使用Comparator接口
class PerSonComparator implements Comparator<PerSon>{
//自定义类的比较器
public int compare(PerSon o1, PerSon o2) {
return o1.getAge()-o2.getAge();
}
}
//构造TreeSet集合的时候传入比较器对象
TreeSet<PerSon> treeSet = new TreeSet<>(new PerSonComparator());
Collections工具类中的sort()排序方法要求需要排序的List集合中的元素必须实现Comparable接口、
1.Collections.sort(实现接口的List集合);
2.Collections.sort(List集合,比较器对象);
将Set集合转换为List集合:
ListmyList = new ArrayList<>(Set);//转换才可以排序
各个集合类之间的转换通过集合构造方法就可以转换,文档中显示构造方法中可以传入集合;Map不能之间转换成Set,可以调用keySet()将Map转换成Set
Set集合/Map中的key部分升序不可重复,可以使用比较器修改规则降序打印I
按照流的方向分为输入流和输出流,读取数据不同分为字符流和字节流;字符流是任何数据都可以读取(万金油),字符流只能读取普通文本(例如txt)
在操作系统文化中,一个“a”字符占用一个字节;在java中占用两个字节
java.io.InputStream 字节输入流
java.io.OutputStream 字节输入流
java.io.Reader 字符输入流
java.io.Writer 字符输入流
四大流都是抽象类(abstract class)
所有的流都实现了:java.io.Closeable接口,都是可关闭的
流是一个管道,是内存和硬盘之间的通道,用完以后要关闭,不然会占用很多资源
所有的输出流都实现了java.io.Flushable接口,都是可以刷新的,都有flush()方法,输出流在最终输出后,一定要flush()刷新一下,这个刷新表示将同道/管道中剩余的未输出的数据强行输出完(清空管道),刷新的作用就是清空管道
文件专属
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileReader
java.io.FileWriter
转换流(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
缓冲流专属
java.io.BufferedReader
java.io.BufferedW riter
java.io.BufferedInputStream
java.io.BufferedOutputStream
数据流专属
java.io.DataInputStream
java.io.DataOutputStream
标准输出流
java.io.PrintWriter
java.io.PrintStream
对象专属流
java.io.ObjetInputStream
java.io.ObjectOutputStream
read()方法返回的是读取的字节本身内容
read(byte[])方法返回的是读取的字节的个数(不是字节本身与内容)
当一个构造方法中需要一个流的时候,被传进来的流成为节点流
外部负责包装的流叫包装流或者处理流
BufferedReader/BufferedWrite构造方法只能传入字符流不能传入字节流,如果需要就可以使用转换流InputStreamReader/OutputStreamWriter进行转换
专属数据流:将数据和数据类型一并写入到文件中,此文件不是普通文件,文本编辑器打不开
DataInputStream:数据字节输入流,DataOutputStream写的文件,只能使用DataInputStream去读取,并且读取的时候需要提前知道写入的顺序,读的顺序和写的顺序一致才可以正常取出数据
//设置打印的文本路径
PrintStream printStream = new PrintStream(new FileOutputStream("First.txt",true));//此处记得为追加而不是重写
//打印输出重定向
System.**setOut**(printStream);
//获取当前时间
Date date = new Date();
//定义时间格式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
//返回时间字符串
String dd = simpleDateFormat.format(date);
//打印
System.**out**.println(dd+"--->>事件:"+msg);
序列化:Serialize(拆分),java对象存储到文件中,将java对象的状态保存下来的过程
反序列化:DeSerialize(重组),将硬盘上的数据重新恢复到内存当中,恢复成java对象
序列化的对象必须实现Serialize接口,此接口中没有任何方法只是一个标志接口,起到了标志的作用,JVM虚拟机看到了这个类实现了这个接口,就会对 这个类进行特殊照顾(生成的文件不可读)
//序列化操作
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("Customer"));
os.writeObject(cc);//序列化具体代码
//反序列化
ObjectInputStream is = new ObjectInputStream(new FileInputStream("Customer"));
Object objs = is.readObject();//反序列化具体操作
System.**out**.println(objs);
以上例子可以知之,普通的读取流不可以读取序列化文件,只能使用专用的流进行反序列化,例如DataInputStream流可以读取DataOutPutStream写的文件
一次性可以实例化多个对象:将序列化的对象放在集合当中即可
注:参与序列化的集合以及里面的元素都需要实现特定接口
//对多个对象进行序列化操作
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("CCustomer"));
/*
没有强制转换:返回的是Object类型集合,强制转换为List集合
再对集合进行遍历
Object object = objectInputStream.readObject();
*/
List<Customer> list = (List<Customer>)objectInputStream.readObject();
//对集合进行遍历,对数组进行反序列化
for (Customer customer:list){
System.**out**.println(customer);
}
注:如果对序列化中的某个属性不想对其反序列化,可以再属性名前面加上关键字:transient(表示游离的,不参与序列化)
类实现了Serialize接口,JVM虚拟机见到关键字就会为此类生成一个序列化编号(即使没有手动进行ObjectOutputStream操作,默认提供)
java虚拟机判断两个类是否相同:
1.先判断类名是否相同,不同则为不同的类
2.若类名相同,则判断类的序列化版本号是否相同,序列化版本号相同则为同一个类,序列化号不同则不为同一个类
缺点:
类中的代码后续无法修改,一旦修改,序列化版本号自动更改,再次对其反序列化运行报错,还需手动重新序列化一次才可以
建议:
凡是一个类实现了Serializable接口,建议为该类提供一个固定不变的序列化版本号,这样即使这个类后续代码修改,但是版本号不会改变,java虚拟机仍认为这是同一个类
非常好的设计理念:
以后经常改变的数据,可以单独写到一个文件当中,使用程序动态读取,将来只需要修改文件的内容,java代码不需要改动,不需要重新编译,服务器 也不用重启即可拿到动态信息;
类似以上机制的这种文件成为配置文件
并且当配置文件中的内容格式是:key1=value;key2=value的时候,我们称之为属性配置文件
java规范中有要求:属性配置文件建议“.properties”结尾,但不是必须这种以“.properties”结尾的文件在java中被称为:属性配置文件
其中properties是专门存放属性配置文件内容的一个类
文件中注释为:“#”开头
//创建流
FileReader is = new FileReader("Temp.properties");
//创建集合Properties
Properties pp = new Properties()
//load()方法将文件中的数据加载到Map集合中
pp.load(is);
//获取文件中的数据
String msg1 = pp.getProperty("1");
System.**out**.println(msg1);
String msg2 = pp.getProperty("2");
System.**out**.println(msg2);
String msg3 = pp.getProperty("3");
System.**out**.println(msg3);
进程的三大特性:原子性、可见性、有序性
进程就是一个程序,线程就是程序的最小单元块
进程之间内存资源不共享;线程之间内存(方法区和堆内存)资源共享,栈内存不共享;一个线程一个栈,各个栈之间互不干扰
串行、并发与并行:
并发可以提高事务的处理效率,即一段时间内可以处理或者完成更多的事情
并行是一种更为严格、更为理想的并发
串行就是事务排队一个一个执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DGTHwYgo-1602207826295)(img\Thread.png)]
1.直接继承Thred类,创建线程对象,调用线程对象的start()方法
public class ThreadTest01 {
public static void main(String[] args) {
MyThread m1 = new MyThread();
m1.start();
for (int i = 0; i <1000 ; i++) {
System.out.println("主线程-->"+i);
}
}
}
class MyThread extends Thread{
public void run() {
for (int i = 0; i <1000 ; i++) {
System.out.println("分支线程-->"+i);
}
}
}
2.实现Runna接口,实现run方法(线程构造方法中传一个封装成线程对象的可运行对象)
public class ThreadTest02 {
public static void main(String[] args) {
Thread t = new Thread(new Mythread());
t.start();
for (int i = 0; i <1000 ; i++) {
System.out.println("主线程-->"+i);
}
}
}
class Mythread implements Runnable{
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
System.out.println("分支线程-->"+i);
}
}
}
3.未来任务类:实现Callable接口(这种线程方式有返回值,前两种为void)
缺点:效率较低
优点:可以拿到另一个线程的返回值
就绪状态:叫做可运行状态,表示当前线程具有抢占CPU时间片的权利(CPU时间片就是执行权);当一个线程抢夺到CPU时间片之后,就开始执行run方法,run方法开始执行标志线程进入运行状态
运行状态:run方法执行后进入此状态,当之前占有的CPU时间片用完以后,会重新进入就绪状态继续抢夺CPU时间片,当再次抢到时间片以后会接着上一次的代码继续向下执行
阻塞状态:当一个线程遇到阻塞时间,例如接受用户键盘输入,或者sleep方法,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片;阻塞状态结束后不会继续开始运行状态,而是要到就绪状态继续抢占CPU时间片,若抢占成功会继续运行上一次run方法的代码而不会从头开始
线程会在就绪状态和运行状态之间疯狂横跳,这两种状态切换受到JVM的调度
线程的生命周期可以通过getState()方法获得,线程状态是Thread.State枚举类型定义的
new 新建状态 创建了线程对象,在调用start()启动之前的状态
runnable 可运行状态,它是一个复合状态,包含了:ready和running两个状态。ready状态该线程可以被线程调度器进行调度使它处于running状态,running状态表明该线程正在运行,yeild方法可以使线程从running状态变为ready状态
waiting等待状态,执行object.notify()或者加入的线程执行完毕,当前线程会转换为runnable状态;Timed_waiting状态与waiting状态类似都是等待状态,区别在于处于该状态的线程不会无线等待,如果线程在指定时间内没有完成期望操作,该线程自动转换为runnable
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tDwfkiRL-1602207826297)(img/线程状态图2.PNG)]
哪个线程的优先级比较高,抢占到的CPU时间片的概率高一些、多一些,java采用的就是抢占式调度模型
平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样
平均分配,一切平等
设置线程的优先级
int getPriority()获取线程优先级,最低优先级1,默认优先级5,最高优先级10
优先级高获得CPU时间片的概率大一些(处于运行状态的时间多一些),不是绝对获得时间片;不正当的设置优先级会导致某些线程无法获得CPU时间片而出现线程饥饿问题;线程的优先级有继承性,在A线程创建了B线程,则二者优先级相同,开发基本不设置使用默认值15
合并线程,当前线程进入阻塞状态,test.join()中test的线程执行完毕后当前线程继续执行,相当于多线程变为了单线程
public class ThreadTest05 {
public static void main(String[] args) {
Thread tt =new Thread(new MyThread03());
// System.out.println(Thread.currentThread().getPriority());
tt.setName("tt");
// tt.setPriority(10);
tt.start();
for (int i = 0; i <10000; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
class MyThread03 implements Runnable{
@Override
public void run() {
// System.out.println(Thread.currentThread().getPriority());
for (int i = 0; i <100 ; i++) {
if(i!=0&&i%5==0){
Thread.yield();
System.out.println("分支线程让位一次");
}
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
//运行结果为:main线程开始,tt线程调用join方法后,tt线程结束main线程继续开始运行
run()方法的异常只可以tyr-catch,不能抛出(子类不能抛出比父类更多的异常);
线程的唤醒使用Interrupt方法(非静态方法),这种方法是使用了java异常处理机制的方法,使休眠的线程出现异常然后被捕捉到,结束try-catch语句而继续向下执行
注意:
- start方法调用结束并不意味着子线程块开始运行
- 新开启的线程会执行run()方法
- 如果开启了多线程,start()方法调用顺序并不一定是线程启动的顺序
- 多线程运行结果与代码执行顺序或调用顺序无关
线程的终止不建议使用stop()方法,会丢失未保存的数据类似突然断电,合理的方法是打上布尔标记,return结束run方法
//子线程休眠一年,主线程运行for循环五次后,子线程对象调用方法唤醒子线程,子线程结束(以异常的情况唤醒)
public class ThreadTest03 {
public static void main(String[] args) {
Thread tt = new Thread(new MyThread01());
//重命名线程名称
tt.setName("tt");
//启动分支线程
tt.start();
//主线程运行
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+"主线程");
}
//倒计时5秒钟
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒分支线程
tt.interrupt();
}
}
class MyThread01 implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName()+"---->begin");
//休眠分支线程,休眠时间为一年
try {
Thread.sleep(1000*60*60*24*365);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---->over");
}
}
判断当前线程是否处于活动状态;而活动状态就是线程已经启动且未终止
public class ThreadTestisAlive extends Thread{
public static void main(String[] args) {
ThreadTestisAlive testisAlive = new ThreadTestisAlive();
System.out.println("begin:"+testisAlive.isAlive());//false
testisAlive.start();
System.out.println("over:"+testisAlive.isAlive());//结果不一定为false
}
public void run() {
System.out.println("run:"+this.isAlive());//true
}
}
可以获得线程的唯一标识:Thread.currentThread.getId() -->实例方法
注:某个线程编号运行结束后,改变好可能会被后续创建的线程使用;重启JVM之后,同一个线程的编号可能不一样
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法;**让当前线程让位,**让给其他线程使用
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”
有可能再次抢到,概率较低了
sleep方法是静态方法,与对象无关;
sleep方法在哪个线程当中,哪个线程就会休眠(虽然可以使用引用来调用sleep方法,但是实际上运行的时候会准换成“包名.sleep()”方式运行)
public class TestSimpleTimer{
public static void main(String[] args) {
int remaining = 10;//从10秒开始计时
while (true){
System.out.println("remaining:"+remaining);
remaining--;
if (remaining<0){
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
此方法可以获得当前线程;
Java中的任何一段代码都是执行在某个线程当中,执行当前代码的线程就是当前线程;同一段代码可能被不同的线程执行,因此当前线程是相对的
Thread.currentThread()方法的返回值是代码实际运行时候的线程对象
public class ThreadCurrentTest {
public static void main(String[] args) {
Thread thread = new Thread(new TestThreadCurrent());
System.out.println("main:"+Thread.currentThread().getName());
thread.start();
}
}
class TestThreadCurrent implements Runnable{
public TestThreadCurrent() {
System.out.println("构造方法:"+Thread.currentThread().getName());
}
@Override
public void run() {
System.out.println("run方法:"+Thread.currentThread().getName());
}
}
设置、获得线程的名字,通过对线程名字的设置,有助于程序的调试,提高程序的可读性,建议为每一个线程提供一个可以提现线程功能的名字
数据在多线程并发的情况下发生安全问题的三个条件:
1.多线程并发
2.有共享数据
3.共享数据有修改行为
解决方法:
- 线程排队执行
- 用排队执行解决线程安全问题,这种机制称为:线程同步机制
- 专业术语:线程同步,实际上就是线程不能并发执行了,线程必须排队
- 线程排队会牺牲一些效率,不可避免数据安全第一位,在数据安全的前提下考虑效率的高低
线程1与线程2,各自执行各自,谁也不需要等待谁
其实就是:多线程并发(效率较高)
异步并发
线程1与线程2,在线程1执行的时候,必须等待线程2执行结束,相反亦是,两个线程之间发生了等待关系
效率较低,线程排队执行
同步排队
程序运行时遇到synchronized关键字后,线程就会进入锁池,在锁池中寻找共享对象的对象锁时,会释放掉之前占有的CPU时间片,有可能找到,有可能没有找到;没有找到就在锁池中等待,如果找到了就会进入就绪状态继续抢夺CPU时间片
锁池并不是一种状态(可以理解为一种阻塞状态)
Java中有三大变量:
1.局部变量 2.实例变量 3.静态变量
局部变量不会出现线程安全问题,实例变量和静态变量存放的地方只有一个,有可能会出现线程安全问题
synchronized关键字放在实例方法上,锁的对象一定是this,缺点是不灵活,并且表示整个方法体都需要同步,无故扩大同步范围,导致程序运行效率降 低,所以此方法不经常使用
优点就是代码少,节俭,如果共享的对象是this,并且整个方法体都需要同步可以锁定代码块
如果使用的是局部变量的话,建议使用StringBuilder,因为局部变量不存在线程安全;StringBuffer效率较低
synchronized有三种写法
1.同步代码块(灵活)
2.实例方法上使用
表示共享对象一定是this
并且同步代码块是整个方法体
3.在静态类方法上使用synchronized
表示类锁
类锁永远只有1把
对象锁:1个对象1把锁,100个对象100把锁
类锁:100个对象,也可能只是1把锁
1.不能上来就使用synchronized,会让程序的执行效率不高,用户体验差,系统的用户吞吐量降低(吞吐量就是并发),不得已情况下适用
2.尽量使用局部变量代替“实例变量”和“静态变量”
3.如果必须使用实例变量,可以考略创建多个对象,这样实例变量的内存就不会共享
4.不能使用局部变量,对象也不能创建多个,就只能选择线程同步机制
java语言中线程分为两大类:
1.用户线程
2.守护线程(后台线程)
其中具有代表性的就是:垃圾回收机制(守护线程)
守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束
Thread daemonTread = new Thread();
// 设定 daemonThread 为 守护线程,default false(非守护线程)
daemonThread.setDaemon(true);
注意:主线程main方法是一个用户线程
守护线程使用的地方:
每天00:00的时候系统数据自动备份
这个需要使用定时器,并且我们可以将定时器设置为守护线程
一直在那里看着,没到00:00的时候就备份一次;所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份
线程启动start()之前设置为守护线程,不然会报错的
优势:
风险:
作用: 通过java语言中的反射机制可以操作字节码文件
通过反射机制可以操作代码片段
反射机制相关的类包:java.lang.reflect.*
反射机制相关的重要的类:
java.lang.Class :代表整个字节码,代表一个类型,代表整个类
java.lang.reflect.Method :代表字节码中的方法字节码;代表类中的方法
java.lang.reflect.Constructor :代表字节码中的构造方法字节码;代表类中的构造方法
java.lang.reflect.Field :代表字节码中的属性字节码;代表类中的成员变量(静态变量)
java中每一个队象都有getClass方法;字节码文件装载到JVM中的时候只有一份
要操作一个类的字节码,首先要获取到这个类的字节码
1.forname();方法参数传入字符串(完整的类名+包名)
2.getClass:所有的对象都有此方法
3.java语言中任何一种类型,包括基本数据类型,都有 .class属性
//返回的Class类型的值赋予c1,c1代表的是User的对象
Class c1 =Class.**forName**("reflect.Bean.User");
//newInstance方法是调用了类User的无参构造方法,创建了User对象
Object o = c1.newInstance();
//创建流
FileInputStream is = new FileInputStream("reflect/src/reflect/Bean/Reflect.properties");
//创建Map集合
Properties pro = new Properties();
//将流加载到集合当中
pro.load(is);
//通过Properties结合中的getProperty方法获得value值(值为完整类名+包名)
String str = pro.getProperty("className");
//通过forName方法获得字节码文件,返回的是一个Class类
Class cc= Class.**forName**(str);
//cc代表的是配置文件中的类,调用newInstance方法创建当前类的对象
Object o =cc.newInstance();
System.**out**.println(o);
重点:
如果只是希望静态代码块执行,其他代码一律不执行,可以使用forName方法
凡是放在src下的都是类路径下
/*
获取绝对路径的方法(比较通用)
获取在类文件下的相对路径,src目录不用写,java文件后缀名为 .class
*/
String path = Thread.**currentThread**().getContextClassLoader().getResource("reflect/ReflectTest01.class").getPath();
//可以和IO流+Properties配合使用
/*
getResourceAsStream直接返回的是InputStream流
配置文件不能有后缀名,且不能直接写文件名,还需要src下面的相对路径
*/
InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("reflect/Bean/Reflect.properties");
Properties pro = new Properties();
pro.load(reader);
reader.close();
String str =pro.getProperty("className");
System.**out**.println(str);
以上两个方法是获得相对路径打印绝对路径(前提为必须在类路径下)
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容,使用此方式,属性配置文件必须放到类路径下
资源绑定器,只能绑定文件扩展名为propreties的配置文件,且在写路径的时候,路径后面的扩展名不能写
//getBundle方法,填写路径下src类路径下的相对路径,且不能加上后缀名
ResourceBundle resourceBundle = ResourceBundle.getBundle("reflect/Bean/Reflect");
/*
通过getString方法获得配置文件的key值从而获得value值
此方法比IO+Properties简化一些
*/
String str = resourceBundle.getString("className");
System.**out**.println(str);
cc.getFields();//获得这个类中public权限的属性
cc.getDeclaredFields()//获得这个类中所有属性
//获得变量的修饰符(例如String、int、boolean)
Class fieldtype =field.getType();//返回的是Class类型
System.out.println(fieldtype.getSimpleName());
//返回的是Class类型,所以在使用Class的getName属性
String string = fieldtype.getName();
//获得变量(属性)的名字(也就是自己起的名字)
field.getName()
//获得属性修饰符(例如:public、private、default、proteeted)
int i = field.getModifiers();//返回的是修饰符的编号
System.out.println(i);
//将修饰编号转换为修饰符名称,获得名字没有前缀
String srt = Modifier.**toString**(i);
System.**out**.println(srt);
总结:
1.获得类中变量的名称:getName()方法
2.获得变量:getFields()–>只能获取public权限
getDeclaredFields()–>获取所有的变量
3.获得变量的数据类型:getType()返回的是Class类型数据
Class的getName()获得数据类型
getType()后可以或得简化名字
4.获得变量的权限:getModifiers()可能有三四个修饰符修饰,并返回数字
Modifier.toString()传入编号获得修饰符
//获得类
Class cc = Class.**forName**("reflect.beann.Student");
//创建类的对象
Object o = cc.newInstance();
//传入变量名,获取变量(私有属性无法获得)
Field field = cc.getDeclaredField("name");
//为o对象的field(指代变量名name)的变量赋予值
field.set(o,"航三");
//获得o对象field变量的值
Object o1 = field.get(o);
//打印
System.**out**.println(o1);
field.setAccessible(true);//打破封装,即可访问
总结:访问属性(修改属性)
1.获得类并创建对象
2.获得属性(获得一个属性,而不是多个属性)
getDeclaredField()方法(所有权限的属性都可以获得)
3.修改属性(参数传入对象引用和赋予的值)
获得的变量名.set(引用,value);
如果是私有属性需要先打破封装在赋值
setAccessible(true);
4.获得属性值并打印
get(引用)
可变长度参数:
语法为:类型…(注:一定是3个点)
1.可变长度参数要求的参数个数为:0-N个
2.可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个
3.可变程度参数可以当做数组
Class cc = Class.**forName**("reflect.MyClss");
//获得所有权限的方法
Method[] methods = cc.getDeclaredMethods();
for (Method mm:methods){
//获得返回值
System.**out**.println(mm.getReturnType().getSimpleName());
//获得方法修饰符
System.**out**.println(Modifier.**toString**(mm.getModifiers()));
//获得方法名称
System.**out**.println(mm.getName());
//获得形参(参数列表可能有多个,所以获得的是数组)
Parameter[] pp = mm.getParameters();
for (Parameter parameter:pp){
//参数起作用的是类型,所以需要获得参数的类型
System.**out**.println(parameter.getType().getSimpleName());
}
}
获得方法总结:
1.获得所有的方法
getDeclaredMethods()
2.获得返回值
mm.getReturnType().getSimpleName()
3.获得方法修饰符
Modifier.toString(mm.getModifiers())
4.获得参数列表
getParameters()
因为参数可能有多个,所有返回的的参数数组,再对数组遍历
getType().getSimpleName()//获得参数的类型
5.调用方法
invok(),调用方法,传入对象+“参数列表”
//获取类
Class cc = Class.**forName**("reflect.MyClss");
//创建对象
Object oo = cc.newInstance();
/*
获得方法,区别方法的特征为参数个数与类型、方法的名字
getDeclaredMethod方法的参数,第一个参数为方法的名字
第二个参数~n个参数为:要获得的方法参数类型.class
*/
Method mm = cc.getDeclaredMethod("login",int.class,String.class);
/*
invoke调用方法
调用方法四要素:
1.方法名(mm已经获得方法名)
2.对象(o为获取类创建的对象)
3.参数
4.返回值
*/
mm.invoke(oo,123,"aaa");
//获得类
Class cc = Class.**forName**("reflect.Bean.User");
//获得构造方法(传入参数类型决定获得的构造方法)
Constructor c1 =cc.getDeclaredConstructor(String.class);
//获得构造方法,创建对象插入指定参数
Object object = c1.newInstance("zhangsan");
System.**out**.println(object);
Class cc = Class.**forName**("java.lang.String");
//获得父类
System.**out**.println(cc.getSuperclass().getSimpleName());
/*
获得接口,接口有多个,所以返回的是数组
*/
Class []classes = cc.getInterfaces();
for (Class ccc :classes){
System.**out**.println(ccc.getSimpleName());
}
1.注解又叫注释,英文单词:Annotation
2.注解是一种引用数据类型,编译生成后为 .class文件
3.语法格式
[修饰符列表] @interface 注解类型名{
}
注解可以注解注解,注解可以出现任何位置
掌握:
Deprecated用@Deprecated注释的程序元素,不鼓励使用这样的元素,通常是因为它很危险或者存在更好的选择
掌握:(只能注解方法例如toString)
Override表示一个方法声明打算重写超类中的另一个方法声明
此注解是给编译器参考的,和运行阶段没有关系,java中凡是带有这个注解,编译器都会进行编译检查,如果这个方法不是重写父类的方法(例如重写父类方法却写错了),编译器报错(应该写在子类重写的方法上)
不用掌握:
SuppressWarnings指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消指定的编译器警告
用来标注“注解类型”的“注解”,称之为元注解
常见的元注解:
Target Retention
Target元注解:属性名为value,属性值为ElementType数组,而ElementType为枚举类型,使用时可以用 枚举名.属性值,此元注解只为规定被注解的注 解可以出现的位置
Retention元注解:属性名为value,属性值为RetentionPolicy枚举类型,使用时可以用 枚举名.属性值,此元注解只为规定被注解的注解是否可以被反射 机制获得
注解中的属性格式为:String name();
使用此注解必须为属性赋值,否则报错;多个属性之间用逗号
默认属性值格式:String name() default=””;
使用此注解可以不用为属性赋值,也可以选择赋值
**属性名为value且只有一个属性时,可以省略value直接“”写入值**
注解当中属性的类型:
byte short int long float double boolean char String Class 枚举类型以及以上每一种的数组形式
@Id
@Target(ElementType.**TYPE**)//要求该注解只能注解类
@Retention(RetentionPolicy.**RUNTIME**)//表示该注解可以被反射
public @interface Id {
}
User类
public class User {
int grade;
String name;
int id;
boolean sex;
}
TestException
public class TestException extends Exception{
public TestException() {
}
public TestException(String message) {
super(message);
}
}
Test
//获得类
Class cc = Class.**forName**("JavaAnnotation.User");
boolean flag = false;
//判断是否有名为 Id 的注解
if(cc.isAnnotationPresent(Id.class)){
//运行到这里说明该类被Id注解
//遍历属性数组
Field[] ff =cc.getDeclaredFields()***\*;\*
for (Field field:ff){
//判断属性中是否以 id 为名字
if("id".equals(field.getName())&&"int".equals(field.getType().getSimpleName())){
//运行到这里,说明该类被Id注解,并且含有 int id 属性
flag =true;
break;
}
}
if(!flag){
throw new TestException("被@Id注解的类中必须有一个int类型的id属性");
}
}
else{
throw new TestException("类必须被@Id注解");
}
外部类创建内部类对象
Outer.Inner inner =new Inner();
外部类调用内部类属性与方法
System.out.println(inner.name);//Inner,内部类私有属性
inner.testInner();
内部类访问外部类属性与方法
1.第一种方法:通过引用
System.out.println(outer.name);//Outer,外部类私有属性
outer.testOuter();
2.第二种方法
System.out.println(Outer.this.name);
Outer.this.testOuter();
外部类、内部类调用本类的属性与方法:直接写出名字即可且内部类可以访问外部类所有的属性与方法包括私有
总结:
外部类调用内部类属性和方法使用内部引用.属性、内部引用.方法访问
外部类无法随心所欲访问内部类属性与方法必须使用引用
静态内部类可以通过类名的方式访问外部类的静态属性和方法,而不能直接访问外部类的非静态属性和方法
System.out.println(Outer.sex);
Outer.test02();
静态内部类的对象创建可以不依赖外部类而直接创建
Inner inner = new Inner();
外部类可以通过静态内部类类名调用内部类的静态属性与方法,而不能直接访问非静态属性和方法
System.out.println(Inner.sex);
Inner.test02();
静态内部类调用外部类、外部类调用静态内部类 -->非静态的属性和方法都可以通过引用来访问
System.out.println(inner.name);//Inner
System.out.println(inner.age);//17
--------------------------------------
System.out.println(outer.name);//Outer
System.out.println(outer.age);//18
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调