Java 中文官方教程 2022 版(四十四)(1)https://developer.aliyun.com/article/1488313
检索和解析构造函数修饰符
原文:
docs.oracle.com/javase/tutorial/reflect/member/ctorModifiers.html
由于构造函数在语言中的作用,比方法更少的修饰符是有意义的:
- 访问修饰符:
public
,protected
和private
- 注解
ConstructorAccess
示例在给定类中搜索具有指定访问修饰符的构造函数。它还显示构造函数是否是合成的(由编译器生成)或具有可变参数。
import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import static java.lang.System.out; public class ConstructorAccess { public static 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()); } } // production code should handle this exception more gracefully } 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; } }
没有明确对应于“包私有”访问权限的Modifier
常量,因此需要检查所有三个访问修饰符的缺失来识别包私有构造函数。
此输出显示了java.io.File
中的私有构造函数:
$ *java ConstructorAccess java.io.File private* private java.io.File(java.lang.String,int) [ synthetic=false var_args=false ] private java.io.File(java.lang.String,java.io.File) [ synthetic=false var_args=false ]
合成构造函数很少见;但是SyntheticConstructor
示例说明了可能发生这种情况的典型情况:
public class SyntheticConstructor { private SyntheticConstructor() {} class Inner { // Compiler will generate a synthetic constructor since // SyntheticConstructor() is private. Inner() { new SyntheticConstructor(); } } }
$ *java ConstructorAccess SyntheticConstructor package-private* SyntheticConstructor(SyntheticConstructor$1) [ synthetic=true var_args=false ]
由于内部类的构造函数引用了封闭类的私有构造函数,编译器必须生成一个包私有构造函数。参数类型SyntheticConstructor$1
是任意的,取决于编译器的实现。依赖于任何合成或非公共类成员存在的代码可能不具有可移植性。
构造函数实现了java.lang.reflect.AnnotatedElement
,提供了用于检索运行时注解的方法,使用java.lang.annotation.RetentionPolicy.RUNTIME
。有关获取注解的示例,请参见检查类修饰符和类型部分。
创建新的类实例
原文:
docs.oracle.com/javase/tutorial/reflect/member/ctorInstance.html
创建类实例的两种反射方法:java.lang.reflect.Constructor.newInstance()
和 Class.newInstance()
。前者更受青睐,因此在这些示例中使用,原因如下:
Class.newInstance()
只能调用零参数构造函数,而Constructor.newInstance()
可以调用任何构造函数,无论参数个数如何。- 无论构造函数抛出的是已检查异常还是未检查异常,
Class.newInstance()
都会抛出该异常。Constructor.newInstance()
总是用InvocationTargetException
包装抛出的异常。 Class.newInstance()
要求构造函数可见;Constructor.newInstance()
在某些情况下可以调用private
构造函数。
有时可能希望从仅在构造后设置的对象中检索内部状态。考虑一个场景,需要获取java.io.Console
使用的内部字符集。(Console
字符集存储在私有字段中,并且不一定与java.nio.charset.Charset.defaultCharset()
返回的 Java 虚拟机默认字符集相同)。ConsoleCharset
示例展示了如何实现这一点:
import java.io.Console; import java.nio.charset.Charset; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import static java.lang.System.out; public class ConsoleCharset { public static void main(String... args) { Constructor[] ctors = Console.class.getDeclaredConstructors(); Constructor ctor = null; for (int i = 0; i < ctors.length; i++) { ctor = ctors[i]; if (ctor.getGenericParameterTypes().length == 0) break; } try { ctor.setAccessible(true); Console c = (Console)ctor.newInstance(); Field f = c.getClass().getDeclaredField("cs"); f.setAccessible(true); out.format("Console charset : %s%n", f.get(c)); out.format("Charset.defaultCharset(): %s%n", Charset.defaultCharset()); // production code should handle these exceptions more gracefully } catch (InstantiationException x) { x.printStackTrace(); } catch (InvocationTargetException x) { x.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); } catch (NoSuchFieldException x) { x.printStackTrace(); } } }
注意:
如果构造函数没有参数且已经可访问,则Class.newInstance()
才会成功。否则,需要像上面的示例一样使用Constructor.newInstance()
。
UNIX 系统的示例输出:
$ *java ConsoleCharset* Console charset : ISO-8859-1 Charset.defaultCharset() : ISO-8859-1
Windows 系统的示例输出:
C:\> *java ConsoleCharset* Console charset : IBM437 Charset.defaultCharset() : windows-1252
另一个常见的 Constructor.newInstance()
应用是调用需要参数的构造函数。RestoreAliases
示例找到一个特定的单参数构造函数并调用它:
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(); } } }
这个示例使用 Class.getDeclaredConstructor()
来找到一个参数类型为 java.util.HashMap
的构造函数。请注意,只需传递 HashMap.class
就足够了,因为任何 get*Constructor()
方法的参数只需要类来确定类型。由于 类型擦除,以下表达式求值为 true
:
HashMap.class == defaultAliases.getClass()
然后,示例使用这个构造函数使用 Constructor.newInstance()
创建类的新实例。
$ *java RestoreAliases* Mail keys: Duke Fang
故障排除
原文:
docs.oracle.com/javase/tutorial/reflect/member/ctorTrouble.html
开发人员在尝试通过反射调用构造函数时,有时会遇到以下问题。
由于缺少零参数构造函数而导致的 InstantiationException
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
失败,它就会失败。
Class.newInstance() 抛出意外异常
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()
检索到的异常相同。此异常并不表示反射包或其使用存在问题。
**提示:**最好使用Constructor.newInstance()
而不是Class.newInstance()
,因为前者的 API 允许检查和处理构造函数抛出的任意异常。
定位或调用正确构造函数的问题
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()
不会尝试进行方法解析;它只是在现有构造函数对象上操作。
提示: new
和Constructor.newInstance()
之间的一个重要区别是,new
执行方法参数类型检查、装箱和方法解析。在反射中,这些都不会发生,必须做出明确选择。
尝试调用不可访问构造函数时出现 IllegalAccessException
如果尝试调用私有或其他不可访问的构造函数,则可能会抛出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()
来抑制此检查的能力。
Java 中文官方教程 2022 版(四十四)(3)https://developer.aliyun.com/article/1488321