一、复习
1、为什么用反射?
为了在“运行时”动态的加载某个类,并且创建它的对象,或者调用它的成员(成员变量、成员方法),或者某个类型的详细信息。
反过来说,如果不用反射,就必须在“编译时”就需要确定类型等。
2、反射的根源(起点):Class类的对象。
这么理解:
编写的代码编译后在“硬盘”上以xx.class文件的形式存在。
这些代码是要先“加载”到内存中,然后才能用。在内存中每一个xxx.class文件要以对象的形式存储在内存中,
这个对象的类型就是Class类型。
每一个Java的类型 <==> xx.class文件或数据
Class对象 《==》 xx.class文件或数据
Class对象 == 一个类型
3、Class对象的获取方式:4种
(1)类型名.class
(2)某个类型的实例对象.getClass()
(3)Class.forName("类型的全名称")
(4)类加载器对象.loadClass("类型的全名称")
如何选择四种方式?
第一:如果编写当前代码时,这个类型是已知的,确定的,存在的,那么可以直接 类型名.class 最直接最简单
第二:如果说某个对象的类型是不确定的,但是对象有某个变量(通常这个变量是父类或父接口类型的),
这个时候如果需要获取对象的运行时的类型,选择 (2)
第三:如果编写当前代码时,这个类型是未知的,不确定的,或暂时还不存在,需要通过读取.xml,.properties,注解信息等方式
才能获取到类型名称的,那么用(3)
第四:一般是自定义类加载器,或者是需要加载某个特定目录下的字节码文件,才会用第(4)种方式。
4、反射的应用
(1)获取类的详细信息
java.lang.Class类
java.lang.reflect(反射)包下的类型:
Package、Modifier、Constructor、Field、Method、Array....
java.lang.Annotation
(2)运行时动态的创建任意类型的对象
A:直接用Class对象.newInstance()
这种方式要求这个类型必须有公共的无参构造
B:先获取类型的构造器Constructor的对象,然后用构造器对象.newInstance(...)
(3)运行时动态的操作任意的成员变量
操作静态变量步骤:
A:获取Class对象
B:获取你要操作的静态变量的Field对象
C:调用Field对象的set/get方法
如果Field对象代表的成员变量的权限修饰符有限制的话,可以调用Field对象的setAccessible(true)
操作非静态变量步骤:
A:获取Class对象
B:获取你要操作的静态变量的Field对象
C:创建Class对象代表的类型的实例对象
例如:Class对象代表的是Student类型,那么要创建Student的对象
D:调用Field对象的set/get方法
如果Field对象代表的成员变量的权限修饰符有限制的话,可以调用Field对象的setAccessible(true)
(4)运行时动态的调用任意的方法
调用静态方法步骤:
A:获取Class对象
B:获取你要调用的静态方法的Method对象
C:调用Method对象的invoke方法
如果Method对象代表的静态方法的权限修饰符有限制的话,可以调用Method对象的setAccessible(true)
调用非静态方法步骤:
A:获取Class对象
B:获取你要调用的静态方法的Method对象
C:创建Class对象代表的类型的实例对象
例如:Class对象代表的是Student类型,那么要创建Student的对象
D:调用Method对象的invoke方法
如果Method对象代表的静态方法的权限修饰符有限制的话,可以调用Method对象的setAccessible(true)
(5)运行时获取泛型父类的信息
(6)运行时获取某个类、方法、成员变量等上面的注解信息
(7)动态的使用Array类型来创建,操作数组
(8)运行时获取某个类的内部类信息,或者外部类信息
5、类的加载
类的加载步骤:
(1)加载
(2)连接:检查、准备、解析
(3)初始化
类的加载的结果:Class对象
类的加载程序:类加载器
类加载器分为4个等级:
(1)引导类加载器
(2)扩展类加载器
(3)应用程序类加载器(程序员自己写的类基本上是它加载的)
(4)自定义类加载器
类加载器的工作模式:双亲委托模式
public class TestReview {
public static void main(String[] args) throws Exception {
//(1)获取int类型的Class对象
Class clazz1 = int.class;
//(2)获取com.atguigu.bean包下某个Javabean类型,这个类型还不确定,要通过info.properties文件才能确定具体的类名
Properties properties = new Properties();//创建一个集合对象,容器对象,用来装从info.properties读取的key,value键值对
properties.load(TestReview.class.getClassLoader().getResourceAsStream("info.properties"));
// Class clazz2 = Class.forName(properties.getProperty("className"));
Student s1 = new Student(1,"张三");
Student s2 = new Student(2,"李四");
System.out.println(s1.equals(s2));
System.out.println(s1.equals("张三"));
}
public static void method(Object obj){
//(3)在这里获取obj对象的运行时类型
Class<?> c3 = obj.getClass();
}
}
class Student{
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()){
System.out.println("类型不一致");
return false;
}
System.out.println("类型一致,都是Student对象");
Student student = (Student) o;
return id == student.id &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
某些类加载的过程,不会立刻触发类初始化。
(1)使用某个类的静态的常量
(2)如果通过子类调用父类的静态成员,子类不会初始化,但是会初始化父类。
(3)使用某个类声明数组,并创建数组对象,但是还未创建元素对象
这三种以外的情况,对类的使用会立刻触发类初始化。
(1)创建这个类的对象
(2)调用这个类的静态成员
(3)通过反射使用某个类
(4)如果使用子类,子类初始化时,会触发父类的初始化
(5)main方法所在的类,在运行main之前,先要对这个类进行初始化
public class TestExam {
static{
System.out.println("main方法所在类的静态代码块");
}
public static void main(String[] args) throws ClassNotFoundException {
System.out.println(MyClass.MAX_VALUE);//100
Son.method();//通过子类名去调用父类静态方法
MyClass[] arr = new MyClass[5];//产生了一种新的类型MyClass[]类型,对这种新的类型进行初始化
arr[0] = new MyClass();
Class.forName("com.atguigu.review.Son");//导致Son类初始化
}
}
class MyClass{
public static final int MAX_VALUE = 100;
static {
System.out.println("MyClass静态代码块");
}
}
class Father{
static {
System.out.println("1.父类的静态代码块");
}
public static void method(){
System.out.println("2.父类的静态方法");
}
}
class Son extends Father{
static {
System.out.println("3.子类的静态代码块");
}
}