b1cat`s

Java反射

Word count: 1.2kReading time: 4 min
2020/02/13

Java反射

反射就是Reflection,Java反射是指程序在运行期可以拿到一个对象的所有信息。反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用期方法。

Class类

class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class,将其加载进内存中,每加载一种class, JVM就为其创建一个Class类型的实例,并关联起来。

  • Class类
1
2
3
public final class Class{
private Class(){}
}
- 以`String`类为例,当JVM加载`String`时,首先读取String。class文件到内存,然后JVM为String类创建一个Class实例并关联起来:

Class cls = new Class(String);

- 这个Class实例是由JVM并且只能有JVM创建。
  • 一个Class实例包含了该class的所有完整信息:包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。

    image-20200213181834851

这种通过Class实例获取class信息的方法称为反射(Reflection)。

  • 如何获取一个class的Class实例:

    • 1.直接通过一个class的静态变量获取Class cls = String.class;
    • 2.通过实例变量提供的getClass()方法获取Class.cls = s.getClass();
    • 3.如果知道一个class名字,可以通过静态方法Class.forName()获取:Class cls = Class.forName("java.lang.String");
  • Class实例比较

    • 由于JVM创建的Class实例是唯一的,通过上述三个方法获取的实例均是相同的
  • 创建Class对应类型的实例:

    • newInstance相当于new String()
    • 只能调用public的无参数构造方法
1
2
Class cls = String.class; 
String s = (String) cls.newInstance();
  • 动态加载
    • JVM在执行Java程序的时候,并不是一次性把所有用到的class加载到内存,而是第一次用到class时才加载。
    • 动态加载class的特性对于Java程序非常重要。利用JVM动态加载class的特性,我们才能在运行期根据条件加载不同的实现类。

访问字段

  • Class类提供了几个方法来获取字段:

    • getFiled(name)获取包括父类的public类中的filed
    • getDeclaredFiled(name)获取当前类的filed
    • getFileds()获取所有public的filed
    • getDeclaredFileds()获取当前类的所有filed
  • Filed字段包含getName()字段名臣,getType()字段类型,getModifiers()修饰符

  • 获取字段实例后,想要获取指定字段值

    • Filed.get(Object)Object value = f.get("name");
    • 如果该字段被修饰为private,可修改作用域f.setAccessible(true);
  • 设置字段值

    • Filed.set(Object name, Object value)
    • 如果不可访问,需修改作用域

调用方法

  • Class几种方法获取Method

    • getMethod(name, Class...)获取某个public的Method(包含父类)
    • getDecleardMethod(name, Class...)获取当前类的Method
    • getMethods()获取所有public的method(包括父类)
    • getDecleardMethods()获取当前类所有的Method
  • 一个Method对象包含一个方法的所有信息

    • getName()
    • getReturnType() 方法返回值类型
    • getParameterTypes() 返回方法的参数类型,数组{String.class, int.class}
    • getModifiers() 方法的修饰符,是一个int
  • 调用方法 m.invoke(Class clazz, para)

  • 调用静态方法:m.invoke(null, para)

  • 调用非public方法,需设定Method.setAccessible(true)修改作用域

    • JVM如果存在SecurityManager,有可能会阻止
    • 如java和javax开头的包类调用,会被阻止确保核心库安全
  • 多态:遵循多态特性

调用构造方法

通过newInstance只能实例public的无参构造方法,而其他的不可以。
为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。调用方法如下

  • getConstructor(Class...):获取某个public的Constructor;
  • getDeclaredConstructor(Class...):获取某个Constructor;
  • getConstructors():获取所有public的Constructor;
  • getDeclaredConstructors():获取所有Constructor。

返回依然是一个实例。

  • 调用的总是当前的构造方法,和父类无关
  • 调用非public的构造方法,需要setAccessible(true)修改作用域

获取继承关系

可通过getSupperclass()获取父类
可通过getInterfaces()获取Interface
通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。

动态代理

Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以再运行期间动态创建某个interface的实例,

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler(){ //负责实现接口方法调用
@override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println(method);
if(method.getName().equals('morning')){
System.out.println("Good monring," +args[0]);
}
return null;
}
};
//创建interface实例
//将返回类型强制转为接口
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(),//ClassLoader
new Class[] {Hello.class}, //要实现的接口
handler); //传入实现的调用方法

hello.morning("Bob");
}

interface Hello{
void morning(String name);
}
CATALOG
  1. 1. Java反射
    1. 1.0.1. Class类
    2. 1.0.2. 访问字段
    3. 1.0.3. 调用方法
    4. 1.0.4. 调用构造方法
    5. 1.0.5. 获取继承关系
    6. 1.0.6. 动态代理