Java 中文官方教程 2022 版(四十三)(2)https://developer.aliyun.com/article/1488264
获取和设置字段值
原文:
docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html
给定一个类的实例,可以使用反射来设置该类中字段的值。通常只在特殊情况下进行此操作,当通常方式无法设置值时。由于这种访问通常违反了类的设计意图,应该谨慎使用。
Book
类演示了如何设置长整型、数组和枚举字段类型的值。获取和设置其他基本类型的方法在Field
中有描述。
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 static void main(String... args) { Book book = new Book(); String fmt = "%6S: %-12s = %s%n"; try { Class<?> c = book.getClass(); Field chap = c.getDeclaredField("chapters"); out.format(fmt, "before", "chapters", book.chapters); chap.setLong(book, 12); out.format(fmt, "after", "chapters", chap.getLong(book)); 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)); Field t = c.getDeclaredField("twin"); out.format(fmt, "before", "twin", book.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(); } } }
这是相应的输出:
$ *java Book* BEFORE: chapters = 0 AFTER: chapters = 12 BEFORE: characters = [Alice, White Rabbit] AFTER: characters = [Queen, King] BEFORE: twin = DEE AFTER: twin = DUM
注意: 通过反射设置字段的值会带来一定的性能开销,因为必须执行各种操作,如验证访问权限。从运行时的角度来看,效果是相同的,操作与直接在类代码中更改值一样原子。
使用反射可能导致一些运行时优化丢失。例如,以下代码很可能会被 Java 虚拟机优化:
int x = 1; x = 2; x = 3;
使用Field.set*()
的等效代码可能不会。
故障排除
原文:
docs.oracle.com/javase/tutorial/reflect/member/fieldTrouble.html
开发人员遇到的一些常见问题及其解释和解决方法如下。
由于不可转换类型而引发的IllegalArgumentException
FieldTrouble
示例将生成一个IllegalArgumentException
。调用Field.setInt()
来设置一个引用类型为Integer
的字段,其值为原始类型。在非反射等效的Integer val = 42
中,编译器会将原始类型42
转换(或装箱)为引用类型new Integer(42)
,以便其类型检查接受该语句。在使用反射时,类型检查仅在运行时发生,因此没有机会对值进行装箱。
import java.lang.reflect.Field; 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(); } } }
$ *java FieldTrouble* Exception in thread "main" java.lang.IllegalArgumentException: Can not set java.lang.Object field FieldTrouble.val to (long)42 at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException (UnsafeFieldAccessorImpl.java:146) at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException (UnsafeFieldAccessorImpl.java:174) at sun.reflect.UnsafeObjectFieldAccessorImpl.setLong (UnsafeObjectFieldAccessorImpl.java:102) at java.lang.reflect.Field.setLong(Field.java:831) at FieldTrouble.main(FieldTrouble.java:11)
要消除此异常,问题行应替换为以下调用Field.set(Object obj, Object value)
:
f.set(ft, new Integer(43));
提示: 当使用反射设置或获取字段时,编译器没有机会执行装箱。它只能转换由Class.isAssignableFrom()
规范描述的相关类型。该示例预计会失败,因为在此测试中isAssignableFrom()
将返回false
,可用于编程验证特定转换是否可能:
Integer.class.isAssignableFrom(int.class) == false
同样,在反射中也不可能自动将原始类型转换为引用类型。
int.class.isAssignableFrom(Integer.class) == false
针对非公共字段的NoSuchFieldException
机智的读者可能会注意到,如果之前展示的FieldSpy
示例用于获取非公共字段的信息,它将失败:
$ *java FieldSpy java.lang.String count* java.lang.NoSuchFieldException: count at java.lang.Class.getField(Class.java:1519) at FieldSpy.main(FieldSpy.java:12)
提示: Class.getField()
和 Class.getFields()
方法返回由Class
对象表示的类、枚举或接口的公共成员字段。要检索在Class
中声明的所有字段(但不是继承的字段),请使用Class.getDeclaredFields()
方法。
修改最终字段时的IllegalAccessException
如果尝试获取或设置private
或其他不可访问字段的值,或者设置final
字段的值(无论其访问修饰符如何),可能会抛出IllegalAccessException
。
FieldTroubleToo
示例展示了尝试设置 final 字段时产生的堆栈跟踪类型。
import java.lang.reflect.Field; public class FieldTroubleToo { public final boolean b = true; public static void main(String... args) { FieldTroubleToo ft = new FieldTroubleToo(); try { Class<?> c = ft.getClass(); Field f = c.getDeclaredField("b"); // f.setAccessible(true); // solution f.setBoolean(ft, Boolean.FALSE); // IllegalAccessException // production code should handle these exceptions more gracefully } catch (NoSuchFieldException x) { x.printStackTrace(); } catch (IllegalArgumentException x) { x.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); } } }
$ *java FieldTroubleToo* java.lang.IllegalAccessException: Can not set final boolean field FieldTroubleToo.b to (boolean)false at sun.reflect.UnsafeFieldAccessorImpl. throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:55) at sun.reflect.UnsafeFieldAccessorImpl. throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:63) at sun.reflect.UnsafeQualifiedBooleanFieldAccessorImpl.setBoolean (UnsafeQualifiedBooleanFieldAccessorImpl.java:78) at java.lang.reflect.Field.setBoolean(Field.java:686) at FieldTroubleToo.main(FieldTroubleToo.java:12)
**提示:**存在访问限制,阻止在类初始化后设置final
字段。但是,Field
声明为扩展AccessibleObject
,从而提供了抑制此检查的能力。
如果AccessibleObject.setAccessible()
成功,那么对该字段值的后续操作将不会因此问题而失败。这可能会产生意想不到的副作用;例如,有时候原始值将继续被应用程序的某些部分使用,即使该值已被修改。只有在安全上下文允许的情况下,AccessibleObject.setAccessible()
才会成功。
方法
原文:
docs.oracle.com/javase/tutorial/reflect/member/method.html
方法 包含可调用的可执行代码。方法是继承的,在非反射代码中,编译器强制执行重载、覆盖和隐藏等行为。相比之下,反射代码使得方法选择可以限制在特定类中而不考虑其超类。可以访问超类方法,但可以确定它们的声明类;这在没有反射的情况下是不可能通过编程方式发现的,这是许多微妙错误的根源。
java.lang.reflect.Method
类提供了访问有关方法修饰符、返回类型、参数、注解和抛出异常的信息的 API。它还可以用于调用方法。以下部分涵盖了这些主题:
- 获取方法类型信息展示了如何枚举在类中声明的方法并获取类型信息
- 获取方法参数的名称展示了如何检索方法或构造函数的参数的名称和其他信息
- 检索和解析方法修饰符描述了如何访问和解码与方法相关的修饰符和其他信息
- 调用方法演示了如何执行一个方法并获取其返回值
- 故障排除 涵盖了在查找或调用方法时遇到的常见错误
获取方法类型信息
原文:
docs.oracle.com/javase/tutorial/reflect/member/methodType.html
方法声明包括名称、修饰符、参数、返回类型和可抛出异常列表。java.lang.reflect.Method
类提供了获取这些信息的方法。
MethodSpy
示例演示了如何枚举给定类中声明的所有方法,并检索给定名称的所有方法的返回、参数和异常类型。
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 static 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(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]); } } // production code should handle these exceptions more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } } }
这是Class.getConstructor()
的输出,这是一个具有参数化类型和可变数量参数的方法的示例。
$ *java MethodSpy java.lang.Class getConstructor* public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor (java.lang.Class<?>[]) throws java.lang.NoSuchMethodException, java.lang.SecurityException ReturnType: class java.lang.reflect.Constructor GenericReturnType: java.lang.reflect.Constructor<T> ParameterType: class [Ljava.lang.Class; GenericParameterType: java.lang.Class<?>[] ExceptionType: class java.lang.NoSuchMethodException GenericExceptionType: class java.lang.NoSuchMethodException ExceptionType: class java.lang.SecurityException GenericExceptionType: class java.lang.SecurityException
这是源代码中方法的实际声明:
public Constructor<T> getConstructor(Class<?>... parameterTypes)
首先注意返回和参数类型是通用的。如果存在类文件中的签名属性,Method.getGenericReturnType()
将会查看它。如果属性不可用,则会回退到未更改的Method.getReturnType()
,这是在引入泛型之前没有更改的。其他以反射中某个值Foo为名称的getGeneric*Foo*()
方法实现方式类似。
接下来,请注意最后(也是唯一的)参数parameterType
是可变参数(具有可变数量的参数)类型为java.lang.Class
。它被表示为java.lang.Class
类型的单维数组。这可以通过调用Method.isVarArgs()
来区分明确为java.lang.Class
数组的参数。Method.get*Types()
返回值的语法在Class.getName()
中描述。
以下示例说明了具有通用返回类型的方法。
$ *java MethodSpy java.lang.Class cast* public T java.lang.Class.cast(java.lang.Object) ReturnType: class java.lang.Object GenericReturnType: T ParameterType: class java.lang.Object GenericParameterType: class java.lang.Object
方法Class.cast()
的通用返回类型报告为java.lang.Object
,因为泛型是通过类型擦除实现的,在编译期间删除了有关泛型类型的所有信息。T
的擦除由Class
的声明定义:
public final class Class<T> implements ...
因此,T
被类型变量的上界替换,即java.lang.Object
。
最后一个示例说明了具有多个重载的方法的输出。
$ *java MethodSpy java.io.PrintStream format* public java.io.PrintStream java.io.PrintStream.format (java.util.Locale,java.lang.String,java.lang.Object[]) ReturnType: class java.io.PrintStream GenericReturnType: class java.io.PrintStream ParameterType: class java.util.Locale GenericParameterType: class java.util.Locale ParameterType: class java.lang.String GenericParameterType: class java.lang.String ParameterType: class [Ljava.lang.Object; GenericParameterType: class [Ljava.lang.Object; public java.io.PrintStream java.io.PrintStream.format (java.lang.String,java.lang.Object[]) ReturnType: class java.io.PrintStream GenericReturnType: class java.io.PrintStream ParameterType: class java.lang.String GenericParameterType: class java.lang.String ParameterType: class [Ljava.lang.Object; GenericParameterType: class [Ljava.lang.Object;
如果发现同一方法名的多个重载,它们都会被Class.getDeclaredMethods()
返回。由于format()
有两个重载(一个带有Locale
,一个没有),MethodSpy
都会显示出来。
注意: Method.getGenericExceptionTypes()
的存在是因为实际上可以声明一个带有泛型异常类型的方法。然而,这很少被使用,因为无法捕获泛型异常类型。
Java 中文官方教程 2022 版(四十三)(4)https://developer.aliyun.com/article/1488271