反射机制原理及使用

最近在研究蓝牙源码时发现,有部分类是被 @hide 掉的,例如 通过蓝牙获取通讯录及通话记录,是通过蓝牙 Pbap 协议,具体实现是在 BluetoothPbapClient 中实现,而该类是 hide 的,注释为当前未完成,后期变动可能会比较大,所以hide掉不会放在 sdk 里使用。
这种情况下,一般是通过修改源码重新编译来实现该需求,不过我为了快速完成需求,考虑使用反射来调用 BluetoothPbapClient 中的接口。
在此记录下反射的使用及原理。

反射,是能够在程序运行时修改程序的行为。

反射的使用

Class

获取 Class

Class 就是一个对象,它用来代表运行在 Java 虚拟机中的类和接口。

获取三种的方式:

1
2
3
4
5
student.getClass();

Student.class;

Class.forName("com.xx.Student");

获取 Class 修饰符

1
2
TestModifier.class.getModifiers()  // 1025
Modifier.toString(TestModifier.class.getModifiers())) // public abstract

Field

获取 Filed

1
2
3
4
5
6
7
// 获取当前类所有的成员变量,包括私有或者非私有的,不包括从祖先类继承的非私有成员变量
getDeclaredFields();
getDeclaredField(String name);

// 获取所有当前类及从祖先类继承的非私有成员变量
getFields();
getField(String name);

获取 Field 的类型

1
2
3
4
// 该方法可以获取到泛型类型
getGenericType()

getType()

获取Field 修饰符

1
2
// 同 Class 获取修饰符
getModifiers()

Field 内容的读取与赋值

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
// get
public Object get(Object obj);

public int getInt(Object obj);

public long getLong(Object obj)
throws IllegalArgumentException, IllegalAccessException;

public float getFloat(Object obj)
throws IllegalArgumentException, IllegalAccessException;

public short getShort(Object obj)
throws IllegalArgumentException, IllegalAccessException;

public double getDouble(Object obj)
throws IllegalArgumentException, IllegalAccessException;

public char getChar(Object obj)
throws IllegalArgumentException, IllegalAccessException;

public byte getByte(Object obj)
throws IllegalArgumentException, IllegalAccessException;

public boolean getBoolean(Object obj)
throws IllegalArgumentException, IllegalAccessException

// set
public void set(Object obj, Object value);

public void getInt(Object obj,int value);

public void getLong(Object obj,long value)
throws IllegalArgumentException, IllegalAccessException;

public void getFloat(Object obj,float value)
throws IllegalArgumentException, IllegalAccessException;

public void getShort(Object obj,short value)
throws IllegalArgumentException, IllegalAccessException;

public void getDouble(Object obj,double value)
throws IllegalArgumentException, IllegalAccessException;

public void getChar(Object obj,char value)
throws IllegalArgumentException, IllegalAccessException;

public void getByte(Object obj,byte b)
throws IllegalArgumentException, IllegalAccessException;

public void getBoolean(Object obj,boolean b)
throws IllegalArgumentException, IllegalAccessException

注:在反射中访问了private修饰的成员,需要添加访问权限:

1
fieldb.setAccessible(true);

Method

获取 Method

1
2
3
4
5
getDeclaredMethods()
getDeclaredMethod(String name, Class<?>... parameterTypes)

getMethods()
getMethod(String name, Class<?>... parameterTypes)

获取方法参数

1
2
3
4
5
6
7
8
9
10
public Parameter[] getParameters() {}
public Class<?>[] getParameterTypes() {}
public Type[] getGenericParameterTypes() {}

Parameter.java:
public String getName() {}

public Class<?> getType() {}

public int getModifiers() {}

获取返回值类型

1
2
3
public Class<?> getReturnType() {}
// 获取返回值类型包括泛型
public Type getGenericReturnType() {}

获取修饰符

1
2
// 同 Class
public int getModifiers() {}

获取异常类型

1
2
public Class<?>[] getExceptionTypes() {}
public Type[] getGenericExceptionTypes() {}

方法执行

1
public Object invoke(Object obj, Object... args) {}

Constructor

获取 Constructor

注:Constructor 不能从父类继承,所以就没有办法通过 getConstructor() 获取到父类的 Constructor。

1
2
3
4
5
getDeclaredConstructors()
getDeclaredConstructor(Class<?>... parameterTypes)

getConstructors()
getConstructor(Class<?>... parameterTypes)

获取对象

在 Java 反射机制中有两种方法可以用来创建类的对象实例:Class.newInstance() 和 Constructor.newInstance()。官方文档建议开发者使用后面这种方法,下面是原因。

Class.newInstance() 只能调用无参的构造方法,而 Constructor.newInstance() 则可以调用任意的构造方法。
Class.newInstance() 通过构造方法直接抛出异常,而 Constructor.newInstance() 会把抛出来的异常包装到 InvocationTargetException 里面去,这个和 Method 行为一致。
Class.newInstance() 要求构造方法能够被访问,而 Constructor.newInstance() 却能够访问 private 修饰的构造器。

反射的原理

java让我们在运行时识别对象和类的信息,主要有2种方式:一种是传统的RTTI(Run-Time Type Identification),它假定我们在编译时已经知道了所有的类型信息,另一种是反射机制。

它允许我们在运行时发现和使用类的信息,也就是这里要说的,通过 Class 类与 reflect 类库一起对反射进行支持,该类库包括 Field、Method 和 Constructor 类。这些类的对象由JVM在启动时创建,用以表示未知类里对应的成员。这样可以使用 Contructor 创建新的对象,用 get() 和 set() 方法获取、修改类中成员等,用 invoke() 调用 Method 对象关联的方法等等。

这样对象信息可以在运行时完全确定下来,而在编译时不需要知道任何关于类的信息。

反射机制并没有很神奇之处,当通过反射对一个未知类型的对象做处理时,JVM只是简单的检查这个对象属于哪个特定的类,因此那个类的 .class 对于JVM是肯定可以获取的。

综上,对于 RTTI 和 反射的区别只在于:

  • RTTI 是编译器在编译时打开和检查 .class 文件
  • 反射 是在运行时打开和检查 .class 文件