这些Java教程是针对JDK 8编写的。本页面描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
有关Java SE 9及其后续版本中更新的语言功能的摘要,请参阅Java语言变更。
有关所有JDK版本的新功能、增强功能和已删除或弃用选项的信息,请参阅JDK发行说明。
在尝试通过反射调用构造函数时,开发人员有时会遇到以下问题。
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 // 生产代码应更优雅地处理这些异常 } 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("构造函数中的异常"); } public static void main(String... args) { try { Class<?> c = Class.forName("ConstructorTroubleToo"); // 方法通过构造函数传播抛出的异常(包括已检查的异常)。 if (args.length > 0 && args[0].equals("class")) { Object o = c.newInstance(); } else { Object o = c.getConstructor().newInstance(); } // 实际代码应该更优雅地处理这些异常 } 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%n捕获到异常: %s%n", x.getCause()); } } }
$ java ConstructorTroubleToo class Exception in thread "main" java.lang.RuntimeException: 构造函数中的异常 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: 构造函数中的异常 at ConstructorTroubleToo.<init>(ConstructorTroubleToo.java:6) ... 5 more 捕获到异常: java.lang.RuntimeException: 构造函数中的异常
如果抛出了一个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: 参数数量错误 Object o = c.getConstructor().newInstance("foo"); } else if ("int".equals(argType)) { // NoSuchMethodException - 寻找int,但是只有Integer Object o = c.getConstructor(int.class); } else if ("Object".equals(argType)) { // newInstance() 不执行方法解析 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(); } } }
$ java ConstructorTroubleAgain 异常线程 "main" java.lang.IllegalArgumentException: 参数数量错误 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("拒绝构造函数%n"); } } public class ConstructorTroubleAccess { public static void main(String... args) { try { Constructor c = Deny.class.getDeclaredConstructor(); // c.setAccessible(true); // 解决方案 c.newInstance(); // 产品代码应更优雅地处理这些异常 } 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: 类 ConstructorTroubleAccess 无法访问具有 "private" 修饰符的 Deny 类的成员 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()
方法来抑制此检查的能力。