Java反射机制

  |  

反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。

关于反射

反射最大的作用之一就在于我们可以不在编译时知道某个对象的类型,而在运行时通过提供完整的”包名+类名.class”得到。注意:不是在编译时,而是在运行时。利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员。

  • 功能:
    • 在运行时能 判断 任意一个 对象所属的类
    • 在运行时能 构造 任意一个 类的对象
    • 在运行时 判断 任意一个 类所具有的成员变量和方法
    • 在运行时 调用 任意一个 对象的方法

说大白话就是,利用Java反射机制我们可以加载一个运行时才得知名称的class,获悉其构造方法,并生成其对象实体,能对其fields设值并唤起其methods。

  • 应用场景:

    反射技术常用在各类通用框架开发中。 因为为了保证框架的通用性,需要根据配置文件加载不同的对象或类,并调用不同的方法,这个时候就会用到反射——运行时动态加载需要加载的对象。

  • 特点:

    由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。


扩展: Accessible

反射提供的 AccessibleObject.setAccessible​(boolean flag)。 它的子类也大都重写了这个方法,这里的所谓 accessible 可以理解成修饰成员的 public、protected、private,这意味着我们可以在运行时修改成员访问限制!

setAccessible 的应用场景非常普遍,遍布我们的日常开发、测试、依赖注入等各种框架中。比如,在 O/R Mapping 框架中,我们为一个 Java 实体对象,运行时自动生成 setter、getter 的逻辑,这是加载或者持久化数据非常必要的,框架通常可以利用反射做这个事情,而不需要开发者手动写类似的重复代码。

另一个典型场景就是 绕过 API 访问控制。我们日常开发时可能被迫要调用内部 API 去做些事情,比如,自定义的高性能 NIO 框架需要显式地释放 DirectBuffer,使用反射绕开限制是一种常见办法。

但是,在 Java 9 以后,这个方法的使用可能会存在一些争议,因为 Jigsaw 项目新增的模块化系统,出于强封装性的考虑,对反射访问进行了限制。 Jigsaw 引入了所谓 Open 的概念,只有当被反射操作的模块和指定的包对反射调用者模块 Open,才能使用 setAccessible ;否则,被认为是不合法(illegal)操作。如果我们的实体类是定义在模块里面,我们需要在模块描述符中明确声明:

1
2
3
4
module MyEntities {
// Open for reflection
opens com.mycorp to java.persistence;
}

因为反射机制使用广泛,根据社区讨论,目前,Java 9 仍然保留了兼容 Java 8 的行为,但是 很有可能在未来版本,完全启用前面提到的针对 setAccessible 的限制,即只有当被反射操作的模块和指定的包对反射调用者模块 Open,才能使用 setAccessible,我们可以使用下面参数显式设置。

1
--illegal-access={ permit | warn | deny }

反射的操作

  • 反射需要知道的对象

    • Constructor :构造方法类,每一个构造方法都是一个Constructor类的对象。

    • Method :成员方法类,每一个成员方法都是一个Method类的对象。

    • Field :成员变量类,每一个成员变量都是一个Field类的对象

    • instance :实例,就是对象

    • nvoke :调用,执行

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
class Person{
private String name;
private String age;
public Person(String name, String age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}

反射之操作构造方法

Class类与构造方法相关的方法

  1. Constructor[] getConstructors()

    • 获得所有的构造方法,返回数组
    • 只包含非private修饰的
  2. Constructor[] getDeclaredConstructors()

    • 获得所有的构造方法,返回数组,包括private修饰的
  3. Constructor getConstructor(Class… parameterTypes)

    • 根据参数类型获得对应的构造方法对象
    • 只能获得非private修饰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws Exception {
//获得Person的字节码class对象
Class c=Person.class;

//获得无参数构造器
Constructor con=c.getConstructor();

//通过无参数构造器创建对象
Object newInstance = con.newInstance();
System.out.println(newInstance);//Person [name=null, age=null]

//获得有参数构造器
Constructor con2=c.getConstructor(String.class,String.class);

//通过有参数构造器创建对象
Object newInstance2 = con2.newInstance("张三","20");
System.out.println(newInstance2);//Person [name=张三, age=20]
}
  1. Constructor getDeclaredConstructor(Class… parameterTypes)

    • 根据参数类型获得对应的构造方法对象,包括private修饰的
  2. public void setAccessible(boolean flag)

    • 暴力反射,设置是否取消权限检查,默认是false
    • flag=true,表示取消权限检查
    • flag=false,表示不取消权限检查
  • T newInstance(Object…initargs)
    • Constructor类的成员方法,根据参数实例化创建对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws Exception {
//把前面的Person的有参数构造方法改为private
//获得Person的字节码class对象
Class c=Person.class;

//获得有参数构造器
Constructor con2=c.getDeclaredConstructor(String.class,String.class);

//暴力反射
/*
如果不改变权限会报出下面异常
java.lang.IllegalAccessException: class com.day_17.demo7.Person with modifiers "private"
*/
con2.setAccessible(true);

//通过有参数构造器创建对象
Object newInstance2 = con2.newInstance("张三","20");
System.out.println(newInstance2);//Person [name=张三, age=20]
}

反射之操作成员方法

Person类

1
2
3
4
5
class Person{
public void run(String a){
System.out.println(a);
}
}

Class类与成员方法相关的方法

  1. public Method[] getMethods()

    • 获得类所有的成员方法对象,包括父类的,返回数组
    • 不包含private修饰
  2. public Method[] getDeclaredMethods()

    • 获得类所有的成员方法对象,不包括父类的,返回数组
    • 包含private修饰
  3. public Method getMethod(String name, Class… parameterTypes)

    • 根据方法名和参数类型获得成员方法对象,不包含private
    • name:方法的名字
    • parameterTypes:根据方法的参数列表创建的Class对象(多少个数据类型就创建多少个Class)
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) throws Exception {
//获得Person字节码文件的Class对象
Class c=Person.class;

//获得Person的成员方法
Method m=c.getMethod("run",String.class);

//通过Class创建一个无参数构造的Person的对象
Object newInstance = c.newInstance();

//执行方法
m.invoke(newInstance,"hello");//hello
}
  1. public Method getDeclaredMethod(String name, Class… parameterTypes)
    • 根据方法名和参数类型获得成员方法对象,包括private的
  • Method类的成员方法

    • Object invoke(Object obj, Object… args)
      • 执行方法
      • obj:调用的是哪个对象的方法。
      • args:调用方法时要传递的参数
      • 返回调用方法执行的结果
  • Class类快速创建对象的方法

    • T newInstance()
      • 前提:该类必须有一个public的无参数构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) throws Exception {
//把前面的run方法改为private
//获得Person字节码文件的Class对象
Class c=Person.class;

//获得Person的成员方法
Method m=c.getDeclaredMethod("run",String.class);

//暴力反射
/*
如果不改变权限会报出下面异常
java.lang.IllegalAccessException: class com.day_17.demo7.Person with modifiers "private"
*/
m.setAccessible(true);
//通过Class创建一个无参数构造的Person的对象
Object newInstance = c.newInstance();

//执行方法
m.invoke(newInstance,"hello");//hello
}

反射之操作成员变量

Person类

1
2
3
4
5
6
7
8
class Person{
public String name;
private String age;
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}

Class类与成员变量相关的方法

  1. Field[] getFields();

    • 获得所有的成员变量,不包含private
  2. Field[] getDeclarerdFields();

    • 获得所有成员变量,包含private
  3. Field getField(String name);

    • 根据名称获得对应的成员变量对象,不包含private的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) throws Exception {
//获得Person的字节码Class对象
Class c=Person.class;

//获得Person的public成员变量name
Field field=c.getField("name");

//通过Class对象创建一个无参数构造的Person对象
Object obj = c.newInstance();

//给对象的name赋值
field.set(obj, "张三");
System.out.println(obj);//Person [name=张三, age=0]
}
  1. Field getDeclaredField(String name);
    • 根据名称获得对应的成员变量对象,包含private的

Field类成员方法

  • void setXxx(Xxx value);

    • 给对应的成员变量设置为指定的值value
    • xxx就是数据类型,
  • public void set(Object obj, Object value)

    • 给指定对象obj对应的成员变量赋值为value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) throws Exception {
//获得Person的字节码Class对象
Class c=Person.class;

//获得Person的private成员变量age
Field field=c.getDeclaredField("age");

//暴力反射
/*
如果不改变权限会报出下面异常
java.lang.IllegalAccessException: class com.day_17.demo7.Person with modifiers "private"
*/
field.setAccessible(true);

//通过Class对象创建一个无参数构造的Person对象
Object obj = c.newInstance();

//给对象的name赋值
field.setInt(obj, 20);
System.out.println(obj);//Person [name=null, age=20]
}

案例

1
ArrayList<Integer> list = new ArrayList<Integer>();

在这个泛型为 Integer 的 ArrayList 中存放一个 String 类型的对象。

泛型只是在编译器起作用,在字节码表中类型还是Object,运行的时候才变成泛型规定的类型

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
public static void main(String[] args) throws Exception {
//创建一个ArrayList对象
ArrayList<Integer> list=new ArrayList<>();

//添加Integer元素
list.add(11);
list.add(2);

//获得list的class对象
Class c=list.getClass();

//通过class生成一个新的ArrayList对象
Object newInstance = c.newInstance();

//获得ArrayList的add方法
Method m=c.getMethod("add",Object.class);

//添加元素到newInstance集合中
m.invoke(newInstance, "newInstance");
//添加元素到list集合中
m.invoke(list, "list");

System.out.println(list);//[11, 2, list]
System.out.println(newInstance);//[newInstance]
}

Copyright © 2018 - 2020 Kuanger All Rights Reserved.

访客数 : | 访问量 :