Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
在尝试通过反射调用构造函数时,开发人员有时会遇到以下问题。
ConstructorTrouble 示例说明了当代码尝试使用 Class.newInstance() 并且没有可访问的零参数构造函数时,创建类的新实例时会发生什么:
public class ConstructorTrouble {
private ConstructorTrouble(int i) {}
public static void main(String... args){
try {
Class<?> c = Class.forName("ConstructorTrouble");
Object o = c.newInstance(); // InstantiationException
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
$ java ConstructorTrouble
java.lang.InstantiationException: ConstructorTrouble
at java.lang.Class.newInstance0(Class.java:340)
at java.lang.Class.newInstance(Class.java:308)
at ConstructorTrouble.main(ConstructorTrouble.java:7)
InstantiationException。在这种情况下,问题是带有 int 参数的构造函数的存在会阻止编译器生成默认(或零参数)构造函数,并且代码中没有显式的零参数构造函数。请记住,Class.newInstance() 的行为与 new 关键字非常相似,只要 new 失败,它就会失败。
ConstructorTroubleToo 示例在 Class.newInstance() 中显示无法解决的问题。也就是说,它传播构造函数抛出的任何异常 - 检查型异常或非检查型异常。
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());
}
}
}
$ java ConstructorTroubleToo class
Exception in thread "main" java.lang.RuntimeException: exception in constructor
at ConstructorTroubleToo.<init>(ConstructorTroubleToo.java:6)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance
(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance
(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at java.lang.Class.newInstance0(Class.java:355)
at java.lang.Class.newInstance(Class.java:308)
at ConstructorTroubleToo.main(ConstructorTroubleToo.java:15)
这种情况是反射所特有的。通常,编写忽略检查型异常的代码是不可能的,因为它无法编译。可以使用 Constructor.newInstance() 而不是 Class.newInstance() 来包装构造函数抛出的任何异常。
$ java ConstructorTroubleToo
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance
(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance
(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at ConstructorTroubleToo.main(ConstructorTroubleToo.java:17)
Caused by: java.lang.RuntimeException: exception in constructor
at ConstructorTroubleToo.<init>(ConstructorTroubleToo.java:6)
... 5 more
Caught exception: java.lang.RuntimeException: exception in constructor
如果抛出 InvocationTargetException,则方法被调用。问题的诊断与直接调用构造函数并抛出由 InvocationTargetException.getCause() 获取的异常相同。此异常并不表示反射包或其用法存在问题。
ConstructorTroubleAgain 类说明了错误代码无法找到或调用预期构造函数的各种方法。
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");
if ("".equals(argType)) {
// IllegalArgumentException: wrong number of arguments
Object o = c.getConstructor().newInstance("foo");
} else if ("int".equals(argType)) {
// NoSuchMethodException - looking for int, have Integer
Object o = c.getConstructor(int.class);
} else if ("Object".equals(argType)) {
// newInstance() does not perform method resolution
Object o = c.getConstructor(Object.class).newInstance("foo");
} else {
assert false;
}
// production code should handle these exceptions more gracefully
} 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();
}
}
}
$ java ConstructorTroubleAgain
Exception in thread "main" java.lang.IllegalArgumentException: wrong number of
arguments
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance
(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance
(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at ConstructorTroubleAgain.main(ConstructorTroubleAgain.java:23)
抛出 IllegalArgumentException,因为请求了零参数构造函数并尝试传递参数。如果构造函数传递了错误类型的参数,则会抛出相同的异常。
$ java ConstructorTroubleAgain int
java.lang.NoSuchMethodException: ConstructorTroubleAgain.<init>(int)
at java.lang.Class.getConstructor0(Class.java:2706)
at java.lang.Class.getConstructor(Class.java:1657)
at ConstructorTroubleAgain.main(ConstructorTroubleAgain.java:26)
如果开发人员错误地认为反射将自动装箱或拆箱类型,则可能会发生此异常。装箱(将基本转换为引用类型)仅在编译期间发生。反射中没有机会发生此操作,因此在查找构造函数时必须使用特定类型。
$ java ConstructorTroubleAgain Object Constructor passed Object
在这里,可能期望调用带有 String 参数的构造函数,因为使用更具体的 String 类型执行 newInstance()。但是为时已晚!找到的构造函数已经是具有 Object 参数的构造函数。newInstance() 不会尝试进行方法解析;它只是对现有的构造函数对象进行操作。
如果尝试调用私有或其他不可访问的构造函数,则可能抛出 IllegalAccessException。ConstructorTroubleAccess 示例说明了生成的堆栈跟踪。
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
class Deny {
private Deny() {
System.out.format("Deny constructor%n");
}
}
public class ConstructorTroubleAccess {
public static void main(String... args) {
try {
Constructor c = Deny.class.getDeclaredConstructor();
// c.setAccessible(true); // solution
c.newInstance();
// production code should handle these exceptions more gracefully
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
$ java ConstructorTroubleAccess
java.lang.IllegalAccessException: Class ConstructorTroubleAccess can not access
a member of class Deny with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.reflect.Constructor.newInstance(Constructor.java:505)
at ConstructorTroubleAccess.main(ConstructorTroubleAccess.java:15)
Constructor 被声明为继承 AccessibleObject,它提供了通过 AccessibleObject.setAccessible() 来抑制此检查的功能。