Java反射

一、反射概述

  • Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过镜子可以看到类的结构,所以,我们形象的称之为:反射。
  • 动态语言:
    • 是一类再运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以删除或是其他结构上的变化,通俗来说就是在运行时,代码可以根据某些条件改变自身结构。
    • 如:C#、JavaScript、PHP、Python、Erlang。
  • 静态语言:
    • 与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。
  • Java不是动态语言,但是Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程时更加灵活。
  • Java反射机制提供的功能:
    1. 在运行时判断任意一个对象所属的类
    2. 在运行时构造任意一个类的对象
    3. 在运行时判断任意一个类所具有的成员变量和方法
    4. 在运行时获取泛型信息
    5. 在运行时调用任意一个对象的成员变量和方法
    6. 在运行时处理注解
    7. 生成动态代理

二、反射功能的体验

一个自定义的Person类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class Person {
private String name; //私有属性name
public Integer age; //公有属性age

//公有空参构造器
public Person() {
}

//公有构造器
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}

//私有构造器
private Person(String name) {
this.name = name;
}

//公有方法
public void eat() {
System.out.println("这是公有方法:吃饭方法");
}

//私有方法
private Integer walk(Integer metre) {
System.out.println("这是私有方法:走路方法,走了" + metre + "米");
return metre;
}

//getter/setter方法
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//没有反射之前,可以做的操作
@Test
public void test1() {
//1.可以创建Person类的对象(只能使用公有的构造器)
Person p1 = new Person("Tom", 20);
//2.通过对象可以调用内部公有的的属性、方法
//调用公有属性
p1.age = 10;
System.out.println(p1); //Person{name='Tom', age=10}
//调用公有方法
p1.eat(); //这是公有方法:吃饭方法
//3.在Person类外部,不可以调用Person类的私有的结构
//比如:name、walk()以及私有构造器
}

//有反射后,可以做的操作
//为了代码结构清晰,因此直接throws异常
@Test
public void test2() throws Exception {
Class<Person> clazz = Person.class;
//1.通过反射,可以创建Person类对象
Constructor<Person> constructor = clazz.getConstructor(String.class, Integer.class);
Person p1 = constructor.newInstance("Tom", 20);
System.out.println(p1); //Person{name='Tom', age=20}
//2.通过反射,调用对象指定的属性、方法
//调用属性
Field age = clazz.getDeclaredField("age");
age.set(p1, 10);
System.out.println(p1);//Person{name='Tom', age=10}
Method eat = clazz.getDeclaredMethod("eat");
eat.invoke(p1); //这是公有方法:吃饭方法
//3.通过反射,可以调用Person的私有结构:私有构造器,私有属性,私有方法
//调用私有构造器
Constructor<Person> declaredConstructor = clazz.getDeclaredConstructor(String.class);
declaredConstructor.setAccessible(true);
Person p2 = declaredConstructor.newInstance("Jack");
System.out.println(p2); //Person{name='Jack', age=null}
//调用私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p2, "Zhangsan");
System.out.println(p2); //Person{name='Zhangsan', age=null}
//调用私有方法
Method walk = clazz.getDeclaredMethod("walk", Integer.class);
walk.setAccessible(true);
Integer metre = (Integer) walk.invoke(p2, 1000);//这是私有方法:走路方法,走了1000米
System.out.println(metre); //方法的返回值:1000
}

由上面的示例,我们自然会想到下面两个问题:

  1. new和反射都能实例化对象,那么开发中应该用哪个?
    • 一般来说还是建议使用new来实例化对象。new只能用于编译期就能确定的类型。
    • 反射的使用场景:在编译时并不能够确定需要实例化哪一个对象,需要在运行时才能确定,这时候才会使用反射。
  2. 反射既然能直接调用私有结构,那么封装性的意义是什么。
    • 封装性解决的并不是安全问题,而仅是一种作用域的声明,避免无意识的冲突和篡改。private的声明只是建议最好不要调用此结构,相当道德约束,而非法律约束。
    • 而反射解决的是能不能调用的问题。只要是你想调用的代码结构,尽管是私有的,反射仍然能够让你调用它。

三、对Class的理解

  • 注意:class和Class没有任何关系,class是声明类的关键字,Class是JDK提供的java.lang.Class类,不要混淆!

  • 类的加载过程:

    • 程序经过Javac.exe命令以后,会生成一个或多个字节码文件(.class文件)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中,此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。即,Class的实例就对应着一个运行时类。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //获取Class实例方式
    @Test
    public void test3() throws ClassNotFoundException {
    //方式一:调用运行时类大的属性:.class
    Class clazz1 = Person.class;
    System.out.println(clazz1); //class com.reflection.Person

    //方式二;通过运行时类的对象,调用getClass()
    Person person = new Person();
    Class clazz2 = person.getClass();
    System.out.println(clazz2); //class com.reflection.Person

    //方式三:调用Class静态方法:forName(String classPath)
    Class clazz3 = Class.forName("com.reflection.Person");
    System.out.println(clazz3); //class com.reflection.Person

    //方式四:使用类的加载器:ClassLoader
    ClassLoader classLoader = ReflectionTest.class.getClassLoader();
    Class clazz4 = classLoader.loadClass("com.reflection.Person");
    System.out.println(clazz4); //class com.reflection.Person

    //获取的都是同一个运行时类,因为类只加载一次
    System.out.println(clazz1 == clazz2); //true
    System.out.println(clazz1 == clazz3); //true
    System.out.println(clazz1 == clazz4); //true
    }
  • 可以有Class对象的结构:

    • class:外部类、成员(成员内部类,静态内部类)、局部内部类、匿名内部类
    • interface:接口
    • []:数组
    • enum:枚举
    • annotation:注解
    • primitive type:基本数据类型
    • void
  • 注意:只要元素类型和维度一致,就是同一个Class

    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    public void test() {
    int[] a = new int[10];
    int[] b = new int[100];
    Class c1 = a.getClass();
    Class c2 = b.getClass();
    System.out.println(c1 == c2); //true
    }

使用ClassLoader加载配置文件

1
2
username=Tom
password=123456
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test() throws Exception {
Properties pros = new Properties();
//方式一:
//此时的路径在module下
//FileInputStream fis = new FileInputStream("jdbc.properties");
//pros.load(fis);

//方式二:
ClassLoader classLoader = ClassLoader.class.getClassLoader();
//此时的路径在src下
InputStream is = classLoader.getResourceAsStream("jdbc.properties");
pros.load(is);

String username = pros.getProperty("username");
String password = pros.getProperty("password");
System.out.println("username=" + username + "\npassword=" + password);
}

四、反射详细API

1、Class类对象:

  • public T newInstance():创建对象
  • public String getName():返回完整类名带包名
  • public String getSimpleName():返回类名
  • public Field[] getFields():返回类中public修饰的属性
  • public Field[] getDeclaredFields():返回类中所有的属性
  • public Field getDeclaredField(String name):根据属性名name获取指定的属性
  • public native int getModifiers():获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号
  • public Method[] getDeclaredMethods():返回类中所有的实例方法
  • public Method getDeclaredMethod(String name, Class<?>… parameterTypes):根据方法名name和方法形参获取指定方法
  • public Constructor<?>[] getDeclaredConstructors():返回类中所有的构造方法
  • public Constructor getDeclaredConstructor(Class<?>… parameterTypes):根据方法形参获取指定的构造方法
  • public native Class<? super T> getSuperclass():返回调用类的父类
  • public Class<?>[] getInterfaces():返回调用类实现的接口集合

2、获取构造方法:

  • Constructor getConstructor(Class[] params):根据构造函数的参数,返回一个具体的具有public属性的构造函数
  • Constructor getConstructors():返回所有具有public属性的构造函数数组
  • Constructor getDeclaredConstructor(Class[] params):根据构造函数的参数,返回一个具体的构造函数(不分public和非public属性)
  • Constructor getDeclaredConstructors():返回该类中所有的构造函数数组(不分public和非public属性)

2.1、Constructor类方法:

  • public String getName():返回构造方法名
  • public int getModifiers():获取构造方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号
  • public Class<?>[] getParameterTypes():返回构造方法的修饰符列表(一个方法的参数可能会有多个。)
  • public T newInstance(Object … initargs):创建对象【参数为创建对象的数据】

3、获取类的成员方法:

  • Method getMethod(String name, Class[] params): 根据方法名和参数,返回一个具体的具有public属性的方法
  • Method[] getMethods(): 返回所有具有public属性的方法数组
  • Method getDeclaredMethod(String name, Class[] params):根据方法名和参数,返回一个具体的方法(不分public和非public属性)
  • Method[] getDeclaredMethods():返回该类中的所有的方法数组(不分public和非public属性)

3.1、Method类方法

  • public String getName():返回方法名
  • public int getModifiers():获取方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号
  • public Class<?> getReturnType():以Class类型,返回方法类型
  • public Class<?>[] getParameterTypes():返回方法的修饰符列表(一个方法的参数可能会有多个。)
  • public Object invoke(Object obj, Object… args):调用方法

4、获取类的成员变量:

  • Field getField(String name) :根据变量名,返回一个具体的具有public属性的成员变量
  • Field[] getFields():返回具有public属性的成员变量的数组
  • Field getDeclaredField(String name): 根据变量名,返回一个成员变量(不分public和非public属性)
  • Field[] getDelcaredFields() :返回所有成员变量组成的数组(不分public和非public属性)

4.1、Field类方法:

  • public String getName():返回属性名
  • public int getModifiers():获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号
  • public Class<?> getType():以Class类型,返回属性类型
  • public void set(Object obj, Object value):设置属性值
  • public Object get(Object obj):读取属性值
  • public void setAccessible(boolean flag)默认false,设置为true为打破封装