Java 中文官方教程 2022 版(四十三)(3)

简介: Java 中文官方教程 2022 版(四十三)

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


相关文章
|
3月前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
12天前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
26天前
|
NoSQL Java 关系型数据库
Liunx部署java项目Tomcat、Redis、Mysql教程
本文详细介绍了如何在 Linux 服务器上安装和配置 Tomcat、MySQL 和 Redis,并部署 Java 项目。通过这些步骤,您可以搭建一个高效稳定的 Java 应用运行环境。希望本文能为您在实际操作中提供有价值的参考。
119 26
|
1月前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
1月前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
2月前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编
36 2
|
1月前
|
Java 数据库连接 编译器
Kotlin教程笔记(29) -Kotlin 兼容 Java 遇到的最大的“坑”
Kotlin教程笔记(29) -Kotlin 兼容 Java 遇到的最大的“坑”
51 0
|
2月前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
2月前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
2月前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编