反射三步曲之二反射字段、方法、构造函数

反射成员

在上一节中,我们介绍了反射的Class的获取以及Field,method,construct的获取,在我们拿到这些信息后如何修改呢?

下面我将会和大家一起探索反射后信息的使用。

字段(Field)

什么是字段?

字段是一个类,接口或者枚举和其相关的值,java.lang.reflect.Field中相关的方法可以取到字段的信息,例如名字、类型、修饰符和注解。还有一些方法可以实现动态访问,和修改字段的值。

获取字段的类型

一个字段可能是基本类型也可能是引用类型,前文也介绍了Java有八种基本类型boolean、byte、short、int 、long、char、float、double,引用类型呢?它是直接或者间接继承了java.lang.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
26
27
28
import java.lang.reflect.Field;
import java.util.List;

public class FieldSpy<T> {
public boolean[][] b = {{ false, false }, { true, true } };
public String name = "Alice";
public List<Integer> list;
public T val;

public static void main(String... args) {
try {
//获取类
Class<?> c = Class.forName(args[0]);
//获取类的字段
Field f = c.getField(args[1]);
//打印声明类型
System.out.format("Type: %s%n", f.getType());
//打印字段泛型
System.out.format("GenericType: %s%n", f.getGenericType());

// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (NoSuchFieldException x) {
x.printStackTrace();
}
}
}

我们分别传入相应的参数

1
2
3
4
fieldSpy.fieldSpy("lab.mon.actlab.java.reflect.FieldSpy","b");
fieldSpy.fieldSpy("lab.mon.actlab.java.reflect.FieldSpy","Alice");
fieldSpy.fieldSpy("lab.mon.actlab.java.reflect.FieldSpy","list");
fieldSpy.fieldSpy("lab.mon.actlab.java.reflect.FieldSpy","val");

然后得到以下信息

1
2
3
4
5
6
7
8
Type: class [[Z
GenericType: class [[Z
Type: class java.lang.String
GenericType: class java.lang.String
Type: interface java.util.List
GenericType: java.util.List<java.lang.Integer>
Type: class java.lang.Object
GenericType: T

这里看到对于数组,拿到的是完全限定名

val字段的为Object,因为泛型是要做类型擦除(type erasure)的,会在编译时会删除有关泛型的所有信息。

因此T被类型上限所取代,在这里是Object。

getGenericType()方法会拿到类的签名属性,如果属性不存在,它将执行Field.getType()方法。

取得、解析字段的修饰符

在上一篇中,我们介绍了怎么取得类的修饰符,下面介绍一下字段的修饰符

  • 访问修饰符: public 、protected 、private
  • 字段特定的运行行为修饰符:transient和volatile
  • 修饰一个实例:static
  • 修饰不允许修改的修饰符:final
  • 注解

Field.getModifiers()方法将会返回一个声明为set的修饰符,这些修饰符定义在java.lang.reflect.Modifier

下面来介绍一段例子来打印出字段的修饰符:

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
enum Spy { BLACK , WHITE }

public class FieldModifierSpy {
volatile int share;
int instance;
class Inner {}

public void fieldModifierSpy(String... args) {
try {
//获取类中的Class
Class<?> c = Class.forName(args[0]);
int searchMods = 0x0;
//解析传入的类型
for (int i = 1; i < args.length; i++) {
searchMods |= modifierFromString(args[i]);
}
//获取类的所有字段
Field[] flds = c.getDeclaredFields();
//打印出类名,和搜索模式
out.format("Fields in Class '%s' containing modifiers: %s%n", c.getName(), Modifier.toString(searchMods));
boolean found = false;
//遍历字段
for (Field f : flds) {
//获取字段的修饰符
int foundMods = f.getModifiers();
// Require all of the requested modifiers to be present
if ((foundMods & searchMods) == searchMods) {
//查找并打印出相应的修饰符
out.format("%-8s [ synthetic=%-5b enum_constant=%-5b ]%n", f.getName(), f.isSynthetic(), f.isEnumConstant());
found = true;
}
}

if (!found) {
out.format("No matching fields%n");
}
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}

private static int modifierFromString(String s) {
int m = 0x0;
if ("public".equals(s)) m |= Modifier.PUBLIC;
else if ("protected".equals(s)) m |= Modifier.PROTECTED;
else if ("private".equals(s)) m |= Modifier.PRIVATE;
else if ("static".equals(s)) m |= Modifier.STATIC;
else if ("final".equals(s)) m |= Modifier.FINAL;
else if ("transient".equals(s)) m |= Modifier.TRANSIENT;
else if ("volatile".equals(s)) m |= Modifier.VOLATILE;
return m;
}
}

我们来往里面传递不同的参数试一试,首先传递

1
2
3
4
fieldModifierSpy.fieldModifierSpy(FieldModifierSpy.Inner.class.getName(),"volatile");
fieldModifierSpy.fieldModifierSpy(Spy.class.getName(),"public");
fieldModifierSpy.fieldModifierSpy(FieldModifierSpy.Inner.class.getName(),"final");
fieldModifierSpy.fieldModifierSpy(Spy.class.getName(),"public","private","static","final");

得到结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
I: Fields in Class 'lab.mon.actlab.java.reflect.FieldModifierSpy' containing modifiers:  
I: instance [ synthetic=false enum_constant=false ]
I: share [ synthetic=false enum_constant=false ]
I: $change [ synthetic=true enum_constant=false ]
I: serialVersionUID [ synthetic=false enum_constant=false ]
I: Fields in Class 'lab.mon.actlab.java.reflect.Spy' containing modifiers: public
I: $change [ synthetic=true enum_constant=false ]
I: BLACK [ synthetic=false enum_constant=true ]
I: WHITE [ synthetic=false enum_constant=true ]
I: Fields in Class 'lab.mon.actlab.java.reflect.FieldModifierSpy$Inner' containing modifiers: final
I: this$0 [ synthetic=true enum_constant=false ]
I: serialVersionUID [ synthetic=false enum_constant=false ]
I: Fields in Class 'lab.mon.actlab.java.reflect.Spy' containing modifiers: private static final
I: $VALUES [ synthetic=true enum_constant=false ]

注意到,有些字段没有声明,但是出现在了输出字段中,这是因为编译器在运行时会生成一些合成字段。

如果要判断某些字段是否是合成字段,要使用方法Field.isSynthetic(),合成字段依赖于编译器,我们常见的合成字段为this$0引用外部的封闭类。

输出的另一个地方$VALUES用于枚举,来隐士的实现静态方法values()。并且合成字段在编译阶段和发布阶段很有可能不同。

并且这些字段在Class.getDeclaredField()中以数组的方式返回。但是在Class.getField()中不会返回,因为他们不是public类型。

get或者set 字段的值

在一些不允许改变其值的地方,我们有时会为了设计必须改变它的值。但是这通常违反了设计者的意图,这一操作需要极其慎重

下面这段Book代码展示了如何对long,array,enum赋值,对于其他基本类型的赋值,可以查阅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
import java.lang.reflect.Field;
import java.util.Arrays;
import static java.lang.System.out;

enum Tweedle { DEE, DUM }

public class Book {
public long chapters = 0;
public String[] characters = { "Alice", "White Rabbit" };
public Tweedle twin = Tweedle.DEE;

public void main(String... args) {
Book book = new Book();
String fmt = "%6S: %-12s = %s%n";

try {
//拿到book类
Class<?> c = book.getClass();
//拿到chapters
Field chap = c.getDeclaredField("chapters");
out.format(fmt, "before", "chapters", book.chapters);
//设置chapters的值
chap.setLong(book, 12);
out.format(fmt, "after", "chapters", chap.getLong(book));
//拿到数组characters的值
Field chars = c.getDeclaredField("characters");
out.format(fmt, "before", "characters",
Arrays.asList(book.characters));
//设置数组的值
String[] newChars = { "Queen", "King" };
chars.set(book, newChars);
out.format(fmt, "after", "characters",
Arrays.asList(book.characters));
//拿到枚举twin的值
Field t = c.getDeclaredField("twin");
out.format(fmt, "before", "twin", book.twin);
//设置枚举twin的值
t.set(book, Tweedle.DUM);
out.format(fmt, "after", "twin", t.get(book));

// production code should handle these exceptions more gracefully
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
注意:通过反射来设置值必须会出现一定的性能开销,因为需要验证访问权限。从运行的角度来看,因为操作是原子操作,就跟代码里面直接修改值是一样的。
并且,反射会取消编译时的优化.

操作时的故障

  • 类型转换的异常IllegalArgumentException

例如对引用类型Integer设置一个基本类型的值,在非反射情况下,直接设置。编译器会将类型转换(box装箱)为引用类型new Integer(24),所以类型检查会通过。但是当用反射的时候类型检查只会出现在运行时,所以没有机会进行装箱。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class FieldTrouble {
public Integer val;

public static void main(String... args) {
FieldTrouble ft = new FieldTrouble();
try {
Class<?> c = ft.getClass();
Field f = c.getDeclaredField("val");
f.setInt(ft, 42); // IllegalArgumentException

// production code should handle these exceptions more gracefully
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}

对于这种情况应该使用f.set(ft, new Integer(43));

对于反射的操作,编译器是没有机会装箱的,编译器只会对调用Class.isAssignableFrom()返回true的方法进行装箱

对于上面的例子来说

1
Integer.class.isAssignableFrom(int.class) == false

因此会抛出异常

但是对于反过来的情况,因为Feild.set(obj,value)会提供拆箱操作,所以是可以执行的

  • 非public的字段抛出的NoSuchFieldException

通常是使用 Class.getField()来调用了非public的方法。可以使用Class.getDeclaredFields()方法

  • 修改final字段的异常IllegalAccessException

如果访问private或者final字段的话,通常会抛出IllegalAccessException异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.lang.reflect.Field;

public class FieldAccess {
public final boolean b = true;

public static void main(String... args) {
FieldAccess ft = new FieldAccess();
try {
Class<?> c = ft.getClass();
Field f = c.getDeclaredField("b");
f.setBoolean(ft, Boolean.FALSE); // IllegalAccessException
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalArgumentException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}

上面这段代码会报异常IllegalAccessException在类初始化后不允许修改final字段,然而,字段的声明继承于

AccessibleObject,这让final字段的修改称为可能。

在设置值之前调用

1
f.setAccessible(true);

使用这个方法有些情况是不安全的,有些情况还会使用其原始的值。

只有安全上下文允许操作时,AccessibleObject.setAccessible才会成功。

方法

获取方法类型信息

一个方法的声明包括方法名,修饰符,参数,返回值,返回类型。和一个异常的列表,java.lang.reflect.Method提供了这些信息的读取。

还是一样举个例子说明情况,下面的例子中,列出了如何获取一个类中的方法并且如何取回类中的返回值、参数、和异常类型。

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
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import static java.lang.System.out;

public class MethodSpy {
private static final String fmt = "%24s: %s%n";

// for the morbidly curious
<E extends RuntimeException> void genericThrow() throws E {}

public void main(String... args) {
try {
//获取Class实例
Class<?> c = Class.forName(args[0]);
//获取所有的方法
Method[] allMethods = c.getDeclaredMethods();
//遍历所有方法
for (Method m : allMethods) {
//如果方法名对应的话走下面的代码
if (!m.getName().equals(args[1])) {
continue;
}
//输出方法名称

out.format("%s%n", m.toGenericString());
//输出返回类型
out.format(fmt, "ReturnType", m.getReturnType());
out.format(fmt, "GenericReturnType", m.getGenericReturnType());

Class<?>[] pType = m.getParameterTypes();
Type[] gpType = m.getGenericParameterTypes();
for (int i = 0; i < pType.length; i++) {
//输出参数类型
out.format(fmt,"ParameterType", pType[i]);
out.format(fmt,"GenericParameterType", gpType[i]);
}

Class<?>[] xType = m.getExceptionTypes();
Type[] gxType = m.getGenericExceptionTypes();
for (int i = 0; i < xType.length; i++) {
//输出异常类型
out.format(fmt,"ExceptionType", xType[i]);
out.format(fmt,"GenericExceptionType", gxType[i]);
}
}

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

如果我们输入

1
methodSpy.main("java.lang.Class","getConstructor");

得到结果

1
2
3
4
5
6
7
8
9
I: public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException,java.lang.SecurityException
I: ReturnType: class java.lang.reflect.Constructor
I: GenericReturnType: java.lang.reflect.Constructor<T>
I: ParameterType: class [Ljava.lang.Class;
I: GenericParameterType: java.lang.Class<?>[]
I: ExceptionType: class java.lang.NoSuchMethodException
I: GenericExceptionType: class java.lang.NoSuchMethodException
I: ExceptionType: class java.lang.SecurityException
I: GenericExceptionType: class java.lang.SecurityException

可以对照一下,我们会拿到Class这个方法的所有类型:

1
2
3
4
public Constructor<T> getConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
return getConstructor0(parameterTypes, Member.PUBLIC);
}

Method.getGenericReturnType() 返回通用的Type(Class文件中的签名属性),如果这个属性不可用,将调用Method.getReturnType() 方法。对于获取其他值getGenericFoo(),这条规则也是通用的。

然后看参数是一个数组,并且是一个variable arity,这里我们可以使用Method.isVarArgs()将其与普通的数组区分开来,

然后我们看下class中的cast方法,执行

1
methodSpy.main("java.lang.Class","cast");

得到:

1
2
3
4
5
I: public T java.lang.Class.cast(java.lang.Object)
I: ReturnType: class java.lang.Object
I: GenericReturnType: T
I: ParameterType: class java.lang.Object
I: GenericParameterType: class java.lang.Object

由于泛型在编译期间会进行类型擦除,因此,T被类型变量的上限Object所取代

获取方法的名称

java.lang.reflect.Executable.getParameters可以根据任何方法和构造函数获得形参的名称。因为MethodConstraint都继承了 Executable并且继承了其getParameters方法。

类文件默认没有存储形参,这是因为大多的工具在生成和消耗类文件的时候不希望大的动态和静态文件包含参数,这会让虚拟机需要更大的内存。而且一些参数可能还包含敏感信息(用户名和密码)。

如果我们想打印出参数的名称,我们首先需要设置虚拟机参数-parameters来允许这样做。

下面我们举个例子来学习:首先我们需要一个ExampleMethods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.*;

public class ExampleMethods<T> {

public boolean simpleMethod(String stringParam, int intParam) {
System.out.println("String: " + stringParam + ", integer: " + intParam);
return true;
}

public int varArgsMethod(String... manyStrings) {
return manyStrings.length;
}

public boolean methodWithList(List<String> listParam) {
return listParam.isEmpty();
}

public <T> void genericMethod(T[] a, Collection<T> c) {
System.out.println("Length of array: " + a.length);
System.out.println("Size of collection: " + c.size());
}

}

然后我们来写出下面一段代码

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
81
82
83
84
85
86
87
88
89
90

package lab.mon.actlab.java.reflect;


import java.lang.reflect.*;
import java.util.function.*;
import static java.lang.System.out;

public class MethodParameterSpy {

private static final String fmt = "%24s: %s%n";

<E extends RuntimeException> void genericThrow() throws E {}

public static void printClassConstructors(Class c) {
Constructor[] allConstructors = c.getConstructors();
out.format(fmt, "Number of constructors", allConstructors.length);
for (Constructor currentConstructor : allConstructors) {
printConstructor(currentConstructor);
}
Constructor[] allDeclConst = c.getDeclaredConstructors();
out.format(fmt, "Number of declared constructors",
allDeclConst.length);
for (Constructor currentDeclConst : allDeclConst) {
printConstructor(currentDeclConst);
}
}


public static void printConstructor(Constructor c) {
out.format("%s%n", c.toGenericString());
Parameter[] params = c.getParameters();
out.format(fmt, "Number of parameters", params.length);
for (int i = 0; i < params.length; i++) {
printParameter(params[i]);
}
}

public static void printClassMethods(Class c) {
//获取类的所有方法
Method[] allMethods = c.getDeclaredMethods();
out.format(fmt, "Number of methods", allMethods.length);
for (Method m : allMethods) {
//答应类的方法
printMethod(m);
}
}

public static void printMethod(Method m) {
//打印方法名称
out.format("%s%n", m.toGenericString());
//打印返回类型
out.format(fmt, "Return type", m.getReturnType());
//打印返回类型
out.format(fmt, "Generic return type", m.getGenericReturnType());

Parameter[] params = m.getParameters();
for (int i = 0; i < params.length; i++) {
//打印参数。
printParameter(params[i]);
}
}

public static void printParameter(Parameter p) {
//获取参数类型。
out.format(fmt, "Parameter class", p.getType());
//获取参数名称
out.format(fmt, "Parameter name", p.getName());
//获取参数的修饰符
out.format(fmt, "Modifiers", p.getModifiers());
//是否是隐式参数
out.format(fmt, "Is implicit?", p.isImplicit());
//是否能拿到名字
out.format(fmt, "Is name present?", p.isNamePresent());
//是否是合成参数
out.format(fmt, "Is synthetic?", p.isSynthetic());
}

public void main(String... args) {

try {
//打印构造函数信息
printClassConstructors(Class.forName(args[0]));
//打印类的方法
printClassMethods(Class.forName(args[0]));
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}

最后我们看打印

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
I/System.out:        Number of methods: 5
I/System.out: public static java.lang.Object lab.mon.actlab.java.reflect.ExampleMethods.access$super(lab.mon.actlab.java.reflect.ExampleMethods,java.lang.String,java.lang.Object...)
I/System.out: Return type: class java.lang.Object
I/System.out: Generic return type: class java.lang.Object
I/System.out: Parameter class: class lab.mon.actlab.java.reflect.ExampleMethods
I/System.out: Parameter name: arg0
I/System.out: Modifiers: 0
I/System.out: Is implicit?: false
I/System.out: Is name present?: false
I/System.out: Is synthetic?: false
I/System.out: Parameter class: class java.lang.String
I/System.out: Parameter name: arg1
I/System.out: Modifiers: 0
I/System.out: Is implicit?: false
I/System.out: Is name present?: false
I/System.out: Is synthetic?: false
I/System.out: Parameter class: class [Ljava.lang.Object;
I/System.out: Parameter name: arg2
I/System.out: Modifiers: 0
I/System.out: Is implicit?: false
I/System.out: Is name present?: false
I/System.out: Is synthetic?: false
I/System.out: public <T> void lab.mon.actlab.java.reflect.ExampleMethods.genericMethod(T[],java.util.Collection<T>)
I/System.out: Return type: void
I/System.out: Generic return type: void
I/System.out: Parameter class: class [Ljava.lang.Object;
I/System.out: Parameter name: arg0
I/System.out: Modifiers: 0
I/System.out: Is implicit?: false
I/System.out: Is name present?: false
I/System.out: Is synthetic?: false
I/System.out: Parameter class: interface java.util.Collection
I/System.out: Parameter name: arg1
I/System.out: Modifiers: 0
I/System.out: Is implicit?: false
I/System.out: Is name present?: false
I/System.out: Is synthetic?: false
I/System.out: public boolean lab.mon.actlab.java.reflect.ExampleMethods.methodWithList(java.util.List<java.lang.String>)
I/System.out: Return type: boolean
I/System.out: Generic return type: boolean
I/System.out: Parameter class: interface java.util.List
I/System.out: Parameter name: arg0
I/System.out: Modifiers: 0
I/System.out: Is implicit?: false
I/System.out: Is name present?: false
I/System.out: Is synthetic?: false
I/System.out: public boolean lab.mon.actlab.java.reflect.ExampleMethods.simpleMethod(java.lang.String,int)
I/System.out: Return type: boolean
I/System.out: Generic return type: boolean
I/System.out: Parameter class: class java.lang.String
I/System.out: Parameter name: arg0
I/System.out: Modifiers: 0
I/System.out: Is implicit?: false
I/System.out: Is name present?: false
I/System.out: Is synthetic?: false
I/System.out: Parameter class: int
I/System.out: Parameter name: arg1
I/System.out: Modifiers: 0
I/System.out: Is implicit?: false
I/System.out: Is name present?: false
I/System.out: Is synthetic?: false
I/System.out: public int lab.mon.actlab.java.reflect.ExampleMethods.varArgsMethod(java.lang.String...)
I/System.out: Return type: int
I/System.out: Generic return type: int
I/System.out: Parameter class: class [Ljava.lang.String;
I/System.out: Parameter name: arg0
I/System.out: Modifiers: 0
I/System.out: Is implicit?: false
I/System.out: Is name present?: false
I/System.out: Is synthetic?: false

日志比较长,但是从中我们可以看到有5个方法,另外方法里面的参数都是什么

首先先看我们使用Parameter里面的方法:

  • getType:返回一个对象,表明这个参数的类型
  • getName:返回参数的名称,如果参数存在,则返回.class提供的名称,其他情况下,将返回argN,这个N是第几个参数的意思
  • getModifiers返回一个整数,表明参数所拥有的各种特征。遵循以下表格,存在多个就求和
  • | decimal | hexadecimal | 描述 |
    | ——- | ———– | —————— |
    | 16 | 0x0010 | 形参声明为 final |
    | 4096 | 0x1000 | 表明形参是合成的 |
    | 32768 | 0x8000 | 表明形参是隐式参数 |

  • isImplicit: 如果是隐士的则返回true

  • isNamePresent: 如果参数在.class文件中有名字的话就返回true

  • isSynthetic: 如果在代码中没有显示或隐士的显示参数则返回true

隐式和合成参数

  • 隐式参数

有些构造体没有写在代码中的话会隐士的声明,

例如:咱们的例子中没有构造函数,但是有一个默认的构造函数隐式的声明了。

再例如:假设这么一种情况:

1
2
3
public class MethodParameterExamples {
public class InnerClass { }
}

InnerClass是一个嵌套类或者说内部类,其构造函数也是隐式的,但是!这个构造函数是有个参数的,当Java编译器编译的时候会大概出现以下代码。

1
2
3
4
5
6
7
8
public class MethodParameterExamples {
public class InnerClass {
final MethodParameterExamples parent;
InnerClass(final MethodParameterExamples this$0) {
parent = this$0;
}
}
}

如果我们用刚才的例子打印出结果将是这样的

1
2
3
4
5
6
7
public MethodParameterExamples$InnerClass(MethodParameterExamples)
Parameter class: class MethodParameterExamples
Parameter name: this$0
Modifiers: 32784
Is implicit?: true
Is name present?: true
Is synthetic?: false

23786标识是final和隐式的

  • 合成参数

除非是类的初始化方法,如果参数不对应显示或隐式的构造声明的话,那么标记为合成(synthetic)的。合成构造是

考虑到一下枚举:

1
2
3
4
5
public class MethodParameterExamples {
enum Colors {
RED, WHITE;
}
}

编译器在编译期会将枚举拆成几个方法,并提供枚举的基本功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
final class Colors extends java.lang.Enum<Colors> {
public final static Colors RED = new Colors("RED", 0);
public final static Colors BLUE = new Colors("WHITE", 1);

private final static values = new Colors[]{ RED, BLUE };

private Colors(String name, int ordinal) {
super(name, ordinal);
}

public static Colors[] values(){
return values;
}

public static Colors valueOf(String name){
return (Colors)java.lang.Enum.valueOf(Colors.class, name);
}
}

编译器在编译期会生成这些Colors(String name, int ordinal), Colors[] values(), 和 Colors valueOf(String name)方法,他们都是隐式声明的,同样的形参也是隐式声明的。

这个构造方法是隐式声明的,但是这个构造方法的行参不是隐式声明的!为什么呢?因为不同的编译器不需要在此构造函数的形式上达成一致,在另一个编译器中可能是另一种声明,所以不是隐式的。

既不是隐式的参数(参数不固定),又不是显式的参数(在隐式方法中),那么就是合成参数了。。

用刚才的MethodParameterExample例子跑一下得到输出:

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
enum Colors:

Number of constructors: 0

Number of declared constructors: 1

Declared constructor #1
private MethodParameterExamples$Colors()
Parameter class: class java.lang.String
Parameter name: $enum$name
Modifiers: 4096
Is implicit?: false
Is name present?: true
Is synthetic?: true
Parameter class: int
Parameter name: $enum$ordinal
Modifiers: 4096
Is implicit?: false
Is name present?: true
Is synthetic?: true

Number of methods: 2

Method #1
public static MethodParameterExamples$Colors[]
MethodParameterExamples$Colors.values()
Return type: class [LMethodParameterExamples$Colors;
Generic return type: class [LMethodParameterExamples$Colors;

Method #2
public static MethodParameterExamples$Colors
MethodParameterExamples$Colors.valueOf(java.lang.String)
Return type: class MethodParameterExamples$Colors
Generic return type: class MethodParameterExamples$Colors
Parameter class: class java.lang.String
Parameter name: name
Modifiers: 32768
Is implicit?: true
Is name present?: true
Is synthetic?: false

检索或解析方法的修饰符

举个例子

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
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import static java.lang.System.out;

public class MethodModifierSpy {

private static int count;
private static synchronized void inc() { count++; }
private static synchronized int cnt() { return count; }

public void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
Method[] allMethods = c.getDeclaredMethods();
for (Method m : allMethods) {
if (!m.getName().equals(args[1])) {
continue;
}
out.format("%s%n", m.toGenericString());
out.format(" Modifiers: %s%n", Modifier.toString(m.getModifiers()));
out.format(" [ synthetic=%-5b var_args=%-5b bridge=%-5b ]%n", m.isSynthetic(), m.isVarArgs(), m.isBridge());
inc();
}
out.format("%d matching overload%s found%n", cnt(), (cnt() == 1 ? "" : "s"));

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

执行

1
methodModifierSpy.main("java.lang.String","compareTo");

得到

1
2
3
4
5
6
7
public int java.lang.String.compareTo(java.lang.String)
Modifiers: public
[ synthetic=false var_args=false bridge=false ]
public int java.lang.String.compareTo(java.lang.Object)
Modifiers: public volatile
[ synthetic=true var_args=false bridge=true ]
2 matching overloads found

这里为什么又两个呢?

第二个synthetic或者说编译期生成的bridge方法,是因为String继承了参数化的接口Comparable,当执行类型擦除的时候,从Comparable.compareTo() 继承的方法类型从Object变为String,当Comparable和String的参数类型在类型擦除后不再匹配,就不用重写了,bridge方法避免了重写的问题。

调用方法

反射提供了调用方法的方法。在非反射代码中,如果无法将类的实例转换为需要的类型,那么将执行java.lang.reflect.Method.invoke(),第一个参数是调用特定方法的实例(方法是静态的话可以为空),接下来的就是传入方法的参数,如果出现异常则会抛出java.lang.reflect.InvocationTargetException,但是如果想获得原始的异常可以使用InvocationTargetException.getCause()方法。

调用普通方法

举例说明:首先创建一个类

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
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Locale;

import static java.lang.System.out;
import static java.lang.System.err;

public class Deet<T> {
private boolean testDeet(Locale l) {
// getISO3Language() may throw a MissingResourceException
out.format("Locale = %s, ISO Language Code = %s%n", l.getDisplayName(), l.getISO3Language());
return true;
}

private int testFoo(Locale l) {
return 0;
}

private boolean testBar() {
return true;
}

public void main(String... args) {
if (args.length != 4) {
err.format("Usage: java Deet <classname> <langauge> <country> <variant>%n");
return;
}

try {
//获取类的class
Class<?> c = Class.forName(args[0]);
//创建类的实例
Object t = c.newInstance();
//获取所有的方法
Method[] allMethods = c.getDeclaredMethods();
for (Method m : allMethods) {
//获取方法名称
String mname = m.getName();
//如果不是以test开头或者返回类型不是boolean的话就跳出循环
if (!mname.startsWith("test") || (m.getGenericReturnType() != boolean.class)) {
continue;
}
//获取所有的参数
Type[] pType = m.getGenericParameterTypes();
//如果方法参数不是1或者不允许访问的话跳出
if ((pType.length != 1) || Locale.class.isAssignableFrom(pType[0].getClass())) {
continue;
}

out.format("invoking %s()%n", mname);
try {
//获取访问权限
m.setAccessible(true);
//调用方法并得到返回值
Object o = m.invoke(t, new Locale(args[1], args[2], args[3]));
out.format("%s() returned %b%n", mname, (Boolean) o);

} catch (InvocationTargetException x) {
Throwable cause = x.getCause();
err.format("invocation of %s failed: %s%n",
mname, cause.getMessage());
}
}

// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}

然后执行:

1
2
Deet deet = new Deet();
deet.main(Deet.class.getCanonicalName(),"ja", "JP", "JP");

最后得到

1
2
3
I/System.out: invoking testDeet()
I/System.out: Locale = Japanese (Japan,JP), ISO Language Code = jpn
I/System.out: testDeet() returned true

其中,Class.isAssignableFrom()来判断Located方法是否可以调用,从代码上来说Locale.class == pType[0].getClass()true的话可以判断Locale是否为final

调用可变数量的参数

举例说明:

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
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

public class InvokeMain {
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
Class[] argTypes = new Class[] { String[].class };
Method main = c.getDeclaredMethod("main", argTypes);
String[] mainArgs = Arrays.copyOfRange(args, 1, args.length);
System.out.format("invoking %s.main()%n", c.getName());
main.invoke(null, (Object)mainArgs);

// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
}
}
}

方法的异常

NoSuchMethodException

例子:未考虑对象擦著

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.lang.reflect.Method;

public class MethodTrouble<T> {
public void lookup(T t) {}
public void find(Integer i) {}

public static void main(String... args) {
try {
String mName = args[0];
Class cArg = Class.forName(args[1]);
Class<?> c = (new MethodTrouble<Integer>()).getClass();
Method m = c.getMethod(mName, cArg);
System.out.format("Found:%n %s%n", m.toGenericString());

// production code should handle these exceptions more gracefully
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}

原方法:

1
public void MethodTrouble.lookup(T)

当使用泛型参数类型声明方法时,编译器将使用其上限替换泛型类型,在这种情况下,T的上限是Object,所以当代码搜索lookup(Integer)时,找不到任何方法。

IllegalAccessException

如果尝试调用私有或其他不可访问的方法,则抛出IllegalAccessException。

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

class AnotherClass {
private void m() {}
}

public class MethodTroubleAgain {
public static void main(String... args) {
AnotherClass ac = new AnotherClass();
try {
Class<?> c = ac.getClass();
Method m = c.getDeclaredMethod("m");
// m.setAccessible(true); // 解决方案
Object o = m.invoke(ac); // IllegalAccessException

// production code should handle these exceptions more gracefully
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}

和字段一样不做多的描述。

IllegalArgumentException

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

public class MethodTroubleToo {
public void ping() { System.out.format("PONG!%n"); }

public static void main(String... args) {
try {
MethodTroubleToo mtt = new MethodTroubleToo();
Method m = MethodTroubleToo.class.getMethod("ping");

switch(Integer.parseInt(args[0])) {
case 0:
m.invoke(mtt);
break;
case 1:
m.invoke(mtt, null);
break;
case 2:
Object arg2 = null;
m.invoke(mtt, arg2); // IllegalArgumentException
break;
case 3:
m.invoke(mtt, new Object[0]);
break;
case 4:
Object arg4 = new Object[0];
m.invoke(mtt, arg4); // IllegalArgumentException
break;
default:
System.out.format("Test not found%n");
}

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

分别执行方法1、2、3、4

  • 1不报异常,因为这个方法没有参数
  • 2报异常,因为2相当于传递了一个空的参数是找不到这个方法的
  • 3不报异常,因为可以理解为传递了一个可变的参数
  • 4报异常,原因和2一样,也是传递了一个参数,方法是找不到这个的

构造函数

构造函数用来创建一个对象的实例,通常下,在执行一个类的方法调用之前会执行初始化操作这就是构造函数。

下面我们来举例说明构造函数

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
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import static java.lang.System.out;

public class ConstructorSift {
public void main(String... args) {
try {
Class<?> cArg = Class.forName(args[1]);

Class<?> c = Class.forName(args[0]);
//获取类的构造函数
Constructor[] allConstructors = c.getDeclaredConstructors();
//遍历构造函数
for (Constructor ctor : allConstructors) {
//获取构造函数的参数
Class<?>[] pType = ctor.getParameterTypes();
//构造函数的参数
for (int i = 0; i < pType.length; i++) {
//如果构造函数的参数包含了传递过来的参数
if (pType[i].equals(cArg)) {
//输出构造函数
out.format("%s%n", ctor.toGenericString());
//获取构造函数的参数
Type[] gpType = ctor.getGenericParameterTypes();
//遍历参数
for (int j = 0; j < gpType.length; j++) {
char ch = (pType[j].equals(cArg) ? '*' : ' ');
//输出
out.format("%7c%s[%d]: %s%n", ch, "GenericParameterType", j, gpType[j]);
}
break;
}
}
}
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}

输入

1
constructorSift.main("java.util.Formatter", "java.util.Locale");

得到输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
I/System.out: public java.util.Formatter(java.io.File,java.lang.String,java.util.Locale) throws java.io.FileNotFoundException,java.io.UnsupportedEncodingException
I/System.out: GenericParameterType[0]: class java.io.File
I/System.out: GenericParameterType[1]: class java.lang.String
I/System.out: *GenericParameterType[2]: class java.util.Locale
I/System.out: public java.util.Formatter(java.io.OutputStream,java.lang.String,java.util.Locale) throws java.io.UnsupportedEncodingException
I/System.out: GenericParameterType[0]: class java.io.OutputStream
I/System.out: GenericParameterType[1]: class java.lang.String
I/System.out: *GenericParameterType[2]: class java.util.Locale
I/System.out: public java.util.Formatter(java.lang.Appendable,java.util.Locale)
I/System.out: GenericParameterType[0]: interface java.lang.Appendable
I/System.out: *GenericParameterType[1]: class java.util.Locale
I/System.out: public java.util.Formatter(java.lang.String,java.lang.String,java.util.Locale) throws java.io.FileNotFoundException,java.io.UnsupportedEncodingException
I/System.out: GenericParameterType[0]: class java.lang.String
I/System.out: GenericParameterType[1]: class java.lang.String
I/System.out: *GenericParameterType[2]: class java.util.Locale
I/System.out: private java.util.Formatter(java.nio.charset.Charset,java.util.Locale,java.io.File) throws java.io.FileNotFoundException
I/System.out: GenericParameterType[0]: class java.nio.charset.Charset
I/System.out: *GenericParameterType[1]: class java.util.Locale
I/System.out: GenericParameterType[2]: class java.io.File
I/System.out: public java.util.Formatter(java.util.Locale)
I/System.out: *GenericParameterType[0]: class java.util.Locale
I/System.out: private java.util.Formatter(java.util.Locale,java.lang.Appendable)
I/System.out: *GenericParameterType[0]: class java.util.Locale
I/System.out: GenericParameterType[1]: interface java.lang.Appendable

可以看到,和之前method的方法类似,Method.getGenericParameterTypes() 将查询类文件中的签名属性。

另一个例子,在String方法中,找到包含char数组的方法

输入:

1
constructorSift.main("java.lang.String", "[C");

输出

1
2
3
4
5
6
7
8
9
10
I/System.out: java.lang.String(int,int,char[])
I/System.out: GenericParameterType[0]: int
I/System.out: GenericParameterType[1]: int
I/System.out: *GenericParameterType[2]: class [C
I/System.out: public java.lang.String(char[])
I/System.out: *GenericParameterType[0]: class [C
I/System.out: public java.lang.String(char[],int,int)
I/System.out: *GenericParameterType[0]: class [C
I/System.out: GenericParameterType[1]: int
I/System.out: GenericParameterType[2]: int

数组的表达式和基本类型和Class.getName()是一样的。第一个是private,我们之所以可拿到它是因为我们用了

Class.getDeclaredConstructors() 这一特征在前面有提到。

构造函数的修饰符

构造函数的修饰符有

  • 访问修饰符: public, protected, private
  • 注解

写Demo说明:

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
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import static java.lang.System.out;

public class ConstructorAccess {
public void main(String... args) {
try {
//获取类
Class<?> c = Class.forName(args[0]);
//获取类的构造函数
Constructor[] allConstructors = c.getDeclaredConstructors();
//遍历构造函数
for (Constructor ctor : allConstructors) {
//获取输入的修饰符
int searchMod = modifierFromString(args[1]);
//获取修饰符修饰符模式
int mods = accessModifiers(ctor.getModifiers());
if (searchMod == mods) {
//输出
out.format("%s%n", ctor.toGenericString());
out.format(" [ synthetic=%-5b var_args=%-5b ]%n",
ctor.isSynthetic(), ctor.isVarArgs());
}
}

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

private static int accessModifiers(int m) {
return m & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED);
}

private static int modifierFromString(String s) {
if ("public".equals(s)) return Modifier.PUBLIC;
else if ("protected".equals(s)) return Modifier.PROTECTED;
else if ("private".equals(s)) return Modifier.PRIVATE;
else if ("package-private".equals(s)) return 0;
else return -1;
}
}

这里要注意一个修饰符包级的私有,所以要检查三种访问修饰符来确定包级私有构造函数

在这里我们输入

1
constructorAccess.main("java.io.File","private");

得到输出

1
2
3
4
I/System.out: private java.io.File(java.lang.String,int)
I/System.out: [ synthetic=false var_args=false ]
I/System.out: private java.io.File(java.lang.String,java.io.File)
I/System.out: [ synthetic=false var_args=false ]

synthetic这个有介绍对于Inner Class来说,编译器在编译的时候回生成一个合成构造函数。包含了一个私有的对外部类的引用。

创建一个Class实例

构造Class实例有两种办法:java.lang.reflect.Constructor.newInstance()Class.newInstance().

最好使用Constructor.newInstance(),有以下几个原因

  • Class.newInstance() 只能构造没有参数的Class,Constructor.newInstance()可以调用包含任何参数的构造函数。
  • Class.newInstance() 不管是检查还是未检查第通过构造函数抛出异常,而Constructor.newInstance()是使用InvocationTargetException来包装抛出异常。
  • Class.newInstance() 要求构造函数是可见的,而Constructor.newInstance()可以调用私有的构造函数。

对于Class.newInstance()的实现来说相对简单,这里就不再举例,那么对于用构造函数来创建对象的方法在这里给出一个Demo

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
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static java.lang.System.out;

class EmailAliases {
private Set<String> aliases;
private EmailAliases(HashMap<String, String> h) {
aliases = h.keySet();
}

public void printKeys() {
out.format("Mail keys:%n");
for (String k : aliases)
out.format(" %s%n", k);
}
}

public class RestoreAliases {

private static Map<String, String> defaultAliases = new HashMap<String, String>();
static {
defaultAliases.put("Duke", "duke@i-love-java");
defaultAliases.put("Fang", "fang@evil-jealous-twin");
}

public static void main(String... args) {
try {
Constructor ctor = EmailAliases.class.getDeclaredConstructor(HashMap.class);
ctor.setAccessible(true);
EmailAliases email = (EmailAliases)ctor.newInstance(defaultAliases);
email.printKeys();

// production code should handle these exceptions more gracefully
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
}
}
}

调用main方法我们得到

1
2
3
I/System.out: Mail keys:
I/System.out: Fang
I/System.out: Duke

此示例使用Class.getDeclaredConstructor()来查找具有java.util.HashMap类型的单个参数的构造函数。

这里我们看到我们使用

EmailAliases.class.getDeclaredConstructor(HashMap.class);来获取了构造函数,为什么没有传递HashMap里面的类型呢?这是因为类型擦除,下面的代码会返回true:

1
HashMap.class == defaultAliases.getClass()

构造异常

InstantiationException

Class.newInstance()缺少零参的构造函数

Unexpected 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
import java.lang.reflect.InvocationTargetException;
import static java.lang.System.err;

public class ConstructorTroubleToo {
public ConstructorTroubleToo() {
throw new RuntimeException("exception in constructor");
}

public static void main(String... args) {
try {
Class<?> c = Class.forName("ConstructorTroubleToo");
// Method propagetes any exception thrown by the constructor
// (including checked exceptions).
if (args.length > 0 && args[0].equals("class")) {
Object o = c.newInstance();
} else {
Object o = c.getConstructor().newInstance();
}

// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
err.format("%n%nCaught exception: %s%n", x.getCause());
}
}
}

如果调用

1
ConstructorTroubleToo.main(“class”);

则会抛出异常

1
2
3
4
5
6
7
Caused by: java.lang.RuntimeException: exception in constructor
at lab.mon.actlab.java.reflect.construct.ConstructorTroubleToo.<init>(ConstructorTroubleToo.java:9)
at java.lang.Class.newInstance(Native Method)
at lab.mon.actlab.java.reflect.construct.ConstructorTroubleToo.main(ConstructorTroubleToo.java:18)
at lab.mon.actlab.java.reflect.ReflectActivity.onResume(ReflectActivity.java:96)
at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1354)
at android.app.Activity.performResume(Activity.java:7079)

如果调用

1
ConstructorTroubleToo.main();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
W/System.err: java.lang.reflect.InvocationTargetException
W/System.err: at java.lang.reflect.Constructor.newInstance0(Native Method)
W/System.err: at java.lang.reflect.Constructor.newInstance(Constructor.java:334)
W/System.err: at lab.mon.actlab.java.reflect.construct.ConstructorTroubleToo.main(ConstructorTroubleToo.java:20)
W/System.err: at lab.mon.actlab.java.reflect.ReflectActivity.onResume(ReflectActivity.java:96)
W/System.err: at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1354)
W/System.err: at android.app.Activity.performResume(Activity.java:7079)
W/System.err: at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3620)
W/System.err: at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3685)
W/System.err: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2898)
W/System.err: at android.app.ActivityThread.-wrap11(Unknown Source:0)
W/System.err: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
W/System.err: at android.os.Handler.dispatchMessage(Handler.java:105)
W/System.err: at android.os.Looper.loop(Looper.java:164)
W/System.err: at android.app.ActivityThread.main(ActivityThread.java:6541)
W/System.err: at java.lang.reflect.Method.invoke(Native Method)
W/System.err: at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

则会抛出异常,但是构造函数将被创建,不会崩溃,而且如果想要捕获异常,只需要调用InvocationTargetException.getCause().即可,这就是之前所说的使用 Constructor.newInstance()Class.newInstance()好的地方,它可以检查和处理构造函数抛出的异常。

定位构造函数出现的问题

如果我们要捕获创建构造函数时出现的问题,我们则需要直接调用trycache即可如一下Demo

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
import java.lang.reflect.InvocationTargetException;

import static java.lang.System.out;

public class ConstructorTroubleAgain {
public ConstructorTroubleAgain() {
}

public ConstructorTroubleAgain(Integer i) {
}

public ConstructorTroubleAgain(Object o) {
out.format("Constructor passed Object%n");
}

public ConstructorTroubleAgain(String s) {
out.format("Constructor passed String%n");
}

public static void main(String... args) {
String argType = (args.length == 0 ? "" : args[0]);
try {
Class<?> c = Class.forName(ConstructorTroubleAgain.class.getName());
if ("".equals(argType)) {
// IllegalArgumentException: wrong number of arguments
Object o = c.getConstructor().newInstance("foo");
} else if ("int".equals(argType)) {
// NoSuchMethodException
Object o = c.getConstructor(int.class);
} else if ("Object".equals(argType)) {
Object o = c.getConstructor(Object.class).newInstance("foo");
} else {
assert false;
}

} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}

我们分别调用三种传值来查看这几种异常

1
2
3
ConstructorTroubleAgain.main();
ConstructorTroubleAgain.main("int");
ConstructorTroubleAgain.main("Object");

捕获三种崩溃

1
Caused by: java.lang.IllegalArgumentException: Wrong number of arguments; expected 0, got 1

是因为参数个数错误导致的,获取的是无参的构造函数,但是调用是有参数的。

1
W/System.err: java.lang.NoSuchMethodException: <init> [int]

Java在反射中,基本类型一般是不会box和unbox的(基本类型转换为引用类型)。

1
I/System.out: Constructor passed Object

第三个中虽然调用传递参数了string构造函数,但是依然调用了Object

IllegalAccessException

一般出现这种异常是因为调用了私有的构造函数,只要我们像方法一样调用setAccessible(true)就可以解决。

谢谢您的鼓励~