Java反射笔记1

概述:

  • 反射是指对于任何一个Class类,在运行的时候能直接得到这个类的全部成分,这种运行时动态获取类信息以及动态调用类中成分的能力叫做Java反射
    正常方式:引入需要的”包类”名称-》通过New实例化-》取得实例化对象

反射方式:实例化对象-》getClass()方法-》得到完整的包类名称

相关API:

java.lang.Class:代表一个类,Class对象表示某个类加载后在堆里的对象

java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法

java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量

java.lang.reflect.Constructor:代表类的构造器,Constructor对象表示构造器

关键:

反射的第一步是先得到编译后的Class类对象,然后就可以得到Class的全部成分

HelloWorld.java ->javac ->HelloWorld.class

Class c = HelloWorld.class;

即在运行时获取类的字节码文件对象,然后可以解析类中的全部成分

功能:

  • 判断任意一个对象所属的类
  • 判断任意一个类的成员变量和成员方法
  • 调用任意一个对象的方法
  • 构造任意一个类的对象
  • 动态代理
    获取Class类对象的方法:
  1. Class类静态方法forName(String className)(将字节码文件加载进内存返回Class对象)
    Class c = Class.forName(全限名:包名+类名)#源代码阶段

  2. 类名.class,通过类名的属性class#Class对象阶段

Class c1 = Student.class;

3.对象.getClass()获取实例化对象对应类的Class对象#Runtime运行阶段

Student s = new Student();

Class c2 = s.getclass();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class GetClassName {
public static void main(String[] args) throws ClassNotFoundException{
// 类的.class属性
Class c1 = GetClassName.class;
System.out.println(c1.getName());

// 实例化对象的getClass()方法
GetClassName demo = new GetClassName();
Class c2 = demo.getClass();
System.out.println(c2.getName());

// Class.forName(String className): 动态加载类
Class c3 = Class.forName("com.GetClassName");
System.out.println(c3.getName());

}
}

结果:
com.GetClassName
com.GetClassName
com.GetClassName

以上三种获取Class类方法中,使用类.class属性会需要我们导入类的包,依懒性较强;如果是使用实例化对象的getClass()方法,需要我们本身创建一个对象,不太方便反而偏离了反射机制,所以一般使用Class.forName方法,此方法不用导入其他类,能加载任意类
获取成员方法Method

Method getMethod(String name, 类<?>… parameterTypes) //返回该类所声明的public方法

Method getDeclaredMethod(String name, 类<?>… parameterTypes) //返回该类所声明的所有方法

//第一个参数获取该方法的名字,第二个参数获取标识该方法的参数类型Method[] getMethods() //获取所有的public方法,包括类自身声明的public方法,父类中的public方法、实现的接口方法

Method[] getDeclaredMethods() // 获取该类中的所有方法

以上是对反射的一些用途简述,但反射是比较抽象,也是看了很多教程才逐渐有个雏形,技术通常是因为需求才诞生的,所以提出一个需求来引出反射。

假如说有一个配置文件re.properties,内容为

1
2
3
classfullpath=com.reflection.Cat

method=hi

根据配置文件的信息来创建对象并调用其方法
创建对象的话,常见的方法应该是建一个类然后New一个对象或者Clone一个 ,按照思路走一下,新建一个包(自定义),并创建RefletionDemo.java文件和Cat.java

1
2
3
4
5
6
7
8
9
 #Cat.java

public class Cat {
private String name = "1";
public void hi(){
System.out.println("hi"+name);
}

}

传统创建对象的方式:

1
2
3
Cat cat = new Cat();
cat.hi();
#先new一个对象,再调用方法

这里传统方法不适用于我们的需求,可以考虑文件流读取?

1
2
3
4
5
6
7
Properties properties = new Properties();
properties.load(new FileInputStream("src\\com.itheima\\variable\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();
String methodName = properties.get("method").toString();
System.out.println("classfullpath="+classfullpath);
System.out.println("method="+methodName);
#使用Properties类可以读写配置文件

以上代码获取了配置文件里类的全路径和方法名并输出,输出的结果和配置文件完全一致
然后

1
new classfullpath()?

这是错误的,因为new后面接类名,但是classfullpath是字符串类型,是很明显不行的
直接new.类的全路径是的可以,但直接new.classfullpath()是不行的,因为这里是字符串,所以传统的方法是不行的(这里的需求可以提炼为通过外部文件配置,在不修改源码的情况下控制程序)所以这里引出了反射

1
2
3
4
5
6
7
8
9
10
11
12
#加载类,返回一个Class类型的对象cls,Class是一个Class类型的类
Class cls = Class.forName(classfullpath);
#通过cls得到加载的类的对象实例
Object o = cls.newInstance();
#输出o的运行类型,很明显是Cat
System.out.println(o.getClass());
#通过cls得到加载的类Cat的method“hi”方法对象
#即在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
#通过method1调用方法,即通过方法对象来调用方法
method1.invoke(o);
#传统方式是对象.方法(),反射机制是方法.invoke(对象)

这里如果在Cat.java里增加新方法

1
2
3
4
5
6
7
8
9
10
11
12
 #Cat.java

public class Cat {
private String name = "1";
public void hi(){
System.out.println("hi"+name);
}

public void cry(){
System.out.println("miaomiaomiao");
}
}

如果按照传统方法,只能修改源码将cat.hi()改为cat.cry()
如果按照反射,直接在配置文件里把method的值改为cry就行了

反射机制

  1. 反射允许程序在执行期借助于Refletion这个API取得任何类的内部信息,并能操作对象的属性以及方法
  2. 加载完类之后,在堆中产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构
    这里对于Class类再举个小例子,Class也是类,没有区别
1
2
p对象 -》类型 person类
cls对象 -》 类型Class类(可以理解为RefletionClass类)

反射的底层原理:
Java程序在计算机有三个阶段,编译阶段.类加载阶段.Runtime阶段

1
2
3
4
5
 public class Cat {
private String name
public void hi(){
}
}

编译阶段中,源码里会有成员变量.成员方法.构造器等元素,经过javac编译后生成Cat.class字节码文件,源码里拥有的元素,字节码文件中也会有
在Runtime运行阶段中如果进行创建对象即

1
2
Cat cat = new Cat();
cat.hi();

会导致类加载,致使Cat.class字节码文件加载到内存里堆去,进入Class类加载阶段,生成一个Class类对象,其含有字节码里所有元素,在从字节码文件得到Class类对象的过程中有一个类加载器ClassLoader作用,在这里就体现了反射机制,在Class类对象中,会把元素比如成员变量当做对象映射为Field[] Field,构造器和成员方法等同理
类加载后就生成了Cat对象,该对象知道他是属于哪个Class对象,其间有一个映射关系,所以我们可以通过这个对象获得所关联的Class对象。当得到Class对象后就能够创建对象,调用对象方法,操作属性等

图片

获取成员变量Field

获取成员变量Field位于java.lang.reflect.Field包中

Field[] getFields() :获取所有public修饰的成员变量

Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符

Field getField(String name) 获取指定名称的 public修饰的成员变量

Field getDeclaredField(String name) 获取指定的成员变量

得到name字段:

1
2
3
4
5
#java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
#getField不能得到私有属性

Field nameField = cls.getField("age");
System.out.println(nameField.get(o));#传统写法 对象.成员变量 ,反射 成员变量对象.get(对象)

获取构造器对象
Class类中用于获取构造器的方法

Constructor[] getConstructors() :返回所有构造器对象的数组#仅public

Constructor[] getDeclareConstructors():返回所有构造器对象的数组(存在即可)

Constructor[] getConstructor(parameterTypes):返回单个构造器对象#仅public

Constructor[] getDeclareConstructor(parameterTypes):返回单个构造器对象,存在即可

1
2
3
4
5
6
7
#java.lang.reflect.Constructor:代表类的成员变量,Constructor对象表示构造器

Constructor constructor = cls.getConstructor();//括号中指定构造器参数类型,无就是无参构造器
System.out.println(constructor);

Constructor constructor2 = cls.getConstructor(String.class);//括号中指定构造器参数类型,无就是无参构造器
System.out.println(constructor2);