Java基础:Reflection 反射机制理解

反射(Reflection)是 Java 中的一种工具,即运行态的 Java 程序可获取任意一个对象的信息,并且可以操作类或对象的内部属性(类型、属性、方法)。
  
程序中一般的对象的类型都是在编译期就确定下来的,而Java反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。

反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。反射是相对常规的通过new来创建对象方式的反操作的称呼。

实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。

概述

JAVA反射机制是在运行状态中,对于任意一个对象,可以获取到该对象的所有属性和方法,并可以执行调用;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

要了解一个运行态的对象,必须先要获取到该类的字节码文件对象(Class),每一个对象都会有一个字节码文件对象,通过Class对象的方法,可以获取到类对象的方法和属性。

Class对象

获得Class对象

在Java程序中获得Class对象通常有如下三种方式:

  1. 使用Class类的forName(Sting ClazzName)静态方法。传入的是类的全限定类名。
    Class c1 = Class.forName("Student");
  2. 调用类的class属性来获取该类对应的Class对象。
    Class c2 = Student.class;
  3. 调用任何一个对象都有的getClass()方法。返回所属类对应的Class对象。
    Student st = new Student();
    Class c3 = st .getClass(); //c3是运行时类 (e的运行时类是Student)

第一和第二种都是直接根据类来取得该类Class对象,第二种方式有两种优势:代码更安全(编译阶段即可检查需要访问的Class对象是否存在)、程序性能更好(无须调用方法)

Class类提供了大量的方法来获取Class对象所对应类的详信息,可以获取该类里包含的成员变量、构造器、方法、内部类、注解等。

Class方法

  1. **getDeclaredConstructors()**获取对应类的所有构造器,含private修饰的构造。
  2. getConstructors获取对应类的所有公共(public)修饰的构造。

反射

反射的作用

  1. 在运行态时创建类的对象。
  2. 判断创建的对象所属的类。
  3. 获取类所具有的属性和方法(暴力反射可获取private类和方法)。
  4. 可以调用对象的方法。

反射相关的类

  • java.lang.Class;
  • java.lang.reflect.Constructor;
  • java.lang.reflect.Field;
  • java.lang.reflect.Method;
  • java.lang.reflect.Modifier;

反射中的方法,属性等操作我们可以从这四个类中查询。多查JDK的API

反射的使用

Class对象可以获得该类的方法(由Method对象表示)、构造器(由Constructor对象表示)、成员变量(由Field对象表示)。

创建对象

创建对象有两种方式

  1. 使用Class对象的newInstance()方法来创建该Class对象对应类的实例。
    前提是该
    Class对象有默认的构造器,而执行的newInstance()**方法时实际上是利用默认无参构造器来创建该类的实例。
    1
    2
    3
    4
    Class stClazz = Class.forName("Student"); 
    //创建此Class 对象所表示的类的一个新实例
    //调用了Student的无参数构造方法.
    Object st = stClazz .newInstance();
  2. 先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class**对象对应类的实例。可以先择使用指定的构造器来创建实例。
    1
    2
    3
    4
    5
    6
    7
    //获取class对象 
    Class<Person> p = Person.class;
    //获取有参构照方法
    Constructor<Person> cstr = p.getConstructor(String.class,int.class);
    //创建对象并传参
    Person person = cstr.newInstance("张三",22);
    System.out.println(person);

第一种方式比较常见,在很多JaveEE框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据字符串业创建对应的实例,这就必须用到反射。Spring框架的XML配置文件使用就是这种方式的体现。

调用方法

当获得某个类对应的Class对象后,可通过该Class对象的**getMethods()方法或getMethod()**方法来获取全部方法或指定方法——这两个方法的返回值是Method()数组,或Method()对象

当通过Methodinvoke()方法来执行对应的方法时,如果是private修改的访问,必须先开启访问权限,使用setAccessible(boolean flag)方法,将值置为true,指示Method在使用时取消访问权限查。

访问类属性

通过Class对象的getFields()getField()方法可以获取该类所包括的全部属性或指定的属性。
Field提供了两组方法来读取或设置成员变量值:

  1. getXxx(Object obj):获取obj对象的该成员变量的值。如果该成员变量的类型是引用类型,则取消get后面的Xxx。
  2. setXxx(Object obj, Xxx val):将obj对象的该成员变量设置成val值。如果该成员变量的类型是引用类型,则取消set后面的Xxx。

反射示例

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class People {

public String nickName;
private String realName;
private int age;
public String getP1(String name, int age) {
return "name= " + name + "; age= " + age;
}
public String getP2(String name, int age) {
return "name= " + name + "; age= " + age;
}
@Override
public String toString() {
return "People [nickName=" + nickName + ", realName=" + realName + ", age=" + age + "]";
}
}

创建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
27
28
29
30
31
32
33
34
35
36
package com.demo;
public class Test3 {
public static void main(String[] args) {

Person p1 = new Person();
Class<?> clazz1 = null;
try {
//方式一:Class.forName("类全限定名")
clazz1 = Class.forName("com.demo.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

//判断是否同一个对象
boolean instance = clazz1.isInstance(p1);
System.out.println(instance);

//方式二:调用任何一个类都具有的class属性。
Class<Person> clazz2 = Person.class;
Person p2 = new Person();

//方式三:调用任何一个类都具有的getClass()方法。
Class<? extends Person> clazz3 = p2.getClass();
System.out.println(clazz1 == clazz2);
System.out.println(clazz2 == clazz3);

// 获取Person的名为name的成员变量
Field nameField = clazz3.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p1, "Yeeku.H.Lee");
Field ageField = personClazz.getDeclaredField("age");
ageField.setAccessible(true);
ageField.setInt(p1, 30);
System.out.println(p1);
}
}

结果如下:

1
2
3
4
===========输出内容==============================================
true
true
true

配置文件创建对象

分别有父类水果(Fruit);子类苹果(Apple)、香蕉(Banana)、桔子(Orange)、榨汁(squeeze);榨汁机(Juicer)。
配置文件名config.properties,文件里有一行类名的参数com.heima.reflect.Apple,读取配置文件里的一行内容,获取类的字节码对象,通过对象调用newInstance()方法创建一个新的实例。

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
package com.demo;
import java.io.BufferedReader;
import java.io.FileReader;
public class Test4 {
public static void main(String[] args) throws Exception {

// //没有用反射,只在说多态
// Juicer j = new Juicer();
// //购买榨汁机
// j.run(new Apple());
//
// //在榨法机类中添加run方法重载,接收橘子
// j.run(new Orange());//父类引用指向子类对象
// 用反射和配置。config.properties内容:com.demo.Apple 文件放在工程根目录下
BufferedReader br = new BufferedReader(new FileReader("config.properties"));

Class<?> clazz = Class.forName(br.readLine());
Fruit f = (Fruit) clazz.newInstance(); // 父类引用指向子类对象,水果的引用指向了苹果对象
Juicer j = new Juicer();
j.run(f);
}
}
interface Fruit {
public void squeeze();
}
class Apple implements Fruit {
public void squeeze() {
System.out.println("榨出一杯苹果汁");
}
}
class Orange implements Fruit {
public void squeeze() {
System.out.println("榨出一杯橘子汁");
}
}
class Juicer {

// public void run(Apple a) {
// a.squeeze();
// }
//
// public void run(Orange o) {
// o.squeeze();
// }

// 改进后代码如下,
public void run(Fruit f) {
f.squeeze();
}
}

参构造创建对象

Constructor:Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数, 就不能这样创建了,可以调用Class类的getConstructor(String.class,int.class)方法获取一个指定的构造函数然后再调用Constructor类的newInstance("张三",20)方法创建对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test5 {
public static void main(String[] args) throws Exception {
// 获取字节码
Class<?> pClazz = Class.forName("com.demo.Person");
// 通过无参构造创建对象
// Person p = (Person) clazz.newInstance();
// System.out.println(p);
// 返射阶段,操作的都是字节码;获取有参构造
Constructor<?> c = pClazz.getConstructor(String.class, int.class);
Person p = (Person) c.newInstance("张三", 23); // 通过有参构造创建对象
System.out.println(p);
}
}

暴力反射操作属性

Field:Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField("name")方法获取,通过set(obj, "李四")方法可以设置指定对象上该字段的值,如果是私有的需要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test5 {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.demo.Person");
Constructor<?> c = clazz.getConstructor(String.class, int.class);
Person p = (Person) c.newInstance("李四", 19);
/*
* 报错:Person类的name私有,直接取用异常 //获取姓名字段 Field f = clazz.getField("name"); //获取性名的值
* f.set(p, "赵五");
*/
// 优化,通过暴利反射获取
Field f = clazz.getDeclaredField("name");
// 取访问时的检查;获取权限
f.setAccessible(true);
f.set(p, "赵五");
System.out.println(f.get(p));
System.out.println(p);
}
}

获取方法并调用

Method对象:Class.getMethod(String, Class...)Class.getDeclaredMethod(String, Class...)方法可以获取类中的指定方法,调用invoke(Object, Object...)可以调用该方法。
Class.getMethod(“eat”) invoke(obj) Class.getMethod(“eat”,int.class) invoke(obj,10)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Test5 {
public static void main(String[] args) {
try {
Class<?> clazz = Class.forName("com.demo.Person");
// 获取有参构造
Constructor<?> c = clazz.getConstructor(String.class, int.class);
// 通过有参构造创建对象
Person p = (Person) c.newInstance("张三", 23);
// 获取eat方法
Method m = clazz.getMethod("eat");
// 调用方法
m.invoke(p);
// 通过反射 获取有参的方法
Method m2 = clazz.getMethod("eat", int.class);
m2.invoke(p, 2);
} catch (Exception e) {
// TODO: handle exception
}
}
}

访问类属性和方法

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
package com.demo;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test9 {
public static void main(String[] args) throws Exception {
//因多态特性,反射不一定知道提前知道对象类型
Class<?> klass = People.class;
Object obj = klass.newInstance();
//获取klass对象类型
String klassType = klass.getTypeName();
System.out.println("getTypeName()获取klass对象类型:" + klassType);
System.out.println("---------------------------------------------------------------");

Field[] fields2 = klass.getFields();
for (Field field : fields2) {
System.out.println("getFields()获取公共的成员变量" + field);
}
System.out.println("---------------------------------------------------------------");
Field[] fields = klass.getDeclaredFields();
for (Field field : fields) {
System.out.println("getDeclaredFields()获取所有的成员变量:" + field);
}
System.out.println("---------------------------------------------------------------");
// 获取方法,传入参数
Method method = klass.getMethod("getP1", String.class, int.class);
// getMethods()方法获取的所有方法,包括继承的方法
Method[] methods = klass.getMethods();
for (Method m : methods) {
System.out.println("getMethods()获取所有包括继承的方法:" + m);
}
System.out.println("---------------------------------------------------------------");
// getDeclaredMethods()方法获取的所有自有方法,包括公供、保护、默认和私有的,不包括继承的方法
Method[] declaredMethods = klass.getDeclaredMethods();
for (Method m : declaredMethods) {
System.out.println("getDeclaredMethods()获取所有自有的方法:" + m);
}
System.out.println("---------------------------------------------------------------");
//获承方法并执行调用,传入方法名和参数类型的class对象
Method method2 = klass.getMethod("getP1", String.class, int.class);
//执行方法,传入对象和方法所需的参数
Object obj2 = method2.invoke(obj, "张三", 22);
System.out.println(obj2);
}
}

结果如下:

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
=====================以下为输出内容=======================================================
getTypeName()获取klass对象类型:com.demo.People
---------------------------------------------------------------
getFields()获取公共的成员变量public java.lang.String com.demo.People.nickName
---------------------------------------------------------------
getDeclaredFields()获取所有的成员变量:public java.lang.String com.demo.People.nickName
getDeclaredFields()获取所有的成员变量:private java.lang.String com.demo.People.realName
getDeclaredFields()获取所有的成员变量:private int com.demo.People.age
---------------------------------------------------------------
getMethods()获取所有包括继承的方法:public java.lang.String com.demo.People.toString()
getMethods()获取所有包括继承的方法:public java.lang.String com.demo.People.getP1(java.lang.String,int)
getMethods()获取所有包括继承的方法:public java.lang.String com.demo.People.getP2(java.lang.String,int)
getMethods()获取所有包括继承的方法:public final void java.lang.Object.wait() throws java.lang.InterruptedException
getMethods()获取所有包括继承的方法:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
getMethods()获取所有包括继承的方法:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
getMethods()获取所有包括继承的方法:public boolean java.lang.Object.equals(java.lang.Object)
getMethods()获取所有包括继承的方法:public native int java.lang.Object.hashCode()
getMethods()获取所有包括继承的方法:public final native java.lang.Class java.lang.Object.getClass()
getMethods()获取所有包括继承的方法:public final native void java.lang.Object.notify()
getMethods()获取所有包括继承的方法:public final native void java.lang.Object.notifyAll()
---------------------------------------------------------------
getDeclaredMethods()获取所有自有的方法:public java.lang.String com.demo.People.toString()
getDeclaredMethods()获取所有自有的方法:public java.lang.String com.demo.People.getP1(java.lang.String,int)
getDeclaredMethods()获取所有自有的方法:public java.lang.String com.demo.People.getP2(java.lang.String,int)
---------------------------------------------------------------
name= 张三; age= 22 -

越过泛型检查

ArrayList的一个对象,在这个集合中添加一个字符串数据,如何实现呢?
泛型只在编译期有效,在运行期会被擦除掉。而反射是运行阶段,立用反射绕过编译阶段的泛型检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.demo;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Test5 {
// 泛型只在编译期有效,在运行期会被擦除掉
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<>();
list.add(111);
list.add(222);
Class<?> clazz = Class.forName("java.util.ArrayList");
// 获取add方法
Method m = clazz.getMethod("add", Object.class);
m.invoke(list, "abc");
System.out.println(list);
}
}

通用方法设置值

public void setProperty(Object obj, String propertyName, Object value){},此方法可将obj对象中名为propertyName的属性的值设置为value

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.demo;
import java.lang.reflect.Field;
public class Test6 {
public void setProperty(Object obj, String propertyName, Object value) throws Exception {
// 获取字节码对象
Class clazz = obj.getClass();
// 暴力反射获取字段
Field f = clazz.getDeclaredField(propertyName);
// 去除权限
f.setAccessible(true);
f.set(obj, value);
}
}

Java基础:Reflection 反射机制理解

http://blog.gxitsky.com/2018/03/29/Java-jdk-7-reflect/

作者

光星

发布于

2018-03-29

更新于

2022-06-17

许可协议

评论