Java 中文官方教程 2022 版(四十四)(2)https://developer.aliyun.com/article/1488316
课程:数组和枚举类型
原文:
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(); } } } }
$ *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(); } } }
$ *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]); } }
$ *java CreateMatrix* matrix[0][0] = 1 matrix[0][1] = 2 matrix[1][0] = 3 matrix[1][1] = 4
通过使用以下代码片段也可以获得相同的结果:
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(); } } }
$ *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
同样,在反射中从基本类型到引用类型的自动转换也是不可能的。
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 } }
$ *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 } }
$ *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
。
- 检查枚举 说明了如何检索枚举的常量以及任何其他字段、构造函数和方法
- 使用枚举类型获取和设置字段 展示了如何使用枚举常量值设置和获取字段
- 故障排除描述了与枚举相关的常见错误
有关枚举的介绍,请参阅 枚举类型 课程。
Java 中文官方教程 2022 版(四十四)(4)https://developer.aliyun.com/article/1488326