事情的起因是一道面向对象的作业题,老师:你们做了就会了。题目和解答见文章末尾

反射基础

用途

反射功能通常用于检查或修改Java虚拟机运行中(runtime)的应用程序的行为

  1. 在运行中分析类的能力,可以通过完全限定类名创建类的对象实例。

  2. 在运行中查看和操作对象,可以遍历类的成员变量。

  3. 反射允许代码执行非反射代码中非法的操作,可以检索和访问类的私有成员变量,包括私有属性、方法等。

注意:要有选择的使用反射功能,如果可以直接执行操作,那么最好不要使用反射。

反射的底层运作

通常,java在编译之后,会将Java代码生成为class源文件,JVM启动时,将会载入所有的源文件,并将类型信息存放到方法区中;将所有对象实例存放在Java堆中,同时也会保存指向类型信息的指针。

img

正常流程下,我们在创建类的实例之前,是明确知道这个类的类型信息的,如名字、方法、属性等,我们可以很容易地创建实例,并通过实例获取属性和调用方法。

如果我们不知道一个方法在实际运行时,需要处理的对象是谁,它的类型信息如何,那么该如何访问这个对象,或是创建一个新的实例呢?

与上述流程相反,我们需要先在方法区获取对象的类型信息,获取之后就可以知道该类的属性、方法、父子类等信息,此时便可以通过这些信息回调处理对象。

以上便是反射的原理,通过读取方法区中的字节码,来动态的找到其反射的类以及类的方法和属性等(实际上就是在运行时,根据全类型名在方法区找对应的类),用这些类型信息完成对该类实例的操作,其实就是直接使用类的一个逆向使用。

下面是一段抄来的代码,可以帮我们有效理解这个过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void reflectMethod(Object obj) {
// 处理这个无法明确类型的实例对象

// 获取类型信息
Class<?> aClass = obj.getClass();
Field[] fields = aClass.getFields();
Method[] methods = aClass.getMethods();
Annotation[] annotations = aClass.getAnnotations();
Constructor<?>[] constructors = aClass.getConstructors();
Class<?>[] interfaces = aClass.getInterfaces();
// ...
// 操作属性或方法
Field field = fields[0];
Object o = field.get(obj); // 获取obj的属性值
}

在Java中的使用

相关类

在这个包下

1
java.lang.reflect.*;

比较常用的一些类

含义
java.lang.Class 代表整个字节码。代表一个类型,代表整个类。
java.lang.reflect.Method 代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor 代表字节码中的构造方法字节码。代表类中的构造方法。
java.lang.reflect.Field 代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。

必须先获得Class才能获取Method、Constructor、Field

获取类型信息(Class)

要操作一个类的字节码,需要首先获取到这个类的字节码,有三种方式可以获取Class

方式 备注
Class.forName(“完整类名带包名”) 静态方法
对象.getClass()
任何类型.class

以上三种方式返回值均为Class类型

Class.forName()

通过完全限定类名获取类,即包名+类名,否则会报错找不到类

1
2
3
4
5
6
7
8
9
Class<HashMap> hashMapClass = Class.forName("java.util.HashMap");


// class类源码
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

Object.getClass()

从一个实例对象中获取它的类,这仅适用于继承自Object的引用类型(Java的类默认继承于Object)

1
2
3
4
Map<String, String> hashMap = new HashMap<>();
Class<? extends Map> aClass = hashMap.getClass();
String text = "text";
Class<? extends String> aClass1 = text.getClass();

xxx.class

直接从未实例化的类获取类

1
2
Class<Integer> integerClass = int.class;
Class<HashMap> hashMapClass = HashMap.class;

通过反射实例化对象

1
对象.newInstance()

newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
否则会抛出java.lang.InstantiationException异常。

下面是一颗栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
class ReflectTest02{
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 下面这段代码是以反射机制的方式创建对象。

// 通过反射机制,获取Class,通过Class来实例化对象
Class c = Class.forName("javase.reflectBean.User");
// newInstance() 这个方法会调用User这个类的无参数构造方法,完成对象的创建。
// 重点是:newInstance()调用的是无参构造,必须保证无参构造是存在的!
Object obj = c.newInstance();
System.out.println(obj);
}
}

获取类的成员变量

获取属性

方法名 备注
public Field[] getFields() 返回类中public修饰的属性
public Field[] getDeclaredFields() 返回类中所有的属性
public Field getDeclaredField(String name) 根据属性名name获取指定的属性
public native int getModifiers() 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】

获取方法

方法名 备注
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() 返回调用类实现的接口集合

Field类方法

获取字段类型

字段可以是基本类型或引用类型。

有八种基本类型:boolean,byte,short,int,long,char,float,和double。

引用类型是java.lang.Object类的直接或间接子类,包含接口,数组和枚举类型等 。

1
2
3
Class<?> className = Class.forName("java.util.HashMap");
Field table = className.getDeclaredField("table");
Class<?> type = table.getType();

获取字段修饰符

修饰符:public、protected、private、static、final等

1
2
3
4
5
6
7
8
9
10
11
12
Class<?> className = Class.forName("java.util.HashMap");
Field table = className.getDeclaredField("table");
// 获取属性的名字
String name = table.getName();
// 获取属性的类型
Class<?> type = table.getType();
// 获取修饰符
int modifiers = table.getModifiers();
System.out.println(Modifier.toString(modifiers));
// 获取注解
Override annotation = table.getDeclaredAnnotation(Override.class);
Annotation[] declaredAnnotations = table.getDeclaredAnnotations();

使用反射机制给属性赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//使用反射机制给属性赋值
Class studentClass = Class.forName("javase.reflectBean.Student");
Object obj = studentClass.newInstance();// obj就是Student对象。(底层调用无参数构造方法)

// 获取no属性(根据属性的名称来获取Field)
Field noField = studentClass.getDeclaredField("no");
// 给obj对象(Student对象)的no属性赋值
/*
虽然使用了反射机制,但是三要素还是缺一不可:
要素1:obj对象
要素2:no属性
要素3:22222值
注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
*/
noField.set(obj, 22222);

// 读取属性的值
// 两个要素:获取obj对象的no属性的值。
System.out.println(noField.get(obj));

set()访问私有属性

不能直接访问,需要打破封装后才可以

1
2
public void setAccessible(boolean flag)
//默认false,设置为true为打破封装

示例:

1
2
3
4
5
6
7
8
Field nameField = studentClass.getDeclaredField("name");
// 打破封装(反射机制的缺点:打破封装,可能会给不法分子留下机会!!!)
// 这样设置完之后,在外部也是可以访问private的。
nameField.setAccessible(true);
// 给name属性赋值
nameField.set(obj, "xiaowu");
// 获取name属性的值
System.out.println(nameField.get(obj));
  • 反射修改final修饰的属性值

    反射功能强大,能修改private以及final修饰的变量。如下代码中,展示了JVM的优化以及反射的一些劣势。

    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
    @Data
    public class FieldReflectDemo {
    // 引用直接指向常量池中的常量值
    private final String constantStr = "FinalConstantStringField";
    // JVM优化了getter方法,直接将对constantStr引用全部替换成了常量
    // public String getConstantStr() {return "FinalConstantStringField";}

    // 在堆中新建了一个对象
    private final String newStr = new String("FinalNewStringField");

    public FieldReflectDemo(){}
    public static void main(String[] args) {
    FieldReflectDemo fieldReflectDemo = new FieldReflectDemo();
    try {
    Class<?> className = fieldReflectDemo.getClass();
    Field constantStr = className.getDeclaredField("constantStr");
    Field newStr = className.getDeclaredField("newStr");
    // 获取实例对象的字段值
    System.out.println("constantStr原:" + constantStr.get(fieldReflectDemo));
    System.out.println("newStr原:" + newStr.get(fieldReflectDemo));
    constantStr.setAccessible(true);
    newStr.setAccessible(true);
    constantStr.set(fieldReflectDemo, "New Filed Name");
    newStr.set(fieldReflectDemo, "New Filed Name");
    System.out.println("constantStr反射修改:" + constantStr.get(fieldReflectDemo));
    System.out.println("newStr反射修改:" + newStr.get(fieldReflectDemo));
    } catch (NoSuchFieldException | IllegalAccessException e) {
    e.printStackTrace();
    }
    System.out.println("constantStr实例对象值:" + fieldReflectDemo.getConstantStr());
    System.out.println("newStr实例对象值:" + fieldReflectDemo.getNewStr());
    /**
    - 输出
    - constantStr原:FinalConstantStringField
    - newStr原:FinalNewStringField
    - constantStr反射修改:New Filed Name
    - newStr反射修改:New Filed Name
    - constantStr实例对象值:FinalConstantStringField
    - newStr实例对象值:New Filed Name
    */
    }
    }

    因为JVM在编译时期, 就把final类型的直接赋值的String进行了优化, 在编译时期就会把String处理成常量。反射成功将其值修改成功了,但是在它的get方法中,返回的不是当前变量,而是返回JVM优化好的一个常量值。

Method类方法

获取方法类型的信息

方法声明包含了方法名、修饰符、参数、返回类型以及抛出的多个异常。

以及通过反射调用实例对象的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Class<? extends MethodReflectDemo> demoClass = methodReflectDemo.getClass();
Method method = demoClass.getDeclaredMethod("getNumByName", String.class);
String name = method.getName();
System.out.println("方法名:" + name);

// 修饰符
//【一般配合Modifier类的toString(int x)方法使用】
int modifiers = method.getModifiers();
System.out.println("所有修饰符:" + Modifier.toString(modifiers));

// 参数
Parameter[] parameters = method.getParameters();

// 返回类型
//【结果集一般配合Class类的getSimpleName()方法使用】
Class<?> returnType = method.getReturnType();
System.out.println("返回类型:" + returnType.getTypeName().getSimpleName());

// 异常
Class<?>[] exceptionTypes = method.getExceptionTypes();

// 实例对象调用方法
Object resValues = method.invoke(methodReflectDemo, "实参");
System.out.println(resValues);

Contructor类

获取

与Method相似,只是构造函数没有返回值,并且无法被实例对象执行,它的调用只能为给定的类创建对象的新实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class<? extends ConstructorReflectDemo> demoClass = methodReflectDemo.getClass();
Constructor<? extends ConstructorReflectDemo> constructor = demoClass.getConstructor();
String name = constructor.getName();
System.out.println("构造方法名:" + name);

// 修饰符
int modifiers = constructor.getModifiers();
System.out.println("所有修饰符:" + Modifier.toString(modifiers));

// 参数
Parameter[] parameters = constructor.getParameters();

// 异常
Class<?>[] exceptionTypes = constructor.getExceptionTypes();

// 构造方法无法被调用,只可以创建新实例
ConstructorReflectDemo constructorReflectDemo = constructor.newInstance();

反射机制创建对象

  1. 先获取到这个有参数的构造方法【用ClassgetDeclaredConstructor()方法获取】
  2. 调用构造方法new对象【用Constructor类的newInstance()方法new对象】
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
//通过反射机制调用构造方法实例化java对象。(这个不是重点)
class ReflectTest{
public static void main(String[] args) throws Exception {
//不使用反射创建对象
Vip vip1 = new Vip();
Vip vip2 = new Vip(123, "zhangsan", "2001-10-19", false);

//使用反射机制创建对象(以前)
Class vipClass = Class.forName("javase.reflectBean.Vip");
// 调用无参数构造方法
Object obj1 = vipClass.newInstance();//Class类的newInstance方法
System.out.println(obj1);

//使用反射机制创建对象(现在)
// 调用有参数的构造方法
// 第一步:先获取到这个有参数的构造方法
Constructor c1 = vipClass.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);
// 第二步:调用构造方法new对象
Object obj2 = c1.newInstance(321, "lsi", "1999-10-11", true);//Constructor类的newInstance方法
System.out.println(obj2);

// 获取无参数构造方法
Constructor c2 = vipClass.getDeclaredConstructor();
Object obj3 = c2.newInstance();
System.out.println(obj3);
}
}

如果需要调用无参构造方法,getDeclaredConstructor()方法形参为空即可【和Class类的newInstance()方法一样的效果】!

万恶之源(题目)

题目要求

  1. 写⼀个关于复数类ComplexNumber的类描述,并完成编译。

1)属性: dRealPart:实部; dImaginPart:虚部

2)构造⽅法:ComplexNumber() 以及 ComplexNumber(double r, double i)

3)⽅法:复数相加 complexAdd(ComplexNumber c); 复数相减 complexMinus(ComplexNumber

c); 打印当前复数toString().

  1. 写⼀个新类,利⽤反射实现:
  2. 列出ComplexNumber类的构造函数、属性和⽅法。
  3. 调⽤复数相加的⽅法,显⽰出结果

代码

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class ComplexNumber {
private double dRealPart; // 实部
private double dImaginaryPart; // 虚部

// 默认构造方法
public ComplexNumber() {
this.dRealPart = 0.0;
this.dImaginaryPart = 0.0;
}

// 带参数的构造方法
public ComplexNumber(double r, double i) {
this.dRealPart = r;
this.dImaginaryPart = i;
}

// 复数相加
public ComplexNumber complexAdd(ComplexNumber c) {
return new ComplexNumber(this.dRealPart + c.dRealPart, this.dImaginaryPart + c.dImaginaryPart);
}

// 复数相减
public ComplexNumber complexMinus(ComplexNumber c) {
return new ComplexNumber(this.dRealPart - c.dRealPart, this.dImaginaryPart - c.dImaginaryPart);
}

// 打印当前复数
@Override
public String toString() {
return dRealPart + " + " + dImaginaryPart + "i";
}
}

public class ReflectionExample {
public static void main(String[] args) {
// 利用反射获取ComplexNumber类的构造函数、属性和方法
try {
Class<?> cls = Class.forName("ComplexNumber");

System.out.println("Constructors:");
Constructor[] constructors = cls.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}

System.out.println("\nFields:");
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}

System.out.println("\nMethods:");
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}

// 调用复数相加的方法,显示结果
System.out.println("\nInvoke complexAdd method:");
ComplexNumber c1 = new ComplexNumber(2.5, 3.0);
ComplexNumber c2 = new ComplexNumber(1.5, 1.0);
Method complexAddMethod = cls.getDeclaredMethod("complexAdd", ComplexNumber.class);
ComplexNumber result = (ComplexNumber) complexAddMethod.invoke(c1, c2);
System.out.println("Result of complexAdd: " + result);

} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |
InvocationTargetException e) {
e.printStackTrace();
}
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Constructors:
public ComplexNumber()
public ComplexNumber(double,double)

Fields:
private double ComplexNumber.dRealPart
private double ComplexNumber.dImaginaryPart

Methods:
public java.lang.String ComplexNumber.toString()
public ComplexNumber ComplexNumber.complexAdd(ComplexNumber)
public ComplexNumber ComplexNumber.complexMinus(ComplexNumber)

Invoke complexAdd method:
Result of complexAdd: 4.0 + 4.0i

Process finished with exit code 0

写在最后

整理之前没有想到会这么多,懒狗一边划水一边整理,大概花了两天才写完,并且感觉大部分知识都是囫囵咽下的,姑且当个方法词典,随用随查吧。

有所纰漏,感谢指正