反射三步曲之一介绍反射和Class

前言

在日常android开发中,为了使用更高级的功能,做一些正常开发中做不到的事情,我们通常会用到反射。然而不管是findViewById()还是使用Gson()反射也真实的伴随着我们。

但是搜罗全网,很难找到一篇很好介绍反射的中文文章,这个艰巨的任务就落在了我的头上,下面,让我们一起探索反射的奥秘。

反射是什么?

反射用于需要能够检查或修改在Java虚拟机中运行的应用程序的运行时行为,是一个相对高级的功能,只有那些掌握了语言基础知识的开发人员才能使用。反射的作用有以下几点:

  • 扩展性:可以通过完全限定名创建可扩展对象的实例
  • 类浏览器可视化开发:类浏览器可以枚举类的私有成员,可以帮助开发者更好的写代码
  • 调试和测试工具:调试工具能够检测类上的私有成员。测试工具可以系统的调用类上定义的PAI。确保测试覆盖率

反射的缺点

反射是很强大的,但切忌不可随意使用,能不用反射尽量不要用。如果使用反射的话,请参考反射的缺点来看反射适用不适用于自己:

  • 开销问题

    因为反射是动态解析类型,虚拟机不做性能优化,因此反射的性能比不用反射的性能要低,应该避免在主要功能上频繁的调用反射。

  • 安全限制:反射需要运行时权限,在安全的管理器下可能不允许这么做。那么需要考虑到代码是否运行在安全限制环境下,如Applet

  • 内部暴露:反射允许执行正常代码下的非法操作,如访问private字段,可能使代码结构崩塌或者是破坏可移植性,反射也打破了抽象,可能升级一个SDK版本,代码也就失效了。

下面我们就从类,字段,方法,构造函数,数组,枚举各个环节来查看反射的奥秘。

Class

每种类型不是引用就是基原primitive,不管是类、枚举、还是数组引用类型,他们都继承于Object,原始类型有booleanbyteshortintlongcharfloat,和double

对于每种类型的对象,Java虚拟机都实例化一个不可变实例,java.lang.Class,该类提供了检车对象运行时属性的方法,还提供了创建新的对象的方法,这是所有反射的切入点。

如何获取Class

Object.getClass()

  • 可以通过调用Object.getClass()来获取该类,这只适用于从Object继承的引用类型:例如
1
Class c = "foo".getClass();
  • 从string中获取class
1
Class c = System.console().getClass();
  • 获取枚举
1
2
enum E { A, B }
Class c = A.getClass();
<div class = "note success">A是一个列举的实例,getclass()返回枚举类型E</div>
  • 数组
1
2
byte[] bytes = new byte[1024];
Class c = bytes.getClass();
array也是一个Object,通过getclass()返回一个数组的实例byte。
1
2
3
4
5
import java.util.HashSet;
import java.util.Set;

Set<String> s = new HashSet<String>();
Class c = s.getClass();
同样的返回的是一个java.util.HashSet

.class 语法

如果类型可用,但是没有实例的话用getClass()是回报错的!我们可以用.class语句来获取当前Class

1
2
3
4
boolean b;
Class c = b.getClass(); // 编译器报错

Class c = boolean.class;
基本类型是不能够取消引用的dereference,因此getClass()会报错,我们必须用.class

另一种情况,非基本类型,如果没有实例的话我们可以直接用包名.class来拿到当前类Class

1
Class z = java.util.HashSet.class;

Class.forName()

如果知道全限定名,那么我们可以用静态方法Class.forName()来获取Class对象,同样的我们不能用这个方法获取基本类型的Class。

1
Class c = Class.forName("com.duke.MyLocaleServiceProvider");

全限定名的例子

有关全限定名字的定义请看我另一篇介绍JNI的文章
1
2
3
Class cDoubleArray = Class.forName("[D");

Class cStringArray = Class.forName("[[Ljava.lang.String;");

对于数组类型获取Class的方式叫做Class.getName(),这个方法适用于引用类型和基本类型。

getName() getSimpleName() getcan的去区别

我们试着访问一个内部类

  • getName()
1
lab.mon.actlab.java.reflect.FieldModifierSpy$Inner
  • getSimpleName()
1
Inner
  • getCanonicalName()
1
lab.mon.actlab.java.reflect.FieldModifierSpy.Inner

基本类型包装的TYPE字段

从前面的介绍可以看到,用.class去获取基本类型的Class是非常方便的。Java还提供了另一种方法去获取基本类型的Class,那就是包装类,每一种基本类型还有Void都在java.long中有一个包装类,用于将基本类型包装为引用类型,每个包装类中包含一个字段TYPE等于基本类型的Class。

1
Class c = Double.TYPE;
1
Class c = Void.TYPE;

其他返回Class的方法

在反射的API中有几个方法也可以获取Class,前提是已经直接或是间接的拿到了Class。

方法如下:

顾名思义就是拿类的父类了
1
2
Class c = javax.swing.JButton.class.getSuperclass();
//c是javax.swing.JButton的父类javax.swing.AbstractButton.
返回所有用public修饰的成员变量,包括类、接口、枚举等。包括子类的

我们来举个栗子:新建一个activity然后里面声明了类、接口、枚举。

1
2
3
4
5
6
7
8
9
10
11
12
public class ReflectActivity extends AppCompatActivity {
public int reflectInt = 1;
public Integer reflectObjectInt = Integer.valueOf(1);
public enum REFLECTENUM {
FIRST, SECEND
}
public interface ReflectInter {
void getString();
}
class ReFelctInner {
}
}

然后在resume()中执行方法

1
Class<?>[] classes = lab.mon.actlab.java.reflect.ReflectActivity.class.getClasses();

后拿到成员变量

image-20190125173941145

可以看到Class数组里面有五个成员,其中三个是咱们自己声明的,两个是Activity继承来的。

获取当前类的成员变量,包括类、接口、枚举等。包括私有的成员变量

注意这里没有public限制,私有的成员变量也可以拿到

1
Class<?>[] classes = lab.mon.actlab.java.reflect.ReflectActivity.class.getDeclaredClasses();
如果当前的类或者接口是一个在一个类中声明,则拿到声明它的类
1
2
3
4
public Class getDeclaredclass() {
Class classes = lab.mon.actlab.java.reflect.ReflectActivity.ReFelctInner.class.getDeclaringClass();
return classes;
}

获取声明当前字段的类

1
2
3
import java.lang.reflect.Field; 
Field f = lab.mon.actlab.java.reflect.ReflectActivity.class.getField("reflectObjectInt");
class c = f.getDeclaringClass();

获取声明当前方法的类

获取声明当前方法的类

匿名类没有声明其的类,但是有一个封闭类
1
2
3
4
5
6
public class MyClass {
static Object o = new Object() {
public void m() {}
};
static Class<c> = o.getClass().getEnclosingClass();
}

检查类修饰符与类型

类运行时的可能声明一个或多个修饰符

访问修饰符:public,protected ,private

重写修饰符:abstract

限定实例的修饰符:static

禁止修改修饰符:final

严格浮点修饰符:strictfp

但是并非所有的类都允许使用全部的修饰符,如final接口不能使用abstract,在java.lang.reflect.Modifier中包含了所有的修饰符。

那么要获取类的信息我们来举个栗子,首先将Activit进行改造

1
2
@Deprecated
public class ReflectActivity<T,V> extends AppCompatActivity implements View.OnClickListener {

然后我们写下这段代码然后传入ReflectActivity的全限定名。

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
public void ClassDeclarationSpy(String... args) {
try {
//先拿到数组中第0个的类型c
Class<?> c = Class.forName(args[0]);
out.format("Class:%n %s%n%n", c.getCanonicalName());
//获取这个类型的访问修饰符
out.format("Modifiers:%n %s%n%n", Modifier.toString(c.getModifiers()));

out.format("Type Parameters:%n");
//获取泛型
TypeVariable[] tv = c.getTypeParameters();
if (tv.length != 0) {
out.format(" ");
for (TypeVariable t : tv)
out.format("%s ", t.getName());
out.format("%n%n");
} else {
out.format(" -- No Type Parameters --%n%n");
}

out.format("Implemented Interfaces:%n");
//获取实现的接口
Type[] intfs = c.getGenericInterfaces();
if (intfs.length != 0) {
for (Type intf : intfs)
out.format(" %s%n", intf.toString());
out.format("%n");
} else {
out.format(" -- No Implemented Interfaces --%n%n");
}
//迭代循环获取继承关系,不断获取父类
out.format("Inheritance Path:%n");
List<Class> l = new ArrayList<Class>();
printAncestor(c, l);
if (l.size() != 0) {
for (Class<?> cl : l)
out.format(" %s%n", cl.getCanonicalName());
out.format("%n");
} else {
out.format(" -- No Super Classes --%n%n");
}
//获取类的注解,注意是类中的注解
out.format("Annotations:%n");
Annotation[] ann = c.getAnnotations();
if (ann.length != 0) {
for (Annotation a : ann)
out.format(" %s%n", a.toString());
out.format("%n");
} else {
out.format(" -- No Annotations --%n%n");
}

// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}

得到输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
I: Class:
I: lab.mon.actlab.java.reflect.ReflectActivity
I: Modifiers:
I: public
I: Type Parameters:
I: T V
I: Implemented Interfaces:
I: interface android.view.View$OnClickListener
I: Inheritance Path:
I: android.support.v7.app.AppCompatActivity
I: android.support.v4.app.FragmentActivity
I: android.support.v4.app.SupportActivity
I: android.app.Activity
I: android.view.ContextThemeWrapper
I: android.content.ContextWrapper
I: android.content.Context
I: java.lang.Object
I: Annotations:
I: @java.lang.Deprecated()

获取类的成员

获取类的字段,方法和构造函有两个方法,枚举这些方法和搜索特定的方法。也就是搜索直接声明在当前类或者继承类的成员。下面来介绍一下获取成员的方法和特征:

  • 对于字段
Class API 成员列表 成员继承 Private 成员
getDeclaredField() no no yes
getField() no yes no
getDeclaredFields() yes no yes
getFields() yes yes no
  • 对于方法
Class API 成员列表 成员继承 Private 成员?
getDeclaredMethod() no no yes
getMethod() no yes no
getDeclaredMethods() yes no yes
getMethods() yes yes no
  • 对于构造
Class API 成员列表 成员继承 Private 成员?
getDeclaredConstructor() no 不适用 yes
getConstructor() no not applicable no
getDeclaredConstructors() yes N/A yes
getConstructors() yes N/A no

一下代码可以打印出当前类的大部分信息

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
78
79
80
package lab.mon.actlab.java.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Member;
import static java.lang.System.out;

enum ClassMember { CONSTRUCTOR, FIELD, METHOD, CLASS, ALL }

public class ClassSpy {
public void classSpy(String... args) {
try {
//获取类名
Class<?> c = Class.forName(args[0]);
out.format("Class:%n %s%n%n", c.getCanonicalName());
//获取包名
Package p = c.getPackage();
out.format("Package:%n %s%n%n", (p != null ? p.getName() : "-- No Package --"));

for (int i = 1; i < args.length; i++) {
switch (ClassMember.valueOf(args[i])) {
case CONSTRUCTOR:
//获取构造函数
printMembers(c.getConstructors(), "Constructor");
break;
case FIELD:
//获取当前类的字段
printMembers(c.getDeclaredFields(), "Fields");
break;
case METHOD:
//获取当前类的方法
printMembers(c.getDeclaredMethods(), "Methods");
break;
case CLASS:
//获取当前类的类,接口,枚举
printClasses(c);
break;
case ALL:
printMembers(c.getConstructors(), "Constuctors");
printMembers(c.getDeclaredFields(), "Fields");
printMembers(c.getDeclaredMethods(), "Methods");
printClasses(c);
break;
default:
assert false;
}
}

} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}

//打印成员变量
private static void printMembers(Member[] mbrs, String s) {
out.format("%s:%n", s);
for (Member mbr : mbrs) {
if (mbr instanceof Field)
out.format(" %s%n", ((Field)mbr).toGenericString());
else if (mbr instanceof Constructor)
out.format(" %s%n", ((Constructor)mbr).toGenericString());
else if (mbr instanceof Method)
out.format(" %s%n", ((Method)mbr).toGenericString());
}
if (mbrs.length == 0)
out.format(" -- No %s --%n", s);
out.format("%n");
}
//打印类成员
private static void printClasses(Class<?> c) {
out.format("Classes:%n");
Class<?>[] clss = c.getClasses();
for (Class<?> cls : clss)
out.format(" %s%n", cls.getCanonicalName());
if (clss.length == 0)
out.format(" -- No member interfaces, classes, or enums --%n");
out.format("%n");
}
}

我们在Actiivty中执行下面这段代码

1
classSpy.classSpy("lab.mon.actlab.java.reflect.ReflectActivity",ClassMember.ALL.name());

最终会得到

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
I: Class:
I: lab.mon.actlab.java.reflect.ReflectActivity
I: Package:
I: lab.mon.actlab.java.reflect
I: Constuctors:
I: public lab.mon.actlab.java.reflect.ReflectActivity()
I: Fields:
I: public int lab.mon.actlab.java.reflect.ReflectActivity.reflectInt
I: public java.lang.Integer lab.mon.actlab.java.reflect.ReflectActivity.reflectObjectInt
I: public java.util.HashSet<java.lang.String> lab.mon.actlab.java.reflect.ReflectActivity.reflectSet
I: public static transient volatile com.android.tools.ir.runtime.IncrementalChange lab.mon.actlab.java.reflect.ReflectActivity.$change
I: public static java.lang.Object lab.mon.actlab.java.reflect.ReflectActivity.reflectObject
I: public static final long lab.mon.actlab.java.reflect.ReflectActivity.serialVersionUID
I: Methods:
I: public static java.lang.Object lab.mon.actlab.java.reflect.ReflectActivity.access$super(lab.mon.actlab.java.reflect.ReflectActivity,java.lang.String,java.lang.Object...)
I: public java.lang.Class<?>[] lab.mon.actlab.java.reflect.ReflectActivity.getAllMember()
I: public java.lang.Class<?>[] lab.mon.actlab.java.reflect.ReflectActivity.getDeclaredMember()
I: public java.lang.Class lab.mon.actlab.java.reflect.ReflectActivity.getDeclaredclass()
I: public java.lang.Class lab.mon.actlab.java.reflect.ReflectActivity.getFiledClass() throws java.lang.NoSuchFieldException
I: public java.lang.Class lab.mon.actlab.java.reflect.ReflectActivity.getReflect()
I: public void lab.mon.actlab.java.reflect.ReflectActivity.onClick(android.view.View)
I: public void lab.mon.actlab.java.reflect.ReflectActivity.onCreate(android.os.Bundle)
I: public void lab.mon.actlab.java.reflect.ReflectActivity.onResume()
I: Classes:
I: lab.mon.actlab.java.reflect.ReflectActivity.ReFelctInner
I: lab.mon.actlab.java.reflect.ReflectActivity.REFLECTENUM
I: android.support.v4.app.SupportActivity.ExtraData
I: android.app.Activity.TranslucentConversionListener

同样的,我们替换换了方法,那么打印的信息就是当前方法的信息

典型的错误

使用了UNcheck或者是不安全的操作

当方法调用的时候,参数的类型会进行检查并且尽可能的转换,但是有事会出现一些转换错误,比如:

1
2
3
4
5
6
7
8
9
10
public class ClassWarning {
void m() {
try {
Class c = ClassWarning.class;
Method m = c.getMethod("m");
} catch (NoSuchMethodException x) {
x.printStackTrace();
}
}
}

这里会出现错误,为什么呢?

这是因为由于c被声明为原始类型(即没有类型参数),但是getMethod()的相应参数是参数化类型,那么就会出现错误,

有两个解决方案

  • 将class设置为泛型
1
Class<?> c = warn.getClass();
  • 增加SuppressWarnings注解
1
2
3
Class c = ClassWarning.class;
@SuppressWarnings("unchecked")
Method m = c.getMethod("m");

构造函数不可访问异常InstantiationException

举例说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Cls {
private Cls() {}
}

public class ClassTrouble {
public static void main(String... args) {
try {
Class<?> c = Class.forName("Cls");
c.newInstance();
// production code should handle these exceptions more gracefully
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}

当执行c.newInstance()的时候会抛出InstantiationException异常,这是为什么呢?

这是因为Cls的构造函数是私有的,而Class.newInstance()的行为和new的行为非常相似,所以会抛出异常。

解决方案:

反射中的典型解决方案是利用java.lang.reflect.AccessibleObject类,因为它提供了抑制访问控制检查的能力。但是这里并不生效,因为java.lang.Class并不继承于AccessibleObject.,唯一的解决方案是修改代找到Constructor并用继承于AccessibleObject.的Constructor.newInstance()

谢谢您的鼓励~