调用方法
原文:
docs.oracle.com/javase/tutorial/reflect/member/methodInvocation.html
反射提供了一种在类上调用方法的方式。通常,只有在非反射代码中无法将类的实例强制转换为所需类型时才需要这样做。方法是使用java.lang.reflect.Method.invoke()
来调用的。第一个参数是要调用该特定方法的对象实例。(如果方法是static
,第一个参数应为null
。)后续参数是方法的参数。如果底层方法抛出异常,它将被java.lang.reflect.InvocationTargetException
包装。可以使用异常链接机制的InvocationTargetException.getCause()
方法检索方法的原始异常。
查找和调用具有特定声明的方法
考虑一个测试套件,它使用反射来调用给定类中的私有测试方法。Deet
示例搜索类中以字符串"test
"开头,具有布尔返回类型和单个Locale
参数的public
方法。然后调用每个匹配的方法。
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 static void main(String... args) {
if (args.length != 4) {
err.format("Usage: java Deet <classname> <langauge> <country> <variant>%n");
return;
}
try {
Class<?> c = Class.forName(args[0]);
Object t = c.newInstance();
Method[] allMethods = c.getDeclaredMethods();
for (Method m : allMethods) {
String mname = m.getName();
if (!mname.startsWith("test")
|| (m.getGenericReturnType() != boolean.class)) {
continue;
}
Type[] pType = m.getGenericParameterTypes();
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);
// Handle any exceptions thrown by method to be invoked.
} 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();
}
}
}
Deet
调用getDeclaredMethods()
,它将返回类中明确声明的所有方法。此外,使用Class.isAssignableFrom()
来确定定位方法的参数是否与所需调用兼容。从技术上讲,代码可以测试以下语句是否为true
,因为Locale
是final
的:
Locale.class == pType[0].getClass()
然而,Class.isAssignableFrom()
更通用。
$ *java Deet Deet ja JP JP*
invoking testDeet()
Locale = Japanese (Japan,JP),
ISO Language Code = jpn
testDeet() returned true
代码语言:javascript复制$ *java Deet Deet xx XX XX*
invoking testDeet()
invocation of testDeet failed:
Couldn't find 3-letter language code for xx
首先,请注意只有testDeet()
符合代码强制执行的声明限制。接下来,当testDeet()
传递无效参数时,它会抛出一个未经检查的java.util.MissingResourceException
。在反射中,对于已检查和未检查的异常处理没有区别。它们都被包装在一个InvocationTargetException
中。
调用具有可变数量参数的方法
Method.invoke()
可用于向方法传递可变数量的参数。要理解的关键概念是,可变参数的方法被实现为如果可变参数被打包在一个数组中。
InvokeMain
示例演示了如何调用任何类中的main()
入口点,并在运行时传递一组参数。
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();
}
}
}
首先,为了找到main()
方法,代码会搜索一个名为"main"的类,该类有一个参数,参数是一个String
数组。由于main()
是static
的,null
是传递给Method.invoke()
的第一个参数。第二个参数是要传递的参数数组。
$ *java InvokeMain Deet Deet ja JP JP*
invoking Deet.main()
invoking testDeet()
Locale = Japanese (Japan,JP),
ISO Language Code = jpn
testDeet() returned true
故障排除
原文:
docs.oracle.com/javase/tutorial/reflect/member/methodTrouble.html
本节包含开发人员在使用反射定位、调用或获取方法时可能遇到的问题示例。
由于类型擦除导致的 NoSuchMethodException
MethodTrouble
示例说明了当代码在类中搜索特定方法时未考虑类型擦除时会发生什么。
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();
}
}
}
代码语言:javascript复制$ *java MethodTrouble lookup java.lang.Integer*
java.lang.NoSuchMethodException: MethodTrouble.lookup(java.lang.Integer)
at java.lang.Class.getMethod(Class.java:1605)
at MethodTrouble.main(MethodTrouble.java:12)
代码语言:javascript复制$ *java MethodTrouble lookup java.lang.Object*
Found:
public void MethodTrouble.lookup(T)
当方法声明具有泛型参数类型时,编译器将使用其上界替换泛型类型,在本例中,T
的上界为Object
。因此,当代码搜索lookup(Integer)
时,尽管MethodTrouble
的实例是如下创建的,但不会找到方法:
Class<?> c = (new MethodTrouble<Integer>()).getClass();
搜索lookup(Object)
成功,如预期。
$ *java MethodTrouble find java.lang.Integer*
Found:
public void MethodTrouble.find(java.lang.Integer)
$ *java MethodTrouble find java.lang.Object*
java.lang.NoSuchMethodException: MethodTrouble.find(java.lang.Object)
at java.lang.Class.getMethod(Class.java:1605)
at MethodTrouble.main(MethodTrouble.java:12)
在这种情况下,find()
没有泛型参数,因此getMethod()
搜索的参数类型必须完全匹配。
提示: 在搜索方法时,始终传递参数化类型的上界。
调用方法时的 IllegalAccessException
如果尝试调用private
或其他不可访问方法,则会抛出IllegalAccessException
。
MethodTroubleAgain
示例展示了尝试在另一个类中调用私有方法导致的典型堆栈跟踪。
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); // solution
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();
}
}
}
异常抛出的堆栈跟踪如下。
代码语言:javascript复制$ *java MethodTroubleAgain*
java.lang.IllegalAccessException: Class MethodTroubleAgain can not access a
member of class AnotherClass with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.reflect.Method.invoke(Method.java:588)
at MethodTroubleAgain.main(MethodTroubleAgain.java:15)
提示: 存在访问限制,阻止对通常无法通过直接调用访问的方法进行反射调用。(包括但不限于在另一个类中的private
方法和在另一个私有类中的公共方法。)但是,Method
被声明为扩展AccessibleObject
,通过AccessibleObject.setAccessible()
提供了抑制此检查的能力。如果成功,则随后对该方法对象的调用不会因此问题而失败。
从 Method.invoke()中抛出的 IllegalArgumentException
Method.invoke()
已经被改造成为可变参数方法。这是一个巨大的便利,但可能会导致意外行为。MethodTroubleToo
示例展示了Method.invoke()
可能产生混乱结果的各种方式。
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); // works
break;
case 1:
m.invoke(mtt, null); // works (expect compiler warning)
break;
case 2:
Object arg2 = null;
m.invoke(mtt, arg2); // IllegalArgumentException
break;
case 3:
m.invoke(mtt, new Object[0]); // works
break;
case 4:
Object arg4 = new Object[0];
m.invoke(mtt, arg4); // IllegalArgumentException
break;
default:
System.out.format("Test not found%n");
}
// production code should handle these exceptions more gracefully
} catch (Exception x) {
x.printStackTrace();
}
}
}
代码语言:javascript复制$ *java MethodTroubleToo 0*
PONG!
由于Method.invoke()
的所有参数都是可选的,除了第一个,当要调用的方法没有参数时,可以省略它们。
$ *java MethodTroubleToo 1*
PONG!
在这种情况下,代码生成这个编译器警告,因为null
是模棱两可的。
$ *javac MethodTroubleToo.java*
MethodTroubleToo.java:16: warning: non-varargs call of varargs method with
inexact argument type for last parameter;
m.invoke(mtt, null); // works (expect compiler warning)
^
cast to Object for a varargs call
cast to Object[] for a non-varargs call and to suppress this warning
1 warning
不可能确定null
代表空参数数组还是第一个参数为null
。
$ *java MethodTroubleToo 2*
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke
(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke
(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at MethodTroubleToo.main(MethodTroubleToo.java:21)
尽管参数为null
,但这会失败,因为类型是Object
,而ping()
不需要任何参数。
$ *java MethodTroubleToo 3*
PONG!
这能够成功是因为new Object[0]
创建了一个空数组,对于可变参数方法来说,这等同于不传递任何可选参数。
$ *java MethodTroubleToo 4*
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0
(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke
(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke
(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at MethodTroubleToo.main(MethodTroubleToo.java:28)
与前面的例子不同,如果空数组存储在一个Object
中,那么它将被视为一个Object
。这与案例 2 失败的原因相同,ping()
不期望有参数。
**提示:**当声明一个方法foo(Object... o)
时,编译器会将传递给foo()
的所有参数放入一个Object
类型的数组中。foo()
的实现与声明为foo(Object[] o)
时相同。理解这一点可能有助于避免上面所示问题的类型。
调用方法失败时的 InvocationTargetException
InvocationTargetException
包装了调用方法对象时产生的所有异常(已检查和未检查)。MethodTroubleReturns
示例展示了如何检索被调用方法抛出的原始异常。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MethodTroubleReturns {
private void drinkMe(int liters) {
if (liters < 0)
throw new IllegalArgumentException("I can't drink a negative amount of liquid");
}
public static void main(String... args) {
try {
MethodTroubleReturns mtr = new MethodTroubleReturns();
Class<?> c = mtr.getClass();
Method m = c.getDeclaredMethod("drinkMe", int.class);
m.invoke(mtr, -1);
// production code should handle these exceptions more gracefully
} catch (InvocationTargetException x) {
Throwable cause = x.getCause();
System.err.format("drinkMe() failed: %s%n", cause.getMessage());
} catch (Exception x) {
x.printStackTrace();
}
}
}
代码语言:javascript复制$ *java MethodTroubleReturns*
drinkMe() failed: I can't drink a negative amount of liquid
**提示:**如果抛出InvocationTargetException
,则方法已被调用。诊断问题的方法与直接调用方法并通过getCause()
检索到的异常相同。此异常并不表示反射包或其使用存在问题。
构造函数
原文:
docs.oracle.com/javase/tutorial/reflect/member/ctor.html
构造函数 用于创建一个属于类的实例的对象。通常在调用方法或访问字段之前执行初始化类所需的操作。构造函数不会被继承。
与方法类似,反射提供了 API 来发现和检索类的构造函数,并获取声明信息,如修饰符、参数、注解和抛出的异常。还可以使用指定的构造函数创建类的新实例。在处理构造函数时使用的关键类是Class
和 java.lang.reflect.Constructor
。涵盖了涉及构造函数的常见操作的以下部分:
- 查找构造函数 说明了如何检索具有特定参数的构造函数
- 检索和解析构造函数修饰符 展示了如何获取构造函数声明的修饰符以及有关构造函数的其他信息
- 创建新的类实例 展示了如何通过调用其构造函数实例化对象的实例
- 故障排除 描述了在查找或调用构造函数时可能遇到的常见错误
查找构造函数
原文:
docs.oracle.com/javase/tutorial/reflect/member/ctorLocation.html
构造函数声明包括名称、修饰符、参数和可抛出异常列表。java.lang.reflect.Constructor
类提供了获取这些信息的方法。
ConstructorSift
示例演示了如何搜索一个类的声明构造函数中具有给定类型参数的构造函数。
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import static java.lang.System.out;
public class ConstructorSift {
public static 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("|%s[%d]: %s%n", ch,
"GenericParameterType", j, gpType[j]);
}
break;
}
}
}
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
Method.getGenericParameterTypes()
将在类文件中的签名属性中查找(如果存在)。如果属性不可用,则会回退到Method.getParameterType()
,这个方法在引入泛型之前并未更改。其他以getGeneric*Foo*()
命名的反射方法也是类似实现的。Method.get*Types()
返回值的语法在Class.getName()
中有描述。
这里是所有在java.util.Formatter
中具有Locale
参数的构造函数的输出。
$ *java ConstructorSift java.util.Formatter java.util.Locale*
public
java.util.Formatter(java.io.OutputStream,java.lang.String,java.util.Locale)
throws java.io.UnsupportedEncodingException
GenericParameterType[0]: class java.io.OutputStream
GenericParameterType[1]: class java.lang.String
*GenericParameterType[2]: class java.util.Locale
public java.util.Formatter(java.lang.String,java.lang.String,java.util.Locale)
throws java.io.FileNotFoundException,java.io.UnsupportedEncodingException
GenericParameterType[0]: class java.lang.String
GenericParameterType[1]: class java.lang.String
*GenericParameterType[2]: class java.util.Locale
public java.util.Formatter(java.lang.Appendable,java.util.Locale)
GenericParameterType[0]: interface java.lang.Appendable
*GenericParameterType[1]: class java.util.Locale
public java.util.Formatter(java.util.Locale)
*GenericParameterType[0]: class java.util.Locale
public java.util.Formatter(java.io.File,java.lang.String,java.util.Locale)
throws java.io.FileNotFoundException,java.io.UnsupportedEncodingException
GenericParameterType[0]: class java.io.File
GenericParameterType[1]: class java.lang.String
*GenericParameterType[2]: class java.util.Locale
下一个示例输出演示了如何在String
中搜索类型为char[]
的参数。
$ *java ConstructorSift java.lang.String "[C"*
java.lang.String(int,int,char[])
GenericParameterType[0]: int
GenericParameterType[1]: int
*GenericParameterType[2]: class [C
public java.lang.String(char[],int,int)
*GenericParameterType[0]: class [C
GenericParameterType[1]: int
GenericParameterType[2]: int
public java.lang.String(char[])
*GenericParameterType[0]: class [C
表达接受Class.forName()
的引用和基本类型数组的语法在Class.getName()
中有描述。请注意,第一个列出的构造函数是包私有
的,而不是public
的。它被返回是因为示例代码使用了Class.getDeclaredConstructors()
而不是Class.getConstructors()
,后者只返回public
构造函数。
这个示例表明,搜索可变参数的参数(具有可变数量的参数)需要使用数组语法:
代码语言:javascript复制$ *java ConstructorSift java.lang.ProcessBuilder "[Ljava.lang.String;"*
public java.lang.ProcessBuilder(java.lang.String[])
*GenericParameterType[0]: class [Ljava.lang.String;
这是源代码中ProcessBuilder
构造函数的实际声明:
public ProcessBuilder(String... command)
参数表示为类型为java.lang.String
的单维数组。可以通过调用Constructor.isVarArgs()
来区分明确为java.lang.String
数组的参数。
最后一个示例报告了已声明具有泛型参数类型的构造函数的输出:
代码语言:javascript复制$ *java ConstructorSift java.util.HashMap java.util.Map*
public java.util.HashMap(java.util.Map<? extends K, ? extends V>)
*GenericParameterType[0]: java.util.Map<? extends K, ? extends V>
与方法类似,可以以类似的方式检索构造函数的异常类型。有关更多详细信息,请参见MethodSpy
示例中描述的获取方法类型信息部分。
检索和解析构造函数修饰符
原文:
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(); }
}
}
代码语言:javascript复制$ *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 系统的示例输出:
代码语言:javascript复制$ *java ConsoleCharset*
Console charset : ISO-8859-1
Charset.defaultCharset() : ISO-8859-1
Windows 系统的示例输出:
代码语言:javascript复制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();
}
}
}
代码语言:javascript复制$ *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());
}
}
}
代码语言:javascript复制$ *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();
}
}
}
代码语言:javascript复制$ *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)
如果开发人员错误地认为反射会自动装箱或拆箱类型,则可能会出现此异常。装箱(将原始类型转换为引用类型)仅在编译期间发生。在反射中没有机会进行此操作,因此在定位构造函数时必须使用特定类型。
代码语言:javascript复制$ *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();
}
}
}
代码语言:javascript复制$ *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()
来抑制此检查的能力。
课程:数组和枚举类型
原文:
docs.oracle.com/javase/tutorial/reflect/special/index.html
从 Java 虚拟机的角度看,数组和枚举类型(或枚举)只是类。许多 Class
中的方法可以用于它们。反射为数组和枚举提供了一些特定的 API。本课程使用一系列代码示例来描述如何区分这些对象与其他类,并对其进行操作。还将检查各种错误。
数组
数组有一个组件类型和一个长度(长度不是类型的一部分)。数组可以整体操作,也可以逐个组件操作。反射为后者提供了 java.lang.reflect.Array
类。
- 识别数组类型 描述了如何确定类成员是否是数组类型的字段
- 创建新数组 演示了如何创建具有简单和复杂组件类型的新数组实例
- 获取和设置数组及其组件 展示了如何访问数组类型的字段以及单独访问数组元素
- 故障排除 包括常见错误和编程误解
枚举类型
在反射代码中,枚举类型与普通类非常相似。Class.isEnum()
可以告诉一个 Class
是否表示一个 enum
。Class.getEnumConstants()
可以检索在枚举中定义的枚举常量。java.lang.reflect.Field.isEnumConstant()
表示一个字段是否是一个枚举类型。
- 检查枚举 演示了如何检索枚举的常量以及任何其他字段、构造函数和方法
- 使用枚举类型获取和设置字段 展示了如何设置和获取具有枚举常量值的字段
- 故障排除 描述了与枚举相关的常见错误
数组
原文:
docs.oracle.com/javase/tutorial/reflect/special/array.html
一个数组是引用类型的对象,包含固定数量的相同类型的组件;数组的长度是不可变的。创建数组的实例需要知道长度和组件类型。每个组件可以是原始类型(如byte
、int
或double
),引用类型(如String
、Object
或java.nio.CharBuffer
),或者是数组。多维数组实际上只是包含数组类型组件的数组。
数组在 Java 虚拟机中实现。数组上的唯一方法是从Object
继承的方法。数组的长度不是其类型的一部分;数组有一个length
字段,可以通过java.lang.reflect.Array.getLength()
访问。
反射提供了访问数组类型和数组组件类型、创建新数组以及检索和设置数组组件值的方法。以下各节包括对数组上常见操作的示例:
- 识别数组类型描述了如何确定类成员是否是数组类型的字段
- 创建新数组演示了如何创建具有简单和复杂组件类型的新数组实例
- 获取和设置数组及其组件展示了如何访问数组类型的字段以及单独访问数组元素
- 故障排除涵盖了常见错误和编程误解
所有这些操作都通过java.lang.reflect.Array
中的static
方法支持。
识别数组类型
原文:
docs.oracle.com/javase/tutorial/reflect/special/arrayComponents.html
可以通过调用Class.isArray()
来识别数组类型。要获取一个Class
,请使用本教程中检索类对象部分描述的方法之一。
ArrayFind
示例标识了命名类中的数组类型字段,并报告了每个字段的组件类型。
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import static java.lang.System.out;
public class ArrayFind {
public static void main(String... args) {
boolean found = false;
try {
Class<?> cls = Class.forName(args[0]);
Field[] flds = cls.getDeclaredFields();
for (Field f : flds) {
Class<?> c = f.getType();
if (c.isArray()) {
found = true;
out.format("%s%n"
" Field: %s%n"
" Type: %s%n"
" Component Type: %s%n",
f, f.getName(), c, c.getComponentType());
}
}
if (!found) {
out.format("No array fields%n");
}
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
Class.get*Type()
返回值的语法在Class.getName()
中有描述。类型名称开头的’[
'字符的数量表示数组的维度(即嵌套的深度)。
输出示例如下。用户输入用斜体表示。一个原始类型为byte
的数组:
$*java ArrayFind java.nio.ByteBuffer*
final byte[] java.nio.ByteBuffer.hb
Field: hb
Type: class [B
Component Type: byte
一个引用类型为StackTraceElement
的数组:
$ *java ArrayFind java.lang.Throwable*
private java.lang.StackTraceElement[] java.lang.Throwable.stackTrace
Field: stackTrace
Type: class [Ljava.lang.StackTraceElement;
Component Type: class java.lang.StackTraceElement
predefined
是一个引用类型的一维数组java.awt.Cursor
,而cursorProperties
是一个引用类型的二维数组String
:
$ *java ArrayFind java.awt.Cursor*
protected static java.awt.Cursor[] java.awt.Cursor.predefined
Field: predefined
Type: class [Ljava.awt.Cursor;
Component Type: class java.awt.Cursor
static final java.lang.String[][] java.awt.Cursor.cursorProperties
Field: cursorProperties
Type: class [[Ljava.lang.String;
Component Type: class [Ljava.lang.String;
创建新数组
原文:
docs.oracle.com/javase/tutorial/reflect/special/arrayInstance.html
就像非反射代码一样,反射支持通过java.lang.reflect.Array.newInstance()
动态创建任意类型和维度的数组的能力。考虑ArrayCreator
,一个能够动态创建数组的基本解释器。将解析的语法如下:
fully_qualified_class_name variable_name[] =
{ val1, val2, val3, ... }
假设fully_qualified_class_name
代表一个具有接受单个String
参数的构造函数的类。数组的维度由提供的值的数量确定。以下示例将构造一个fully_qualified_class_name
数组的实例,并用val1
、val2
等给定的实例填充其值。(此示例假定熟悉Class.getConstructor()
和java.lang.reflect.Constructor.newInstance()
。有关Constructor
的反射 API 的讨论,请参阅本教程的 Creating New Class Instances 部分。)
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.Arrays;
import static java.lang.System.out;
public class ArrayCreator {
private static String s = "java.math.BigInteger bi[] = { 123, 234, 345 }";
private static Pattern p = Pattern.compile("^\s*(\S )\s*\w \[\].*\{\s*([^}] )\s*\}");
public static void main(String... args) {
Matcher m = p.matcher(s);
if (m.find()) {
String cName = m.group(1);
String[] cVals = m.group(2).split("[\s,] ");
int n = cVals.length;
try {
Class<?> c = Class.forName(cName);
Object o = Array.newInstance(c, n);
for (int i = 0; i < n; i ) {
String v = cVals[i];
Constructor ctor = c.getConstructor(String.class);
Object val = ctor.newInstance(v);
Array.set(o, i, val);
}
Object[] oo = (Object[])o;
out.format("%s[] = %s%n", cName, Arrays.toString(oo));
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
}
}
}
}
代码语言:javascript复制$ *java ArrayCreator*
java.math.BigInteger [] = [123, 234, 345]
上面的示例展示了可能希望通过反射创建数组的一种情况;即如果组件类型直到运行时才知道。在这种情况下,代码使用Class.forName()
获取所需组件类型的类,然后调用特定的构造函数来初始化数组的每个组件,然后设置相应的数组值。
获取和设置数组及其组件
原文:
docs.oracle.com/javase/tutorial/reflect/special/arraySetGet.html
就像在非反射代码中一样,可以整体设置或逐个组件设置或检索数组字段。要一次设置整个数组,请使用java.lang.reflect.Field.set(Object obj, Object value)
。要检索整个数组,请使用Field.get(Object)
。可以使用java.lang.reflect.Array
中的方法来设置或检索单个组件。
Array
提供了形式为set*Foo*()
和get*Foo*()
的方法,用于设置和获取任何原始类型的组件。例如,可以使用Array.setInt(Object array, int index, int value)
设置int
数组的组件,并可以使用Array.getInt(Object array, int index)
检索它。
这些方法支持自动扩宽数据类型。因此,Array.getShort()
可以用于设置int
数组的值,因为一个 16 位的short
可以被扩宽为 32 位的int
而不会丢失数据;另一方面,在int
数组上调用Array.setLong()
将导致抛出IllegalArgumentException
,因为 64 位的long
不能被缩小为 32 位的int
而不丢失信息。无论传递的实际值是否能够准确表示为目标数据类型,这都是正确的。Java 语言规范,Java SE 7 版,章节Widening Primitive Conversion和Narrowing Primitive Conversion包含了对扩宽和缩窄转换的完整讨论。
引用类型数组(包括数组的数组)的组件使用Array.set(Object array, int index, int value)
和Array.get(Object array, int index)
进行设置和检索。
设置类型为数组的字段
GrowBufferedReader
示例演示了如何替换类型为数组的字段的值。在这种情况下,代码将java.io.BufferedReader
的后备数组替换为更大的数组。(这假设原始BufferedReader
的创建在不可修改的代码中;否则,可以简单地使用接受输入缓冲区大小的替代构造函数BufferedReader(java.io.Reader in, int size)
。)
import java.io.BufferedReader;
import java.io.CharArrayReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import static java.lang.System.out;
public class GrowBufferedReader {
private static final int srcBufSize = 10 * 1024;
private static char[] src = new char[srcBufSize];
static {
src[srcBufSize - 1] = 'x';
}
private static CharArrayReader car = new CharArrayReader(src);
public static void main(String... args) {
try {
BufferedReader br = new BufferedReader(car);
Class<?> c = br.getClass();
Field f = c.getDeclaredField("cb");
// cb is a private field
f.setAccessible(true);
char[] cbVal = char[].class.cast(f.get(br));
char[] newVal = Arrays.copyOf(cbVal, cbVal.length * 2);
if (args.length > 0 && args[0].equals("grow"))
f.set(br, newVal);
for (int i = 0; i < srcBufSize; i )
br.read();
// see if the new backing array is being used
if (newVal[srcBufSize - 1] == src[srcBufSize - 1])
out.format("Using new backing array, size=%d%n", newVal.length);
else
out.format("Using original backing array, size=%d%n", cbVal.length);
// production code should handle these exceptions more gracefully
} catch (FileNotFoundException x) {
x.printStackTrace();
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (IOException x) {
x.printStackTrace();
}
}
}
代码语言:javascript复制$ *java GrowBufferedReader grow*
Using new backing array, size=16384
$ *java GrowBufferedReader*
Using original backing array, size=8192
请注意,上述示例使用了数组实用方法java.util.Arrays.copyOf)
。java.util.Arrays
包含许多在操作数组时方便的方法。
访问多维数组的元素
多维数组简单来说就是嵌套数组。二维数组是数组的数组。三维数组是二维数组的数组,依此类推。CreateMatrix
示例演示了如何使用反射创建和初始化多维数组。
import java.lang.reflect.Array;
import static java.lang.System.out;
public class CreateMatrix {
public static void main(String... args) {
Object matrix = Array.newInstance(int.class, 2, 2);
Object row0 = Array.get(matrix, 0);
Object row1 = Array.get(matrix, 1);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
for (int i = 0; i < 2; i )
for (int j = 0; j < 2; j )
out.format("matrix[%d][%d] = %d%n", i, j, ((int[][])matrix)[i][j]);
}
}
代码语言:javascript复制$ *java CreateMatrix*
matrix[0][0] = 1
matrix[0][1] = 2
matrix[1][0] = 3
matrix[1][1] = 4
通过使用以下代码片段也可以获得相同的结果:
代码语言:javascript复制Object matrix = Array.newInstance(int.class, 2);
Object row0 = Array.newInstance(int.class, 2);
Object row1 = Array.newInstance(int.class, 2);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
Array.set(matrix, 0, row0);
Array.set(matrix, 1, row1);
可变参数Array.newInstance(Class<?> componentType, int... dimensions)
提供了一个方便的方式来创建多维数组,但组件仍然需要使用多维数组是嵌套数组的原则进行初始化。(反射不提供用于此目的的多个索引get
/set
方法。)
故障排除
原文:
docs.oracle.com/javase/tutorial/reflect/special/arrayTrouble.html
以下示例展示了在操作数组时可能发生的典型错误。
由于不可转换的类型导致IllegalArgumentException
ArrayTroubleAgain
示例将生成一个IllegalArgumentException
。调用Array.setInt()
来设置一个Integer
类型的组件,其值为基本类型int
。在非反射等效的ary[0] = 1
中,编译器会将值1
转换(或装箱)为引用类型new Integer(1)
,以便其类型检查接受该语句。在使用反射时,类型检查仅在运行时发生,因此没有机会将值装箱。
import java.lang.reflect.Array;
import static java.lang.System.err;
public class ArrayTroubleAgain {
public static void main(String... args) {
Integer[] ary = new Integer[2];
try {
Array.setInt(ary, 0, 1); // IllegalArgumentException
// production code should handle these exceptions more gracefully
} catch (IllegalArgumentException x) {
err.format("Unable to box%n");
} catch (ArrayIndexOutOfBoundsException x) {
x.printStackTrace();
}
}
}
代码语言:javascript复制$ *java ArrayTroubleAgain*
Unable to box
要消除此异常,有问题的行应该被以下调用替换Array.set(Object array, int index, Object value)
:
Array.set(ary, 0, new Integer(1));
提示: 当使用反射设置或获取数组组件时,编译器无法执行装箱。它只能转换与Class.isAssignableFrom()
规范描述的相关类型。该示例预计会失败,因为isAssignableFrom()
在此测试中将返回false
,可以用程序验证特定转换是否可能:
Integer.class.isAssignableFrom(int.class) == false
同样,在反射中从基本类型到引用类型的自动转换也是不可能的。
代码语言:javascript复制int.class.isAssignableFrom(Integer.class) == false
对空数组的ArrayIndexOutOfBoundsException
ArrayTrouble
示例说明了如果尝试访问长度为零的数组元素将会发生的错误:
import java.lang.reflect.Array;
import static java.lang.System.out;
public class ArrayTrouble {
public static void main(String... args) {
Object o = Array.newInstance(int.class, 0);
int[] i = (int[])o;
int[] j = new int[0];
out.format("i.length = %d, j.length = %d, args.length = %d%n",
i.length, j.length, args.length);
Array.getInt(o, 0); // ArrayIndexOutOfBoundsException
}
}
代码语言:javascript复制$ *java ArrayTrouble*
i.length = 0, j.length = 0, args.length = 0
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
at java.lang.reflect.Array.getInt(Native Method)
at ArrayTrouble.main(ArrayTrouble.java:11)
提示: 可以有没有元素的数组(空数组)。在常见代码中只有少数情况下会看到它们,但它们可能会在反射中无意中出现。当然,无法设置/获取空数组的值,因为会抛出ArrayIndexOutOfBoundsException
。
如果尝试缩小范围会导致IllegalArgumentException
ArrayTroubleToo
示例包含的代码会失败,因为它尝试执行一个可能会丢失数据的操作:
import java.lang.reflect.Array;
import static java.lang.System.out;
public class ArrayTroubleToo {
public static void main(String... args) {
Object o = new int[2];
Array.setShort(o, 0, (short)2); // widening, succeeds
Array.setLong(o, 1, 2L); // narrowing, fails
}
}
代码语言:javascript复制$ *java ArrayTroubleToo*
Exception in thread "main" java.lang.IllegalArgumentException: argument type
mismatch
at java.lang.reflect.Array.setLong(Native Method)
at ArrayTroubleToo.main(ArrayTroubleToo.java:9)
提示: Array.set*()
和 Array.get*()
方法将执行自动扩展转换,但如果尝试进行缩小转换,则会抛出 IllegalArgumentException
。有关扩展和缩小转换的完整讨论,请参阅Java 语言规范,Java SE 7 版,分别查看Widening Primitive Conversion和Narrowing Primitive Conversion部分。
枚举类型
原文:
docs.oracle.com/javase/tutorial/reflect/special/enum.html
枚举是一种语言构造,用于定义类型安全的枚举,当需要固定一组命名值时可以使用。所有枚举隐式扩展 java.lang.Enum
。枚举可以包含一个或多个枚举常量,这些常量定义了枚举类型的唯一实例。枚举声明定义了一个枚举类型,与类非常相似,可以具有字段、方法和构造函数等成员(有一些限制)。
由于枚举是类,反射不需要定义一个显式的java.lang.reflect.Enum
类。枚举特定的反射 API 只有 Class.isEnum()
、Class.getEnumConstants()
和 java.lang.reflect.Field.isEnumConstant()
。涉及枚举的大多数反射操作与任何其他类或成员相同。例如,枚举常量被实现为枚举上的public static final
字段。以下部分展示了如何在枚举中使用 Class
和 java.lang.reflect.Field
。
- 检查枚举 说明了如何检索枚举的常量以及任何其他字段、构造函数和方法
- 使用枚举类型获取和设置字段 展示了如何使用枚举常量值设置和获取字段
- 故障排除描述了与枚举相关的常见错误
有关枚举的介绍,请参阅 枚举类型 课程。
检查枚举
原文:
docs.oracle.com/javase/tutorial/reflect/special/enumMembers.html
反射提供了三个特定于枚举的 API:
Class.isEnum()
表示此类是否表示枚举类型
Class.getEnumConstants()
检索由枚举定义的枚举常量列表,按照它们声明的顺序
java.lang.reflect.Field.isEnumConstant()
表示此字段是否表示枚举类型的元素
有时需要动态检索枚举常量的列表;在非反射代码中,可以通过在枚举上调用隐式声明的静态方法 values()
来实现这一点。 如果枚举类型的实例不可用,则获取可能值列表的唯一方法是调用 Class.getEnumConstants()
,因为无法实例化枚举类型。
给定完全限定名称,EnumConstants
示例显示如何使用 Class.getEnumConstants()
检索枚举中常量的有序列表。
import java.util.Arrays;
import static java.lang.System.out;
enum Eon { HADEAN, ARCHAEAN, PROTEROZOIC, PHANEROZOIC }
public class EnumConstants {
public static void main(String... args) {
try {
Class<?> c = (args.length == 0 ? Eon.class : Class.forName(args[0]));
out.format("Enum name: %s%nEnum constants: %s%n",
c.getName(), Arrays.asList(c.getEnumConstants()));
if (c == Eon.class)
out.format(" Eon.values(): %s%n",
Arrays.asList(Eon.values()));
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
输出示例如下。 用户输入以斜体显示。
代码语言:javascript复制$ *java EnumConstants java.lang.annotation.RetentionPolicy*
Enum name: java.lang.annotation.RetentionPolicy
Enum constants: [SOURCE, CLASS, RUNTIME]
代码语言:javascript复制$ *java EnumConstants java.util.concurrent.TimeUnit*
Enum name: java.util.concurrent.TimeUnit
Enum constants: [NANOSECONDS, MICROSECONDS,
MILLISECONDS, SECONDS,
MINUTES, HOURS, DAYS]
该示例还表明,通过调用 Class.getEnumConstants()
返回的值与在枚举类型上调用 values()
返回的值相同。
$ *java EnumConstants*
Enum name: Eon
Enum constants: [HADEAN, ARCHAEAN,
PROTEROZOIC, PHANEROZOIC]
Eon.values(): [HADEAN, ARCHAEAN,
PROTEROZOIC, PHANEROZOIC]
由于枚举是类,可以使用本教程中描述的字段、方法和构造函数部分中描述的相同反射 API 获取其他信息。 EnumSpy
代码示例说明了如何使用这些 API 获取有关枚举声明的其他信息。 该示例使用 Class.isEnum()
来限制要检查的类集。 它还使用 Field.isEnumConstant()
来区分枚举声明中的枚举常量和其他字段(并非所有字段都是枚举常量)。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Member;
import java.util.List;
import java.util.ArrayList;
import static java.lang.System.out;
public class EnumSpy {
private static final String fmt = " s: %s %s%n";
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
if (!c.isEnum()) {
out.format("%s is not an enum type%n", c);
return;
}
out.format("Class: %s%n", c);
Field[] flds = c.getDeclaredFields();
List<Field> cst = new ArrayList<Field>(); // enum constants
List<Field> mbr = new ArrayList<Field>(); // member fields
for (Field f : flds) {
if (f.isEnumConstant())
cst.add(f);
else
mbr.add(f);
}
if (!cst.isEmpty())
print(cst, "Constant");
if (!mbr.isEmpty())
print(mbr, "Field");
Constructor[] ctors = c.getDeclaredConstructors();
for (Constructor ctor : ctors) {
out.format(fmt, "Constructor", ctor.toGenericString(),
synthetic(ctor));
}
Method[] mths = c.getDeclaredMethods();
for (Method m : mths) {
out.format(fmt, "Method", m.toGenericString(),
synthetic(m));
}
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
private static void print(List<Field> lst, String s) {
for (Field f : lst) {
out.format(fmt, s, f.toGenericString(), synthetic(f));
}
}
private static String synthetic(Member m) {
return (m.isSynthetic() ? "[ synthetic ]" : "");
}
}
代码语言:javascript复制$ *java EnumSpy java.lang.annotation.RetentionPolicy*
Class: class java.lang.annotation.RetentionPolicy
Constant: public static final java.lang.annotation.RetentionPolicy
java.lang.annotation.RetentionPolicy.SOURCE
Constant: public static final java.lang.annotation.RetentionPolicy
java.lang.annotation.RetentionPolicy.CLASS
Constant: public static final java.lang.annotation.RetentionPolicy
java.lang.annotation.RetentionPolicy.RUNTIME
Field: private static final java.lang.annotation.RetentionPolicy[]
java.lang.annotation.RetentionPolicy. [ synthetic ]
Constructor: private java.lang.annotation.RetentionPolicy()
Method: public static java.lang.annotation.RetentionPolicy[]
java.lang.annotation.RetentionPolicy.values()
Method: public static java.lang.annotation.RetentionPolicy
java.lang.annotation.RetentionPolicy.valueOf(java.lang.String)
输出显示,java.lang.annotation.RetentionPolicy
的声明仅包含三个枚举常量。枚举常量暴露为public static final
字段。字段、构造函数和方法是由编译器生成的。$VALUES
字段与values()
方法的实现有关。
**注意:**出于各种原因,包括支持枚举类型的演变,枚举常量的声明顺序很重要。Class.getFields()
和Class.getDeclaredFields()
不能保证返回值的顺序与声明源代码中的顺序匹配。如果应用程序需要排序,请使用Class.getEnumConstants()
。
对于java.util.concurrent.TimeUnit
的输出显示,更复杂的枚举是可能的。这个类包括几个方法以及额外声明为static final
的字段,这些字段不是枚举常量。
$ java EnumSpy java.util.concurrent.TimeUnit
Class: class java.util.concurrent.TimeUnit
Constant: public static final java.util.concurrent.TimeUnit
java.util.concurrent.TimeUnit.NANOSECONDS
Constant: public static final java.util.concurrent.TimeUnit
java.util.concurrent.TimeUnit.MICROSECONDS
Constant: public static final java.util.concurrent.TimeUnit
java.util.concurrent.TimeUnit.MILLISECONDS
Constant: public static final java.util.concurrent.TimeUnit
java.util.concurrent.TimeUnit.SECONDS
Constant: public static final java.util.concurrent.TimeUnit
java.util.concurrent.TimeUnit.MINUTES
Constant: public static final java.util.concurrent.TimeUnit
java.util.concurrent.TimeUnit.HOURS
Constant: public static final java.util.concurrent.TimeUnit
java.util.concurrent.TimeUnit.DAYS
Field: static final long java.util.concurrent.TimeUnit.C0
Field: static final long java.util.concurrent.TimeUnit.C1
Field: static final long java.util.concurrent.TimeUnit.C2
Field: static final long java.util.concurrent.TimeUnit.C3
Field: static final long java.util.concurrent.TimeUnit.C4
Field: static final long java.util.concurrent.TimeUnit.C5
Field: static final long java.util.concurrent.TimeUnit.C6
Field: static final long java.util.concurrent.TimeUnit.MAX
Field: private static final java.util.concurrent.TimeUnit[]
java.util.concurrent.TimeUnit. [ synthetic ]
Constructor: private java.util.concurrent.TimeUnit()
Constructor: java.util.concurrent.TimeUnit
(java.lang.String,int,java.util.concurrent.TimeUnit)
[ synthetic ]
Method: public static java.util.concurrent.TimeUnit
java.util.concurrent.TimeUnit.valueOf(java.lang.String)
Method: public static java.util.concurrent.TimeUnit[]
java.util.concurrent.TimeUnit.values()
Method: public void java.util.concurrent.TimeUnit.sleep(long)
throws java.lang.InterruptedException
Method: public long java.util.concurrent.TimeUnit.toNanos(long)
Method: public long java.util.concurrent.TimeUnit.convert
(long,java.util.concurrent.TimeUnit)
Method: abstract int java.util.concurrent.TimeUnit.excessNanos
(long,long)
Method: public void java.util.concurrent.TimeUnit.timedJoin
(java.lang.Thread,long) throws java.lang.InterruptedException
Method: public void java.util.concurrent.TimeUnit.timedWait
(java.lang.Object,long) throws java.lang.InterruptedException
Method: public long java.util.concurrent.TimeUnit.toDays(long)
Method: public long java.util.concurrent.TimeUnit.toHours(long)
Method: public long java.util.concurrent.TimeUnit.toMicros(long)
Method: public long java.util.concurrent.TimeUnit.toMillis(long)
Method: public long java.util.concurrent.TimeUnit.toMinutes(long)
Method: public long java.util.concurrent.TimeUnit.toSeconds(long)
Method: static long java.util.concurrent.TimeUnit.x(long,long,long)
使用枚举类型获取和设置字段
原文:
docs.oracle.com/javase/tutorial/reflect/special/enumSetGet.html
存储枚举的字段与任何其他引用类型一样设置和检索,使用Field.set()
和Field.get()
。有关访问字段的更多信息,请参阅本教程的 Fields 部分。
考虑一个需要在服务器应用程序中动态修改跟踪级别的应用程序,通常在运行时不允许此更改。假设服务器对象的实例可用。SetTrace
示例展示了代码如何将枚举的String
表示转换为枚举类型,并检索和设置存储枚举的字段的值。
import java.lang.reflect.Field;
import static java.lang.System.out;
enum TraceLevel { OFF, LOW, MEDIUM, HIGH, DEBUG }
class MyServer {
private TraceLevel level = TraceLevel.OFF;
}
public class SetTrace {
public static void main(String... args) {
TraceLevel newLevel = TraceLevel.valueOf(args[0]);
try {
MyServer svr = new MyServer();
Class<?> c = svr.getClass();
Field f = c.getDeclaredField("level");
f.setAccessible(true);
TraceLevel oldLevel = (TraceLevel)f.get(svr);
out.format("Original trace level: %s%n", oldLevel);
if (oldLevel != newLevel) {
f.set(svr, newLevel);
out.format(" New trace level: %s%n", f.get(svr));
}
// production code should handle these exceptions more gracefully
} catch (IllegalArgumentException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (NoSuchFieldException x) {
x.printStackTrace();
}
}
}
由于枚举常量是单例,可以使用==
和!=
运算符来比较相同类型的枚举常量。
$ *java SetTrace OFF*
Original trace level: OFF
$ *java SetTrace DEBUG*
Original trace level: OFF
New trace level: DEBUG
故障排除
原文:
docs.oracle.com/javase/tutorial/reflect/special/enumTrouble.html
以下示例展示了在使用枚举类型时可能遇到的问题。
尝试实例化枚举类型时出现 IllegalArgumentException
正如前面提到的,实例化枚举类型是被禁止的。EnumTrouble
示例尝试这样做。
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import static java.lang.System.out;
enum Charge {
POSITIVE, NEGATIVE, NEUTRAL;
Charge() {
out.format("under construction%n");
}
}
public class EnumTrouble {
public static void main(String... args) {
try {
Class<?> c = Charge.class;
Constructor[] ctors = c.getDeclaredConstructors();
for (Constructor ctor : ctors) {
out.format("Constructor: %s%n", ctor.toGenericString());
ctor.setAccessible(true);
ctor.newInstance();
}
// production code should handle these exceptions more gracefully
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
}
}
}
代码语言:javascript复制$ *java EnumTrouble*
Constructor: private Charge()
Exception in thread "main" java.lang.IllegalArgumentException: Cannot
reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:511)
at EnumTrouble.main(EnumTrouble.java:22)
提示: 明确实例化枚举是一种编译时错误,因为这将阻止定义的枚举常量保持唯一。这种限制也在反射代码中执行。试图使用默认构造函数实例化类的代码应该首先调用Class.isEnum()
来确定该类是否为枚举。
设置具有不兼容枚举类型的字段时出现 IllegalArgumentException
存储枚举的字段应该设置为适当的枚举类型。(实际上,任何类型的字段都必须设置为兼容的类型。)EnumTroubleToo
示例会产生预期的错误。
import java.lang.reflect.Field;
enum E0 { A, B }
enum E1 { A, B }
class ETest {
private E0 fld = E0.A;
}
public class EnumTroubleToo {
public static void main(String... args) {
try {
ETest test = new ETest();
Field f = test.getClass().getDeclaredField("fld");
f.setAccessible(true);
f.set(test, E1.A); // IllegalArgumentException
// production code should handle these exceptions more gracefully
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
代码语言:javascript复制$ *java EnumTroubleToo*
Exception in thread "main" java.lang.IllegalArgumentException: Can not set E0
field ETest.fld to E1
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException
(UnsafeFieldAccessorImpl.java:146)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException
(UnsafeFieldAccessorImpl.java:150)
at sun.reflect.UnsafeObjectFieldAccessorImpl.set
(UnsafeObjectFieldAccessorImpl.java:63)
at java.lang.reflect.Field.set(Field.java:657)
at EnumTroubleToo.main(EnumTroubleToo.java:16)
提示: 严格来说,将类型为X
的字段设置为类型为Y
的值只有在以下语句成立时才能成功:
X.class.isAssignableFrom(Y.class) == true
代码可以修改以执行以下测试,以验证类型是否兼容:
代码语言:javascript复制if (f.getType().isAssignableFrom(E0.class))
// compatible
else
// expect IllegalArgumentException