Java基础+面试:
1.JDK、JRE、JVM关系
JDK包含JRE JRE包含DVM
JDK=JRE+JAVA的开发工具(javac.exe、java.exe、javadoc.exe)
JRE=JVM+Java核心类库
2.如何配置path环境变量
JAVA_HOME=bin的上级目录
path=%JAVA_HOME%\bin
3.常见的几个命令行操作
cd md(创建文件) rd(删除目录) del(删除文件或目录) rd/s=del cd…
4.Java文件有几个类?
一个.java源文件中可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致。
一个文件中可以只有非public类。如果只有一个类,并且是非public的,此类可以跟文件名不同。
5.Java数据类型
- 基本数据类型:
byte(1个字节) short(2个字节) int(4个字节) long(8个字节,必须以L或l结尾)
char(1个字符 2个字节)
float(4个字节,必须以f或F结尾) double(8个字节)
boolean(不确定)
- 引用数据类型:
类(String Date Void Integer Long Boolean Byte Character Double Float Short )、接口(List Map<K,V> )、数组
引用类型在堆里,基本类型在栈里。
栈空间小且连续,往往会被放在缓存。引用类型cache miss率高且要多一次解引用。
对象还要再多储存一个对象头,对基本数据类型来说空间浪费率太高
6.基本数据类型变量自动转换规则
float
表示数值的范围比long还大 long → float 无须强制转换
byte、short、char->int ->long ->float->double
7.基本数据类型之间强制类型转换的使用规则和强转可能出现的问题
容量大->容量小 使用强转符 :()
问题:损失精度。
8.&与&&的异同
同:都表示且的关系,只有两边都为true,才为true,否则为false
异:左边false时,&&不判断右边,&判断右边
9.switch后面使用的表达式可以是哪些数据类型的
byte、short、char、int、枚举数据类型、String类型
10.说明break和continue使用上的相同点和不同点
break: switch-case和循环结构(结束当前循环),其后不可以声明执行语句。
continue:循环结构(结束当次循环),其后不可以声明执行语句。
11.一维数组 二维数组
一维数组初始化:
动态初始化:int[] arr =new int[5];
静态初始化:String[] arr1=new String[]{“tom”,“jerry”};
数组一旦初始化,其长度就是确定的。arr.length
数组长度一旦确定,就不可修改。
二维数组初始化:
动态初始化:int[] [] arr=new int[4] [3];
int[] [] arr1=new int[4] [];
静态初始化:int[] [] arr2=new int [] []{{1,2,3},{4,5},{6,7,8}};
12.不同类型的一维数组元素的默认值是多少
整数:0
浮点数:0.0
char:0
boolean:false
引用类型:null
13.快速排序时间复杂度O(nlogn) 冒泡排序时间复杂度O(n^2) 堆排序 归并排序
14.面向对象思想编程内容的三条主线分别是什么
①类及类的成员:属性、方法、构造器;代码块、内部类
②面向对象的三大特征:封装、继承、多态
③其他关键字:this,super,abstract,interface,static,final,package,import
15.什么是方法的重载
两同一不同:同一个类、相同方法名;参数列表不同。
16.值传递机制
如果参数是基本数据类型,此时实参赋给形参的时实参真实存储的数据值
如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值。
17.this关键字可以用来调用哪些结构
属性、方法、构造器
this理解为当前对象,当前正在创建的对象
18.一个类只有一个直接父类,一个父类可以有多个子类,子类能获取直接父类的结构,子类能获取父类中private权限的属性或方法。
19.方法重写(overrride/overwrite)规则?
-
参数列表必须完全与被重写方法的相同;
-
返回类型①父类的返回值类型是void,则子类重写的返回值类型只能是void
②父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A的子类
③父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的。
④子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。
-
访问权限不能比父类中被重写的方法的访问权限更低。 例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
-
父类的成员方法只能被它的子类重写。
-
声明为final的方法不能被重写。
-
声明为static的方法不能被重写,但是能够被再次声明。
-
子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
-
子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
20.super 和 this的异同
- super(参数):调用基类中的某一个构造函数(应该为构造函数中的第一条语句)
- this(参数):调用本类中另一种形成的构造函数(应该为构造函数中的第一条语句)
- super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参) this:它代表当前对象名(在程序中易产生二义性之处,应使用 this 来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用 this 来指明成员变量名)
- 调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用 super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。
- super() 和 this() 类似,区别是,super() 从子类中调用父类的构造方法,this() 在同一类内调用其它方法。
- super() 和 this() 均需放在构造方法内第一行。
- 尽管可以用this调用一个构造器,但却不能调用两个。
- this 和 super 不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有 super 语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
- this() 和 super() 都指的是对象,所以,均不可以在 static 环境中使用。包括:static 变量,static 方法,static 语句块。
- 从本质上讲,this 是一个指向本对象的指针, 然而 super 是一个 Java 关键字。
21. ==和equals的区别
== :运算符
-
可以使用在基本数据类型变量和引用数据类型变量中
-
如果比较的是基本数据类型变量:
比较两个变量保存的数据是否相等。(不一定类型要相同)
-
如果比较的是引用数据类型变量:
比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写.
重写的原则:比较两个对象的实体内容是否相同.
22.包装类
基本数据类型封装起来
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
string转其他 String s=“123”; int a=Integer.parseInt(s);
其他转string String s=String.valueOf(a);
Integer i=new Integer(1);
Integer j=new Integer(1);
System.out.println(i==j);//false
Integer i=1; //和Integer.valueOf(1)效果一样
Integer j=1;
System.out.println(i==j);//true Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],保存-128~127范围的整数。目的:提高效率。
Integer i=128; //相当于new了一个对象
Integer j=128;//相当于new了一个对象
System.out.println(i==j);//false
int a=1;
Integer b=new Integer(1);
System.out.println(a==b);//true 基本型和基本型封装型进行“==”运算符的比较,基本型封装型将会自动拆箱变为基本型后再进行比较
//自动装箱和类型的自动转换不能同时进行
double d = 5.3e12; //相当于 5.3*10的12次方,科学计数法
double d = 3; //s对 (自动转换类型)
Double d = 3; //错 (自动装箱的目标必须严格对应它拆箱后的类型)
Double d = 3.0;//对 (自动装箱)
23.static关键字
static:静态的
static 可以用来修饰:属性(多个对象共享同一个)、方法、代码块、内部类
①静态变量随着类的加载而加载,通过类.静态变量 的方式进行调用。
②静态变量的加载要早于对象的创建
③由于类只会加载一次,则静态变量在内存中也只存一份
使用static修饰的方法为静态方法,通过类.静态方法 的方式进行调用。
静态方法中只能调用静态方法和静态变量(生命周期一致)。非静态方法既可以调用静态方法,也可以调用非静态方法。
静态方法内不能使用this、super关键字。
如何确定一个属性是否要声明为static的?
属性可以被多个对象所共享,不会随着对象的不同而不同。
如何确定一个方法是否要声明为static的?
操作静态属性的方法,通常设置为static。
工具类中的方法,习惯上声明为static的。比如:Math、Arrays、Collections
24.栈存放局部变量 堆存放new出来的结构(对象、数组) 方法区存放类的加载信息、静态域、常量池
25.设计模式------单例模式
只能存在一个对象实例。
构造器访问权限为 private
典型例子:Runtime
public class Singleton{
public static void main(String[] args){
Bank bank=Bank.getInstance(); //因为构造器是private,所以不能创建对象,通过对象.方法来获取对象,因此把获取对象的 方法设置为static的,static方法只能调用static属性,因此将属性设置为static
}
}
//饿汉式
class Bank{
//1.私有化构造器
private Bank(){
}
//2.内部创建类的对象
//4.要求此对象也必须声明为静态的
private static Bank instance =new Bank();
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
// 直接把static变量暴露给外部:
public class Singleton {
// 静态字段引用唯一实例:
public static final Singleton INSTANCE = new Singleton();
// private构造方法保证外部无法实例化:
private Singleton() {
}
}
//懒汉式1.0
class Bank{
//1.私有化构造器
private Bank(){
}
//2.声明当前类对象,没有初始化
//4.要求此对象也必须声明为静态的
private static Bank instance =null;
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
if(instance==null){
instance=new Bank();
}
return instance;
}
}
//懒汉式2.0
class Bank{
//1.私有化构造器
private Bank(){
}
//2.声明当前类对象,没有初始化
//4.要求此对象也必须声明为静态的
private static Bank instance =null;
//3.提供公共的静态的方法,返回类的对象
public static synchronized Bank getInstance(){
if(instance==null){
instance=new Bank();
}
return instance;
}
}
//效率稍差
public static Bank getInstance(){
synochronized(Bank.class){
if(instance==null){
instance=new Bank();
}
return instance;
}
}
//效率稍高
public static Bank getInstance(){
if(instance==null){
synochronized(Bank.class){
instance=new Bank();
}
}
return instance;
}
区分饿汉式和懒汉式
懒汉式:好处:延迟对象的创建。
坏处:懒汉式1.0写法线程不安全
饿汉式:坏处:对象加载时间(生命周期)过长。
好处:饿汉式是线程安全的
应用场景:网站计数器,应用程序的日志应用,数据库连接池,读取配置文件的类,Application,任务管理器,回收站。
26.代码块
代码块作用:用来初始化类、对象
分类:静态代码块、非静态代码块
静态代码块:随着类的加载而执行,而且只执行一次。如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行。静态代码块的执行优先于非静态代码块的执行。静态代码块内只能调用静态属性、静态方法,不能调用非静态的结构。
作用:初始化类的属性(静态属性)
非静态代码块:随着对象的创建而执行,每创建一个对象就执行一次。如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行。静态代码块内可以调用静态属性、静态方法,或非静态属性、非静态方法。非静态代码块先于构造函数执行。
作用:可以在创建对象时,对对象的属性等进行初始化。
对属性可以赋值的位置:
①默认初始化
②显示初始化
③构造器中初始化
④有了对象以后,可以通过“对象.属性”或"对象.方法"的方式,进行赋值
⑤在代码块中赋值
执行的先后顺序:①-②/⑤-③-④ ②和⑤ 按顺序
父类B静态代码块->子类A静态代码块->父类B非静态代码块->父类B构造函数->子类A非静态代码块->子类A构造函数
27.final关键字
final可以用来修饰类、方法、变量
修饰类:此类不能被其他类所继承,如:String类、System类、StringBuffer类
修饰方法:表明此方法不能被重写,如Object类中的getClass(),
修饰变量:此时的变量成为常量。
final修饰属性:可以考虑赋值的位置有:显示初始化、代码块在初始化、构造器中初始化
final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参一个实参。一旦以后,就只能在方法内使用此形参,但不能进行重新赋值。
static final 用来修饰属性:全局常量
28.抽象类和抽象方法(abstract)
abstract:抽象的
abstract可以用来修饰的结构:类、方法
abstract修饰类:抽象类
此类不能被实例化
抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作。
abstract修饰方法:抽象方法
抽象方法只有方法的声明,没有方法体
包含抽象方法的类一定是抽象类。反之,抽象类中可以没有抽象方法。
若子类重写了父类中的所有的抽象方法,此子类方可实例化
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
abstract使用上的注意点:
①abstract不能用来修饰属性、构造器等结构
②abstract不能用来修饰私有方法、静态方法(静态方法不能被重写)、final的方法、final的类
//创建匿名子类对象
//用一次 省事
//匿名内部类使用前提和条件:必须存在继承和实现关系的时候才可以使用
public abstract Person(){
public abstract void eat();
public abstract void breath();
}
Person p=new Person(){
public void eat(){
System.out.println("吃东西");
}
public void breath(){
System.out.println("好好呼吸");
}
};
method(p);
//创建匿名子类的匿名对象
method(new Person(){
public void eat(){
System.out.println("吃东西");
}
public void breath(){
System.out.pritln("好好呼吸");
}
});
关于抽象类
JDK 1.8以前,抽象类的方法默认访问权限为protected
JDK 1.8时,抽象类的方法默认访问权限变为default
关于接口
JDK 1.8以前,接口中的方法必须是public的
JDK 1.8时,接口中的方法可以是public的,也可以是default的
JDK 1.9时,接口中的方法可以是private的
29.设计模式–模板方法设计模式
例:JavaWeb的Servlet中关于doGet/doPost方法调用,Spring中JDBCTemplate、HibernateTemplate,数据库访问的封装,Junit单元测试,Hibernate中的模板程序
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
**缺点:**每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
30.接口(interface)
1.接口使用interface来定义
2.java中,接口和类是并列的两个结构
3.如何定义接口:
JDK7及以前:只能定义全局常量和抽象方法
全局常量:public static final的,书写时可以省略
抽象方法:public abstract的,书写时可以省略
JDK8除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法。
接口中定义的静态方法,只能通过接口来调用。
通过实现类的对象,可以调用接口中的默认方法。
如果实现类重写了接口中的默认方法,调用时,仍然调用的时重写以后的方法。
如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的时父类中同名同参数的方法。-----类优先原则
如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。-----接口冲突 那么要求我们必须在实现类中重写此方法。
4.接口中不能定义构造器!意味着接口不可以实例化
5.java开发中,接口通过让类去实现(implements)的方式来使用
如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
如果实现类没有覆盖接口中的所有抽象方法,则此实现类仍为一个抽象类
6.java可以实现多个接口
7.接口与接口之间可以继承,而且是多继承
31.接口的应用–设计模式–代理模式
32.接口的应用–设计模式–工厂模式
33.内部类
1.java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类
2.内部类的分类:成员内部类(静态、非静态) vs局部内部类(方法内、代码块内、构造器内)
3.成员内部类:
一方面,作为外部类的成员
可以调用外部类的结构,包括private成员和静态成员。 访问外部同名成员:外部类.this.成员变量 外部类.this.成员方法
可以被static修饰
可以被4种不同的权限修饰
另一方面,作为一个类:
类内可以定义属性、方法、构造器等
可以被final修饰,表明该类不能被继承,言外之意,不使用final,就可以被继承
可以被abstract修饰
4.关注如下的3个问题
4.1如何实例化成员内部类的对象
Person 、static Dog、 Bird
静态的成员内部类:
Person.Dog dog=new Person.dog();
dog.show();
非静态的成员内部类:
Person p=new Person();
Person.Bird bird=p.new Bird();
bird.sing();
4.2如何在成员内部类中区分调用外部类的结构
重名:
方法的形参:name
内部类的属性:this.name
外部类的属性:Person.this.name
4.3开发中局部内部类的使用
返回接口类型的对象。
局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰的。
对于局部内部类,只有在方法的局部变量被标记为final或局部变量是effctively final的,内部类才能使用它们。
匿名内部类:一般使用匿名内部类的方法来编写事件监听代码。
匿名内部类也是不能有访问修饰符和static修饰符的。
匿名内部类是唯一一种没有构造器的类。
为什么局部内部类和匿名内部类只能访问局部final变量?
原因:外部类和内部类是处于同一个级别的,内部类不会因为定义在方法中就随着方法执行完毕而被销毁。那么问题来了,如果外部类的方法中变量不定义为final,那么外部类的方法执行完毕后,这个局部变量肯定被gc了。这时候内部类的某个方法还没执行完,却找不到他引用的外部变量了。因此,Java就会将这个变量复制一份作为成员变量置于内部类中,但是如果内部类中改变变量的值会造成数据不一致行,因此必须将其限制为final变量。
静态内部类:静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或方法
外部类的访问控制符只有public和default两种。
成员内部类同成员方法和字段,访问控制符可以是public、protected、default、private四种。
局部内部类同局部变量,不能被public、protected、private、static修饰,但可以被final和abstract修饰。
34.异常处理
父类:java.lang.Throwable
java.lang.Error: 一般不编写针对性的代码进行处理
Java虚拟机无法解决的严重问题。如:JVM系统内部错误、系统资源耗尽情况。比如:StackOverflowError和OOM
**java.lang.Exception:**可以进行异常的处理
空指针,试图读取不存在的文件,网络连接中断,数组角标越界
编译时异常:(checked)
IOException
FileNotFoundException
ClassNotFoundException
运行时异常:(unchecked)
NullPointerException
ArrayIndexOutOfBoundsException
ClassCastException
NumberFormatException
InputMismatchException
ArithmeticException
异常的处理:抓抛模型
过程一:”抛“:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。并将此对象抛出。一旦抛出对象后,其后的代码就不再执行。
关于异常对象的产生:①系统自动生成的异常对象
②手动的生成一个异常对象,并抛出(throw)
过程二:”抓“:可以理解为异常的处理方式:
①try-catch-finally
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}catch(异常类型3 变量名3){
//处理异常的方式3
}
…
finally{
//一定会执行的代码
}
说明:
1.finally是可选的 finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中有return语句,catch中有return语句等情况。
2.使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配。
3.一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况)。继续执行其后的代码
4.catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面,否则,报错。
5.常用的异常对象处理的方式:①String getMessage() ②printStackTrace();
6.在try结构中声明的变量,在出了try结构以后,就不能再被调用。
7.try-catch结构可以相互嵌套
②throws+异常类型
写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后序的代码,就不再执行!
体会:try-catch-finally:真正的将异常给处理掉了。
throws的方式只是将异常抛给了方法的调用者。并没有真正将异常处理掉。
方法重写的规则之一:子类重写的方法抛出的异常类型不大于父类被重写的方法的异常类型。
开发中如何选择使用try-catch-finally还是throws?
如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式处理。而执行的方法a可以考虑try-catch-finally方式进行处理。
用户自定义异常类
如何自定义异常类?
1.继承于现有的异常结构:RuntimeException、Exception
2.提供全局常量:serialVersionUID
3.提供重载的构造器
throw和throws不同?
throw是异常的生成阶段:手动抛出异常对象。声明在方法体内。
throws是异常的处理方式:声明方法可能要抛出的各种异常类。声明在方法的声明处。
35.多线程
进程可以细化为多个线程。
每个线程,拥有自己独立的:栈、程序计数器
多个线程,共享一个进程的结构:方法区、堆。
1.何时需要多线程:
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
需要一些后台运行的程序。
/*多线程的创建方式1:
*1.创建一个继承于Thread类的子类
*2.重写Thread类的run()--->将此线程执行的操作声明在run()中
*3.创建Thread类的子类的对象
*4.通过此对象调用start()
*/
class MyThread extends Thread{
@Override
public void run() {
private static int ticket=100; //必须要用static 因为new了多次Mythread
for(int i=0;i<100;i++){
if(i%2==0){
System.out.println(i);
}
}
}
}
public class ThreadTest{
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); //①启动当前线程 ②调用当前线程的run() 问题1:我们不能通过直接调用run()的方式启动线程 问题2:不可以再次让一个线程start() 需要重新创建一个线程对象
//如下线程仍然在main线程中执行的
for (int i = 0; i < 100; i++) {
System.out.println("******");
}
}
}
//创建Thread的匿名子类的方式
new Thread(){
public void run(){
xxx
}
}.start();
/**
Thread类中方法
*currentThread():静态方法,返回执行当前代码的线程
*getName():获取当前线程的名字
*setName():设置当前线程的名字 或构造器的方式
*Thread.currentThread().setName();给主线程命名
*yield()释放当前cpu的执行权
*join() 在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
*sleep(long millitime) 让当前线程睡眠millitime毫秒
*isAlive() 判断当前线程是否存活
*/
2.线程的优先级:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 默认优先级
3.如何获取和设置当前线程的优先级:
getPriority():获取线程的优先级
setPriority(int p):设置线程的优先级
说明:高优先级的线程要抢占低优先级cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
/**
* 创建多线程的方式2:实现Runnable接口
* 1.创建一个实现了Runnable接口的类
* 2.实现类去实现Runnable中的抽象方法:run()
* 3.创建实现类的对象
* 4.通过此对象作为参数传递到Thread类的构造器中,创建Thread对象
* 5.通过Thread类的对象调用start()
*/
public class ThreadTest1 {
public static void main(String[] args) {
MyThread myThread=new MyThread();
Thread t1 = new Thread(myThread);
t1.start();
}
}
class Mythread implements Runnable{
private int ticket=100; //不需要static修饰 因为只new一次Mythread
@Override
public void run() {
}
}
4.比较创建线程的两种方式:
开发中优先选择实现Runnable接口的方式
原因:1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
5.线程的声明周期 Thread.State类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8kWRxepf-1678071295193)(C:\Users\bxh\AppData\Roaming\Typora\typora-user-images\image-20230212134603842.png)]
6.线程的同步
在java中,我们通过同步机制,来解决线程的安全问题
方式1:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:1.操作共享数据的代码,即为需要被同步的代码 -->不能包含代码多了,也不能包含少了
2.共享数据:多个线程共同操作的变量
3.同步监视器:俗称 锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。(唯一)
实现Runnable接口创建多线程的方式中,可以使用this来充当同步监视器。
继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
方式2:同步方法
1.同步方法仍然设计到同步监视器,只是不需要我们显示的声明。
2.非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
方式3:Lock锁 JDK5.0新增
//实例化ReentrantLock
private ReentrantLock lock =new ReentrantLock();
run()方法内try{ 调用锁定方法lock.lock();} finally{调用解锁方法:lock.unlock()}
面试题:synchronized与Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动释放同步监视器
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
建议使用优先顺序:Lock ->同步代码块->同步方法
7.同步的方式,解决了线程的安全问题-----好处
操作同步代码时,只能有一个线程参与,其他线程等待。相等于是一个单线程的过程,效率低。-----局限性
8.死锁问题
9.线程的通信
涉及到的三个方法:
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait的一个进程。如果有多个线程被wait,就唤醒优先级高的线程。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
1.wait() notify() notifyAll()三个方法必须使用在同步代码块或同步方法
2.wait() notify() notifyAll()三个方法的调用者必须是同步代码块或同步方法中的监视器
否则,会出现IllegalMonitorStateException异常
3.wait() notify() notifyAll()三个方法是定义在java.lang.Object类中
面试题:sleep()和wait()的异同?
相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同点:(1)两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
(2)调用的范围不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块或同步方法中
(3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
/**
* 创建线程的方式三:实现Callable接口。 --JDK5.0新增
* 1.创建一个实现Callable的实现类
* 2.实现call方法,将此线程需要执行的操作声明在call()中
* 3.创建Callable接口实现类的对象
* 4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
* 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
* 6.获取Callable中call方法的返回值
*/
public class ThreadNew implements Callable {
@Override
public Object call() throws Exception {
int sum=0;
for (int i = 0; i < 100; i++) {
sum+=i;
System.out.println(i);
}
return sum;
}
}
class Test{
public static void main(String[] args) {
ThreadNew threadNew = new ThreadNew();
FutureTask futureTask = new FutureTask(threadNew);
Thread thread = new Thread(futureTask);
thread.start();
try {
//get()返回值即为FurtureTask构造器参数Callable实现类重写call()的返回值
Object o = futureTask.get();
System.out.println("总和为:"+o);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
10.如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
call()可以有返回值
call()可以抛出异常,被外面的操作捕获,获取异常的信息
Callable是支持泛型的
/**
* 创建线程的方式四:使用线程池
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
* 3.便于线程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime: 线程没有任务时最多保持多长时间后会终止
*/
class NumberThread implements Runnable{
@Override
public void run() {
int sum=0;
for (int i = 0; i < 100; i++) {
System.out.println(i);
sum+=i;
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1=(ThreadPoolExecutor) service;
service1.setCorePoolSize(15);
//2.执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适用于Runnable
//service.submit(Callable callable); //适用于Callbel
//3.关闭连接池
service.shutdown();
}
}
36.java常用类
String类
1.声明为final的,不可被继承
2.实现了Serializable接口:表示字符串是支持序列化的
实现了Comparable接口:表示String可以比较大小
3.String内部定义了final char[] value 用于存储字符串数据
4.String:代表不可变的字符序列。简称:不可变性。
体现:1.当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值
2.当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
3.当调用String的replace()修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
5.通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。String s=“bxh”;
6.字符串常量池中是不会存储相同内容的字符串的。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
String的实例化方式:
方式一:通过字面量定义的方式
方式二:通过new+构造器的方式
面试题:String s=new String(“abc”);方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:”abc“
public void test2(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";//编译期优化
//如果拼接符号的前后出现了变量,则相当于在堆空间中new String(),具体的内容为拼接的结果:javaEEhadoop
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
//intern():判断字符串常量池中是否存在javaEEhadoop值,如果存在,则返回常量池中javaEEhadoop的地址;
//如果字符串常量池中不存在javaEEhadoop,则在常量池中加载一份javaEEhadoop,并返回次对象的地址。
String s8 = s6.intern();
System.out.println(s3 == s8);//true
}
public class StringExer {
String str = new String("good");
char[] ch = {'t', 'e', 's', 't'};
public void change(String str, char ch[]) {
str = "test ok";
ch[0] = 'b';
}
public static void main(String[] args) {
StringExer ex = new StringExer();//
ex.change(ex.str, ex.ch);
System.out.println(ex.str);//good
System.out.println(ex.ch);//best
}
}
//str 的内容并没有变:“test ok” 位于字符串常量池中的另一个区域(地址),进行赋值操作并没有修改原来 str 指向的引用的内 容
String 与 char[] 之间的转换
1.String->char[]: 调用String的toCharArray();
String str1=“abc123”;
char[] charArray=str1.toCharArray();
2.char[]->String:调用String的构造器
char[] arr=new char[]{‘h’,‘e’,‘l’,‘l’,‘o’};
String str2=new String(arr);
String 与 byte[] 之间的转换
编码:String ->byte[]:调用String的getBytes()
解码:byte[]->String:调用String的构造器
StringBuffer和StringBuilder
String:不可变的字符序列;
StringBuffer:可变的字符序列;线程安全,效率低;
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全,效率高;
三者底层都使用char[]存储
StringBuffer sb1=new StringBuffer();//char[] value=new char[16];底层创建了一个长度是16的char型数组
StringBuffer sb2=new StringBuffer(“abc”);//char[] value=new char[“abc”.lenth()+16];底层创建了一个长度是"abc".lenth()+16的char型数组
问题1:System.put.println(sb1.lenth());//0
System.put.println(sb2.lenth());//3
问题2:扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中。
指导意义:开发中建议使用StringBuffer(int capacity)或StringBuilder(int capacity).
效率排序:StringBuilder>StringBuffer>String
public class TsDemo {
/**
* 基本数据类型的值传递,不改变其值
* 引用数据类型的值传递,改变其值
* <p>
* String类虽然是引用数据类型,但是他当作参数传递时和基本数据类型是一样的
*/
public static void main(String[] args) {
String s = "abc";
System.out.println(s); //abc
change(s);
System.out.println(s);//abc
System.out.println("---------------------");
System.out.println(s);//abc
changeString(s);
System.out.println(s);//abc
System.out.println("---------------------");
StringBuffer sb = new StringBuffer();
sb.append("abc");
System.out.println(sb);//abc
change(sb);
System.out.println(sb);//abc
}
public static void change(StringBuffer sb) {
//调用该方法时实际参数的sb和形式参数的sb指向的是同一个对象(StringBuffer容器)
//方法内部又在该容器里添加了"xyz",所以方法结束时,局部变量的sb消失,但是实际参数的sb所指向的容器的内部的内容已经发生了改变
sb.append("xyz");
}
public static void change(String s) {
s += "xyz";
}
//为了便于大家理解,再建立一个changeString方法
public static void changeString(String str) {
//因为str是属于局部变量,在调用该方法是实际参数s和形式参数str指向同一个字符串对象
//但是在方法内部将str又指向了一个新的字符串对象,而此时s还是指向的原来的字符串对象
//changeString方法执行完毕,局部变量str消失,方法内部产生的新的字符串对象称为垃圾
//但是s还是指向的原有的字符串对象,并没有改变
str += "xyz";
}
}
//大部分分析已经放在注释里面了,这里总结一下就是:看是否修改了实际存储指针指向的存储空间,对于String来说,change方法只是修改了方法内部的局部变量的值,方法结束时,局部变量消失,值并没有改变,对于StringBuffer的change方法,指向的存储空间已经发生了变化,方法退出后,局部变量sb消失,但是存储空间已经改变。
String str=null;
StringBuffer sb=new StringBuffer();
sb.append(str);
System.out.println(sb.length());//4
System.out.println(sb);//"null" 源码
StringBuffer sb1=new StringBuffer(str);//抛空指针异常
日期和时间:
- System.currentTimeMillis()
返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差
称为时间戳
- java.util.Date类
|–java.sql.Date类
1.两个构造器的使用 Date() Date(毫秒数)
2.两个方法的使用
toString():显示当前的年 月 日 时 分 秒
getTime();获取当前Date对象对应的毫秒数(时间戳)
3.java.sql.Date 对应着数据库中的日期类型的变量
实例化:new java.sql.Date();
java.util.Date->java.sql.Date
Date date=new Date();
java.sql.Date date=new java.sql.Date(date.getTime());
Date date1=new Date();
System.out.println(date1.toString());
System.out.println(date1.getTime());
-
SimpleDateFormat的使用:SimpleDateFormat对日期Date类的格式化和解析
1.SimpleDateFormat的实例化
默认构造器:SimpleDateFormat sdf=new SimpleDateFormat();
带参构造器:SimpleDateFormat sdf=new SimpleDateFormat(pattern); “yyyy-MM-dd hh:mm:ss”
2.两个操作:
格式化:日期–>字符串 Date date=new Date(); String format=sdf.format(date);
解析:字符串–>日期 String str =“19-2-1 上午11.25”; Date date1= sdf.parse(str); //要求字符串是符合SimpleDateFormat识别的格式,否则会抛异常
- Calendar日历类(抽象类)的使用
实例化
方式一:创建子类(GregorianCalendar)的对象
方法二:调用其静态方法getInstance()
Calender calender=Calender.getInstance();
常用方法
get() set() add() getime() setTime()
- LocalDateTime
- Instant
- DateTimeFormatter
java比较器
java中的对象,正常情况下只能进行比较 == 或 != 不能使用> 或 <
但是在开发场景中,需要对多个对象进行排序,如何实现?
使用两个接口中的任何一个:Comparable或Comparator
Comparable接口的使用举例:
-
像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方
-
重写compareTo(obj)的规则:>返回正整数 <返回负整数 =返回0
-
对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(),指明如何排序
Comparator接口的使用:定制排序
1.当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator的对象来排序
2.重写compare(Object o1,Object o2)方法,比较o1和o2的大小
Comparable和Comparator比较
某个类实现Comparable,并重写compareTo(),这个类就可以通过sort()排序,内部比较器,例如要比较自定义类Person的age,Person类要实现Comparable接口,要修改源代码。
Comparator相当于一个外部比较器,外部实现Comparator接口,重写compare(Object a,Object b)方法,不需要修改原来的源代码,当自定义对象进行比较时,把比较器和对象一起传过去就可以比较大小了。
System类
currentTimeMillis(); exit(int status); gc(); getProperty(String key)
Math类 BigInteger BigDecimal
37.枚举类
1.类的对象是有限的,确定的。
当需要定义一组常量时,强烈建议使用枚举类
2.如何定义枚举类:
方式一:jdk5.0之前,自定义枚举类
class Season{
//1.声明Season对象的属性;private final 修饰
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值
private Season(String seasonName,String seasonDesc){
this.seasonName=seasonName;
this.seasonDesc=seasonDesc;
}
//3.提供当前枚举类的多个对象:public static final
public static final Season SPRING=new Season("春天","春暖花开");
public static final Season SUMMER=new Season("夏天","夏日炎炎");
public static final Season AUTUMN=new Season("秋天","秋高气爽");
public static final Season WINTER=new Season("冬天","冰天雪地");
get(){}
toString(){}
}
方式二:jdk5.0,可以使用enum关键字定义枚举类
//说明:定义的枚举类默认继承于java.lang.Enum类 重写了toString方法
enum Season{
//1.提供当前枚举类的对象,多个对象之间用逗号隔开,末尾对象分号结束。
SPRING=("春天","春暖花开");
SUMMER=("夏天","夏日炎炎");
AUTUMN=("秋天","秋高气爽");
WINTER=("冬天","冰天雪地");
//2.声明Season对象的属性;private final 修饰
private final String seasonName;
private final String seasonDesc;
//3.私有化类的构造器,并给对象属性赋值
private Season(String seasonName,String seasonDesc){
this.seasonName=seasonName;
this.seasonDesc=seasonDesc;
}
get(){}
toString(){}//看具体诉求,可以不重写
}
3.enum中常用的方法:
values()
toString()
valueoOf() //找指定名的对象
4.使用enum关键字枚举类
case1:实现接口,在enum类中实现抽象方法
case2:让枚举类的对象分别实现接口中的抽象方法。
38.注解(Annotation) jdk5.0新增
1.可用于修饰包,类,构造器,方法,成员变量,参数,局部变量的声明
框架=注解+反射+设计模式
2…示例一:生成文档相关的注解
示例二:在编译时进行格式检查(jdk内置的三个基本注解)
@Override:限定重写父类方法,该注解只能用于方法
@Deprecated:用于表示所修饰的元素(类、方法等)已过时
@SuppressWarnings:抑制编译器警告
示例三:跟踪以来代码性,
3.如何自定义注解?参照@SupressWarnings定义
①注解声明为:@interface
②内部定义成员,通常使用value表示
③可以指定成员的默认值,使用default定义
④如果自定义注解没有成员,表明是一个标识作用
如果注解有成员,在使用注解时,需要指明成员的值
自定义注解必须配上注解的信息处理流程(使用反射)才有意义
4.jdk提供的四种元注解:对现有的注解进行解释说明的注释
Retention:指定所修饰的Annotation的声明周期:SOURCE/CLASS(默认)/RUNTIME
只有声明为RUNTIME声明周期的注解,才能通过反射获取。
Target:用于指定被修饰的Annotation能用于修饰哪些程序元素
Documented:表示所修饰的注解在被javadoc解析时,保留下来
Inherited:被它修饰的Annotation将具有继承性
5.通过反射获取注解信息
6.jdk8 中注解的新特性:可重复注解、类型注解
可重复注解:①在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
②MyAnnotation的Target和Retention和MyAnnotations相同
类型注解:ElementType.Type_PARAMETER表示该注解能写在类型变量的声明语句中(如:泛型声明)
ElementType.Type_USE 表示该注解能写在使用类型的任何语句中
39.集合框架
1.概述
集合、数组都是对多个数据进行存储操作的结构,简称java容器
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储
2.1数组在存储多个数据方面的特点:
一旦初始化以后,其长度就确定了。
数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。
比如:String[] arr;int [] arr1;Object[] arr2;
2.2数组在存储多个数据方面的缺点:
一旦初始化以后,其长度就不可被修改。
数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
集合的使用场景:
JOSN对象或JSON数组转为Java对象或Java对象构成的List
Java对象或Java对象构成的List转为JOSN对象或JSON数组
集合可分为Collection和Map两种体系
Collection:单列数据
List:元素有序、可重复的集合 ---->"动态"数组
ArrayList、LinkedList、Vector
Set:元素无序、不可重复的集合
HashSet、LinkedHashSet、TreeSet
Map:双列数据,保存具有映射关系key-value对的集合
HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rCwzRTMX-1678071295197)(C:\Users\bxh\AppData\Roaming\Typora\typora-user-images\image-20230217142627586.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XTuXXy4w-1678071295200)(C:\Users\bxh\AppData\Roaming\Typora\typora-user-images\image-20230217142658826.png)]
Collection接口中的方法使用
add() addAll() clear() containsAll(Collection coll) removeAll() 差集
contains(Object obj)//判断当前集合中是否包含obj 我们在判断时会调用obj对象所在类的equals()
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()
remove() 判断时会调用obj对象所在类的equals()
retainAll(coll) 获取当前集合和coll集合的交集,并返回给当前的集合
equals(Object obj) 要想返回true,需要当前集合和形参集合的元素都相同
hashCode() 返回当前对象的哈希值
集合–>数组 toArray()
数组–>集合 Arrays.asList()
iterator() 返回Iterator接口的实例,用于遍历集合元素。放在IteratorTest.java中
Iterator iterator=coll.iterator();
while(iterator.hasNext()){
iterator.next();
}
//for each循环内部仍然是调用了迭代器
//增强for循环是将数组中的重新赋给一个变量,修改此变量。不会改变数组或集合中的值
ArrayList、LinkedList、Vector的异同
同:三个类都实现了List接口,存储数据的特点相同:存储有序的、可重复的数据。
不同:ArrayList:作为List接口的主要实现类,线程不安全,效率高;底层使用Object[] elementData存储
LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储,
Vector:作为List接口的古老实现类,线程安全,效率低;底层使用Object[] elementData存储
ArrayList的源码分析:
- jdk7:ArrayList list=new ArrayList(); //底层创建了长度是10的Object[] 数组elementData
list.add(123); //elementData[0]=new Integer(123);
…
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的值复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list=new ArrayList(int capacity)
- jdk8:ArrayList list=new ArrayList(); //底层Object[] elementData初始化为{}并没有创建长度为10 的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
后序的添加和扩容操作与jdk1.7无异
小结:jdk7中的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
LinkedList的源码分析:
LinkedList list=new LinkedList(); //内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。
其中,Node定义为: 体现了LinkedList双线链表
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Vector的源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组
在扩容方面,默认扩容为原来的数组长度的2倍。
List接口中的常用方法:
增:void add(int index,Object ele)
插:boolean addAll(int index,Collection eles)
查:Object get(int index)
int indexOf(Object obj)
int lastIndexOf(Object obj)
删:Object remove(int index)
改:Object set(int index,Object ele)
List subList(int fromIndex,int toIndex) //左闭右开
//区分List中remove(int index)和remove(Object obj)
public void testListRemove(){
List list=new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);
}
private void updateList(List list){
list.remove(2); // [1,2]
list.remove(new Integer(2));//[1,3]
}
Set接口:无序 不可重复
1.无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
2.不可重复性:保证添加的元素按照equals()判断时,不能返回true 即:相同的元素只能添加一个
-
Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
-
要求:向Set中添加的数据,其所在类必须重写hashCode()和 equals()
要求:重写的hashCode()和 equals()尽可能保持一致性:相等的对象必须具有相等的散列码
重写两个方法的小技巧:对象中用作equals()方法比较的Field,都应该用来计算hashCode值。
HashSet:作为Set接口的主要实现类:线程不安全;可以存储null值
LinkedHashSet、作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
TreeSet: 可以按照添加对象的指定属性,进行排序
添加元素的过程:以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出HashSet底层数组中的存放位置(即:索引位置),判断数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功。 ----->情况1
如果此位置上有其他元素b(或以链表形式存在多个元素),则比较元素a与b的hash值:
如果hash值不相同,则元素a添加成功。 ----->情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法
equals()返回true,元素a添添加失败
equals()返回false,则元素a添加成功 ----->情况3
对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。(七上八下)
jdk7:元素a放到数组中,指向原来的数组
jdk8:原来的元素在数组中,指向a
HashSet底层是 链表+数组
LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
TreeSet
1.向TreeSet中添加的数据,要求是相同类的对象
2.排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)
3.自然排序中,比较两个对象是否相等的标准为:compareTo()返回0,不再是equals()
4.在定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()
TreeSet t=new TreeSet(Comparator comparator)
TreeSet和后面要讲的TreeMap采用红黑树的存储结构
特点:有序,查询速度比List快
集合Collection中存储的如果是自定义类的对象,需要自定义类重写哪个方法?
equals()
List: equals()
Set:
HashSet\LinkedHashSet为例: equals()、hashCode()
TreeSet为例:Comparable:ComparaTo(Object obj) 、Comparator:compare(Object o1,Object o2)
//equals() hashCode() 都重写过
public void test(){
HashSet set =new HashSet();
Person p=new Person(1001,"AA");
Person p=new Person(1002,"BB");
set.add(p1);
set.add(p2);
System.out.println(set); //[Person{id=1002,name='BB'},Person{id=1001,name='AA'}]
p1.name="CC";
set.remove(p1);//通过hashCode()找Person(1001,'CC')对象 找到一个新的位置
System.out.println(set); //[Person{id=1002,name='BB'},Person{id=1001,name='CC'}]
set.add(new Person(1001,"CC"));
System.out.println(set);
//[Person{id=1002,name='BB'},Person{id=1001,name='CC'},Person{id=1001,name='CC'}]
set.add(new Person(1001,"AA"));
System.out.println(set);
//[Person{id=1002,name='BB'},Person{id=1001,name='CC'},Person{id=1001,name='CC'},Person{id=1001,name='AA'}]
}
Map接口
HashMap:作为Map的主要实现类;线程不安全,效率高;存储null的key和value
HashMap的实例有俩个参数影响其性能: “初始容量” 和 装填因子
HashMap是采用拉链法解决哈希冲突的。(链表法是将相同hash值的对象组成一个链表放在hash值对应的槽位。)
|----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素
TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序和定制排序
底层使用红黑树。
Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
|----Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表(jdk7及之前)
数组+链表+红黑树 (jdk8)
面试题:
1.HashMap的底层实现原理?
2.HashMap和Hashtable的异同?
3.CurrentHashMap与Hashtable的异同?
Map结构的理解:
Map中的key:无序的、不可重复的,使用Set存储所有的key -------->key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所有的value ----->value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象
map中的entry:无序、不可重复的,使用Set存储所有的entry
HashMap的底层实现原理?
jdk7: HashMap map=new HashMap(); //在实例化以后,底层创建了长度是16的一维数组Entry[] table
…
map.put(key1,value1):
首先,调用key1所在类的hashCode()计算key1的哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
如果此位置上的数据为空,此时的key1-value1添加成功。 ----->情况1
如果此位置上的数据不为空(意味着此位置上存在一个或多个数据(以链表的形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时的key1-value1添加成功。 ----->情况2
如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法
如果equals()返回false:此时key1-value1添加成功。 ----->情况3
如果equals()返回true:使用value1替换value2.
补充:关于情况2和3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来的2倍,并将原有的数据复制过来。
jdk8 相较于jdk7在底层实现方式不同:
1.new HashMap():底层没有创建一个长度为16的数组
2.jdk8底层的数组是:Node[],而非Entry[]
3.首次调用put()方法时,底层创建长度是16的数组
4.jdk7底层结构只有数组+链表。jdk8中底层结构:数组+链表+红黑树
当数组的某一索引位置上的元素以链表形式存在的数据>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储。
map中常用的方法:
增、改:Object put(Object key,Object value) Object putAll(Map m)
删:Object remove(Object key) 返回value
查询:Object get(Object key)
void clear();
boolean containsKey(Object key)
boolean containsValue(Object value)
int size()
boolean isEmpty()
boolean equals(Object obj)
原视图操作的方法:
遍历:Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的set集合
Properties:
public static void main(String[] args) throws Exception{
Properties pros=new Properties();
FileInputStream fis=new FileInputStream("jdbc.properties");
pros.load(fis);//加载流对应的文件
String name=pros.getProperty("name");
String password=pros.getProperty("password");
System.out.println(name+password);
}
Collections和Collection的区别?
Collections是操作Collection和Map的工具类
Collections常用方法:格式: Collection.sort()
reverse(List):反转List中元素的顺序
shuffle(List):对list进行随机排序
sort(List)
sort(List,Comparator)
swap(List,int,int) 将指定list中的i处元素和j处元素交换
Object max(Collection)
Object max(Collection,Comparator)
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object) 返回指定集合中指定元素出现次数
void copy(List dest,List src) 将src中的内容复制到dest中
boolean replaceAll(List list,Object old Val,Object newVal)
Collections类中提供了多个synchroniedXxx()方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
List list1= Collections.synchronizedList(list); //返回的list1即为线程安全的List
40.泛型 jdk1.5新增
泛型:标签
在集合使用泛型之前的情况:1.类型不安全 2.强转时,可能出现ClassCastException
在集合使用泛型好处:
1.编译时就会进行类型检查,保证数据安全。 2.避免了强转操作
总结:①集合接口或集合类在jdk5.0时都修改为带泛型的结构。
②在实例化集合类时,可以知名具体的泛型类型
③指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。比如add(E e) —>实例化以后:add(Integer e)
④注意点:泛型类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换。
⑤如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
如何自定义泛型结构:泛型类、泛型接口、泛型方法
由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不需要指明泛型。
静态方法中不能使用类的泛型
异常类不能声明为泛型的
泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
换句话说,泛型方法所属的类是不是泛型类都没关系。
泛型方法,可以声明为静态的。原因:泛型参数是调用方法时确定的,并非在实例化类时确定。
public <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list=new ArrayLiset<>();
for(E e:arr){
list.add(e);
}
return list;
}
List list1 =null;
List list2 =null;
list1=list2;//list1和list2的类型不具备子父类关系 编译不通过
通配符的使用:
通配符:?
类A是类B的父类,G(A)和G(B)是没有关系的,二者共同的父类是:G<?>
? extends A 小于等于A
? super A 大于等于A
41.IO流
1.File类
File类的一个对象,代表一个文件或一个文件目录(文件夹)
File类声明在java.io包下
File file = new File(“”);
如何创建File类对象:File(String filePath) File(String parentPath,String childPath) File(File parentFile,String childPath)
路径分隔符:win:\ \ unix:/
常用方法:getAbsolutePath(); getPath(); getName(); getParent(); length(); lastModified(); list(); listFiles();
renameTo() isDirectory() idFile() exists() canRead() canWrite() isHidden() createNewFile() mkdir() mkdirs() delete()
File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
后序File类的对象常会作为参数传递到流的构造器中,指明读取或写入的“终点”。
流的分类:
操作数据单位:字节流、字符流
数据的流向:输入流、输出流
流的角色:节点流、处理流
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutPutStream | Writer |
节点流:(或文件流)
FileInputStream (read(byte[] buffer))
FileOutputStream (write(byte[] buffer,0,len))
FileReader(read(char[] cbuf))
FileWriter (write(char[] cbuf,0,len))
缓冲流:(处理流)BufferedInputStream read(byte[] buffer))
BufferedOutputStream (write(byte[] buffer,0,len) / flush() )
BufferedReader (read(char[] cbuf)/String readLine())
BufferedWriter (write(char[] cbuf,0,len)/ flush() )
FileReader fr=null;
try{
File file=new File("hell.txt");
fr=new FileReader(file);
//int date=fr.read();
//while(data!=-1){
//System.out.print((char)data);
//data=fr.read();
//}
//改进
int data;
while((data=fr.read())!=-1){
System.out.print((char)data);
}
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(fr!=null)
fr.close();
}catch(IOException e){
e.printStackTrace();
}
}
//read()操作升级,使用read的重载方法。
FileReader fr=null;
try{
File file=new File("hell.txt");
fr=new FileReader(file);
char[] cbuf=new char[5];
int len;
while((len=fr.read(cbuf))!=-1){
for(int i=0;i<len;i++){ //错误写法:for(int i=0;i<cubf.lenth;i++) String str=new String(cubf); 正确:String str=new String(cubf,0,len);
fw.write(cubf,0,len)
System.out.print(cbuf[i]);
}
}
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(fr!=null)
fr.close();
}catch(IOException e){
e.printStackTrace();
}
}
输出操作,对应的File可以不存在的。
如果不存在,在输出的过程中,会自动创建此文件
如果存在:如果流使用的构造器是FileWriter(file,false)/FileWriter(file):对原有文件的覆盖
如果流使用的构造器是FileWriter(file,true):追加操作。
结论:对于文本文件(.txt,.java,.c,.cpp),使用字符流处理 。如果是复制操作,且不在终端读,也可以用字节流。
对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt)使用字节流
缓冲流:提高读写速率 原因:内部提供了一个缓存区
处理流就是套接在已有流的基础上。
1.造文件
2.造流
2.1造字节流
2.2造缓冲流
3.复制的细节:读取、写入
4.资源关闭
要求:先关闭外层流,再关闭内层的流
说明:关闭外层流的同时,内层流也会自动进行关闭。所以内层流的关闭可以省略。
转换流:提供了字符流和字节流之间的转换
InputStreamReader:将一个字节的输入流转换为字符的输入流 继承自Reader
OutputStreamWriter:将一个字符的输出流转换为字节的输出流 继承自Writer
解码:字节、字节数组---->字符数组、字符串
编码:字符数组、字符串—>字节、字节数组
字符集: InputStreamReader(InputStream in) 创建一个默认字符集字符输入流
InputStreamReader(InputStream in,String charsetName) 创建一个指定字符集字符输入流
OutputStreamWriter(OutputStream out)
OutputStreamWriter(OutputStream out,String charsetName)
/*将UTF-8编码的文本文件,转换为GBK编码的文本文件。
1、指定UTF-8编码的转换流,读取文本文件。
2、使用GBK编码的转换流,写出文本文件。
*/
package com.thr;
import java.io.*;
/**
* @author Administrator
* @date 2020-02-27
* @desc 将读入UTF-8文件转换为GBK
*/
public class ConversionStreamTest {
public static void main(String[] args) {
//定义转换流
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
//创建流对象,指定GBK编码
isr = new InputStreamReader(new FileInputStream("D:\\IO\\utf8.txt"),"UTF-8");
osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK");
int len;
char[] buffer = new char[1024];
while ((len=isr.read(buffer))!=-1){
osw.write(buffer,0,len);
}
System.out.println("成功...");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//释放资源
if (osw!=null){
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (isr!=null){
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ASCII:美国标准信息交换码
ISO8859-1:拉丁码表,欧洲码表
GB2312:中国的中文编码表
GBK:中国的中文编码表升级
Unicode:国际标准码
UTF-8:变长的编码方式
**对象流:**ObjectInputStream ObjectOutputStream
用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把java中的对象写入到数据源中,也能把对象从数据源中还原回来。
序列化:用ObjectOutputStream类保存基本数据或对象的机制
反序列化:用ObjectInputStream类读取基本数据类型或对象的机制
不能序列化static和transient修饰的成员变量。
对象序列化机制:允许把内存中的java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其他程序获取了这种二进制流,就可以恢复成原来的java对象。
自定义类需要满足如下要求,方可序列化:
1.需要实现接口:Serializabel
2.当前类提供一个全局常量:serialVersionUID
3.除了当前Person类需要实现Serializable接口外,还必须保证其内部所有属性也必须是可序列化的(默认基本数据类型可序列化,引用类型的属性也要序列化)
不能序列化static和transient修饰的成员变量。
RandomAccessFile(随机流)的使用
1.直接继承于java.lang.Object类,实现了DataInput和DataOutput接口
2.RandomAccessFile即可以作为一个输入流。又可以作为一个输出流
3.如果RandomAccessFile作为输出流时,写入到的文件如果不存在,则在执行过程中自动创建
如果写入到的文件存在,则会对原有文件内容进行覆盖。(默认从头覆盖) seek()指定位置
4.可以通过相关操作,完成插入操作
42.网络编程
通信双方地址
IP:唯一的标识Internet上的计算机(通信实体) 在java中使用InetAddress类代表IP。 IPv4 IPv6 万维网 局域网
如何实例化InetAddress两个方法:InetAddress.getByname(String host)、InetAddress.getLocalHost()
两个常用方法:getHostName() /getHostAddress()
端口号:标识正在计算机上运行的进程 公认端口:0~1023 注册端口:1024~49151 动态/私有端口:49152~65535
端口号与IP地址的组合得出一个网络套接字:Socket
一定的规则:网络通信协议
OSI参考模型:应用层 表示层 会话层 传输层 网络层 数据链路层 物理层
TCP/IP参考模型: 应用层 传输层 网络层 物理+数据链路层
OSI参考模型 | TCP/IP参考模型 | TCP/IP参考模型各层对应协议 |
---|---|---|
应用层 | 应 | HTTP、FTP、Telnet、DNS… |
表示层 | 用 | |
会话层 | 层 | |
传输层 | 传输层 | TCP、UDP… |
网络层 | 网络层 | IP、ICMP、ARP… |
数据链路层 | 物理层+ | Link |
物理层 | 数据链路层 |
//客户端发送内容给服务器端,服务器端将内容打印到控制台上
public class TCPTest1 {
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
try {
//1.创建socket对象,指明服务器端的ip和端口号
InetAddress inet = InetAddress.getByName("127.0.0.1");
socket = new Socket(inet, 8899);
//2.获取一个输入流,用于输出数据
os = socket.getOutputStream();
//3.写出数据的操作
os.write("你好,我是客户端".getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//4.资源的关闭
if (os!=null){
try {
os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
@Test
public void server() {
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
//1.创建服务器端的ServerSocket,指明自己的端口号
ss = new ServerSocket(8899);
//2.调用accept()表示接收来自于客户端的socket
socket = ss.accept();
//3.获取输入流
is = socket.getInputStream();
//不建议这样写,可能会有乱码
// byte[] buffer =new byte[20];
// int len;
// while((len=is.read())!=-1){
// String s=new String(buffer,0,len);
// System.out.println(s);
//4.读取输入流的数据
byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer=new byte[5];
int len;
while((len=is.read(buffer))!=-1){
byteArrayOutputStream.write(buffer,0,len);
}
System.out.println(byteArrayOutputStream.toString());
System.out.println("收到来自于"+socket.getInetAddress().getHostAddress()+"的数据");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
5.关闭资源
if (byteArrayOutputStream!=null){
try {
byteArrayOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if(is!=null){
try {
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if(ss!=null){
try {
ss.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
43.反射机制
1. 反射的概述(熟悉)
- Java给我们提供了一套API,使用这套API我们可以在运行时动态的获取指定对象所属的类,创建运行时类的对象,调用指定的结构(属性、方法)等。
- API:
java.lang.Class
:代表一个类- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
- … …
- 反射的优点和缺点
- 优点:
-
提高了Java程序的灵活性和扩展性,
降低了耦合性
,提高自适应
能力 -
允许程序创建和控制任何类的对象,无需提前
硬编码
目标类
-
- 缺点:
- 反射的
性能较低
。- 反射机制主要应用在对灵活性和扩展性要求很高的系统框架上
- 反射会
模糊
程序内部逻辑,可读性较差
。
- 反射的
- 优点:
- 反射,平时开发中,我们使用并不多。主要是在框架的底层使用。
2. Class:反射的源头
-
Class的理解 (掌握)
针对于编写好的.java源文件进行编译(使用javac.exe),会生成一个或多个.class字节码文件。接着,我们使用 java.exe命令对指定的.class文件进行解释运行。这个解释运行的过程中,我们需要将.class字节码文件加载(使用类的加载器)到内存中(存放在方法区)。加载到内存中的.class文件对应的结构即为Class的一个实例。
-
获取Class的实例的几种方式(前三种)
- 类.class //调用运行时的静态属性:class
- 对象.getClass() //调用运行时类的对象的getClass()
- (使用较多)Class调用静态方法forName(String className) //全类名 Class.forName(“xxx”)
- (了解)使用ClassLoader的方法loadClass(String className)
-
Class 可以指向哪些结构。
简言之,所有Java类型! (1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类 (2)interface:接口 (3)[]:数组 (4)enum:枚举 (5)annotation:注解@interface (6)primitive type:基本数据类型 (7)void
3. 类的加载过程、类的加载器(理解)
-
类的加载过程
过程1:类的装载(loading) 将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成 过程2:链接(linking) > 验证(Verify):确保加载的类信息符合JVM规范,例如:以cafebabe开头,没有安全方面的问题。 > 准备(Prepare):正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。 > 解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。 过程3:初始化(initialization) 执行类构造器<clinit>()方法的过程。 类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。
-
类的加载器(jdk8):双亲委派机制
5.1 作用:负责类的加载,并对应于一个Class的实例。 5.2 分类(分为两种): > BootstrapClassLoader:引导类加载器、启动类加载器 > 使用C/C++语言编写的,不能通过Java代码获取其实例 > 负责加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容) > 继承于ClassLoader的类加载器 > ExtensionClassLoader:扩展类加载器 > 负责加载从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库 > SystemClassLoader/ApplicationClassLoader:系统类加载器、应用程序类加载器 > 我们自定义的类,默认使用的类的加载器。 > 用户自定义类的加载器 > 实现应用的隔离(同一个类在一个应用程序中可以加载多份);数据的加密。
Properties处理属性文件
//方式一 读取的文件的默认路径为当前的model
Properties pros =new Properties();
FileInputStream is=new FileInputStream(new File("info.properties"));
pros.load(is);
String name=pros.getProperty("name");
String pwd=pros.getProperty("password");
//方式二 通过类的加载器读取的文件的默认路径为:当前model下的src下
Properties pros =new Properties();
InputStream is=ClassLoader.getSystemClassLoader().getResourceAsStream("info.properties");
pros.load(is);
String name=pros.getProperty("name");
String pwd=pros.getProperty("password");
4. 反射的应用1:创建运行时类的对象(重点)
Class clazz = Person.class;
//创建Person类的实例
Person per = (Person) clazz.newInstance();
System.out.println(per);
要想创建对象成功,需要满足:
条件1:要求运行时类中必须提供一个空参的构造器
条件2:要求提供的空参的构造器的权限要足够。
JavaBean中要求给当前类提供一个公共的空参构造器。有什么用?
场景一:子类对象在实例化时,子类的构造器的首行默认调用父类空参的构造器。
场景二:在反射中,经常用来创建运行时类的对象。那么我们要求各个运行时类都提供一个空参的构造器,便于我们编写创建运行时类对象的代码。
在jdk9中标识为过时,替换成什么结构?
通过Constructor类调用newInstance(...)
5. 反射的应用2:获取运行时类所有的结构
(了解)获取运行时类的内部结构1:所有属性、所有方法、所有构造器
(熟悉)获取运行时类的内部结构2:父类、接口们、包、带泛型的父类、父类的泛型等
6. 反射的应用3:调用指定的结构(重点)
3.1 调用指定的属性(步骤)
步骤1.通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
步骤2. setAccessible(true):确保此属性是可以访问的
步骤3. 通过Filed类的实例调用get(Object obj) (获取的操作)
或 set(Object obj,Object value) (设置的操作)进行操作。
3.2 调用指定的方法(步骤)
步骤1.通过Class的实例调用getDeclaredMethod(String methodName,Class ... args),获取指定的方法
步骤2. setAccessible(true):确保此方法是可访问的
步骤3.通过Method实例调用invoke(Object obj,Object ... objs),即为对Method对应的方法的调用。
invoke()的返回值即为Method对应的方法的返回值
特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null
3.3 调用指定的构造器(步骤)
步骤1.通过Class的实例调用getDeclaredConstructor(Class ... args),获取指定参数类型的构造器
步骤2.setAccessible(true):确保此构造器是可以访问的
步骤3.通过Constructor实例调用newInstance(Object ... objs),返回一个运行时类的实例。
7. 反射的应用4:注解的使用(了解)
略
8. 体会:反射的动态性
public class ReflectTest {
//体会:静态性
public Person getInstance(){
return new Person();
}
//体会:反射的动态性
//举例1:
public <T> T getInstance(String className) throws Exception {
Class clazz = Class.forName(className);
Constructor con = clazz.getDeclaredConstructor();
con.setAccessible(true);
return (T) con.newInstance();
}
@Test
public void test1() throws Exception {
Person p1 = getInstance();
System.out.println(p1);
String className = "com.atguigu04.other.dynamic.Person";
Person per1 = getInstance(className);
System.out.println(per1);
String className1 = "java.util.Date";
Date date1 = getInstance(className1);
System.out.println(date1);
}
//体会:反射的动态性
//举例2:
public Object invoke(String className,String methodName) throws Exception {
//1. 创建全类名对应的运行时类的对象
Class clazz = Class.forName(className);
Constructor con = clazz.getDeclaredConstructor();
con.setAccessible(true);
Object obj = con.newInstance();
//2. 获取运行时类中指定的方法,并调用
Method method = clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
return method.invoke(obj);
}
@Test
public void test2() throws Exception {
String className = "com.atguigu04.other.dynamic.Person";
String methodName = "show";
Object returnValue = invoke(className,methodName);
System.out.println(returnValue);
}
}
二、企业真题
2.1 反射概述
- 对反射了解吗?反射有什么好处?为什么需要反射?(微*银行)
类似问题:
> Java反射的作用是什么?(三*重工、上海*和网络)
> Java反射机制的作用有什么?(上海明*物联网)
> 反射的具体用途?(阿***芝*信用项目组)
略
- 反射的使用场合和作用、及其优缺点(*软国际)
类似问题:
> 反射机制的优缺点(君*科技)
> Java反射你怎么用的?(吉*航空)
略
- 实现Java反射的类有什么?(君*科技)
类似问题:
> Java反射 API 有几类?(北京*蓝)
问API。
- 反射是怎么实现的?(上海立*网络)
从Class说起。
2.2 Class的理解
- Class类的作用?生成Class对象的方法有哪些?(顺*)
反射的源头。 主要有三种。
- Class.forName(“全路径”) 会调用哪些方法 ? 会调用构造方法吗?加载的类会放在哪?(上*银行外包)
Class.forName() 会执行执行类构造器()方法。
不会调用构造方法
加载的类放在方法区。
2.3 类的加载
- 类加载流程(汇**通、同*顺、凡*科技)
过程1:类的加载(loading)
将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由加载器完成
过程2:链接(linking)
验证(Verify):确保加载的类信息符合JVM规范,例如:以cafababe开头,没有安全方面的问题。
准备(Prepare):正式为类变量设置默认初始值的阶段,这些内存都将在方法区中进行分配。
解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
过程3:初始化(initialization)
执行类构造器()方法的过程
类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的
2.4 创建对象
- 说一下创建对象的几种方法?(华油***集团、*科软、凡*科技)
类似问题:
> 除了使用new创建对象之外,还可以用什么方法创建对象?(*云网络)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LPuOGcqz-1678071295207)(E:\51单片机\03_复习与企业真题\images\image-20221214145240412.png)]
- 如何找到对象实际类的?(*度)
对象.getClass();
Object obj = new Date();
obj.getClass();// 获取到的是Date。
- Java反射创建对象效率高还是通过new创建对象的效率高?(三*重工)
new 的方式。
2.5 调用属性、方法
- 如何利用反射机制来访问一个类的方法?(神州**软件)
调用指定的方法(步骤)
步骤1.通过Class的实例调用getDeclaredMethod(String methodName,Class ... args),获取指定的方法
步骤2. setAccessible(true):确保此方法是可访问的
步骤3.通过Method实例调用invoke(Object obj,Object ... objs),即为对Method对应的方法的调用。
invoke()的返回值即为Method对应的方法的返回值
特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null
- 说一下Java反射获取私有属性,如何改变值?(阿****麻信用项目组)
调用指定的属性(步骤)
步骤1.通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
步骤2. setAccessible(true):确保此属性是可以访问的
步骤3. 通过Filed类的实例调用get(Object obj) (获取的操作)
或 set(Object obj,Object value) (设置的操作)进行操作。
针对于核心源码的api,内部的私有的结构在jdk17中就不可以通过反射调用了。
静态代理
特点:代理类和被代理类在编译期间,就确定下来了。
interface ClothFactiry{
void produceCloth();
}
//代理类
class ProxyFactory implements ClothFactory{
private ClothFactory factory;//用被代理类对象进行实例化
public ProxyFactory(ClothFactory factory){
this.factory=factory;
}
public void produceCloth(){
System.out.println("代理工厂做一些准备");
factory.produceCloth();
System.out.println("代理工厂做一些后序的收尾工作");
}
}
//被代理类
class NikeClothFactory implements ClothFactiry{
public void produceCloth(){
System.out.println("Nike工厂生产一批运动服");
}
}
public class StaticProxyTest{
public static void main(String[] args){
//创建被代理对象
ClothFactory nike =new NikeClothFactory();
//创建代理类的对象
ProxyFactory proxyFactory=new ProxyFactory(nike);
proxyFactory.produceCloth();
}
}
动态代理(反射的应用)面向切面编程
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
public String getBelief(){
return "I believe I can fly";
}
public void eat(String food){
System.out.println("我喜欢吃"+food);
}
}
//动态代理需要解决的问题
//问题1:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
//问题2:当通过代理类的对象调用方法a时,如何动态的取调用被代理类中的同名方法a
class ProxyFactory{
//调用此方法,返回一个代理类的对象。解决问题1
public static Object getProxyInstance(Object obj){//obj:被代理类的对象
MyInvocationHandler handler =new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass.getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;
public void bind(Object obj){
this.obj=obj;
}
//当我们通过代理类的对象调用方法a时,就会自动调用如下的方法:invoke()
//将被代理类要执行的方法a的功能声明在invoke()中
public Object invoke(Object peoxy,Method method,Object[] args)throws Throwable{
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue=method.invoke(obj,args);
//上述方法的返回值就作为当前类中的invoke()的返回值
return returnValue;
}
}
public class ProxyTest{
public static void main(String[] args){
//创建被代理对象
SuperMan superMan =new SuperMan();
Human proxyInstance= (Human)proxyFactory.getProxyInstance(superMan);
proxyInstance.getBeliet();
proxyInstance.eat("xxx");
}
}