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

2024-05-24 14:59:49 浏览数 (2)

调用方法

原文: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方法。然后调用每个匹配的方法。

代码语言:javascript复制
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,因为Localefinal的:

代码语言:javascript复制
Locale.class == pType[0].getClass()

然而,Class.isAssignableFrom()更通用。

代码语言:javascript复制
$ *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()入口点,并在运行时传递一组参数。

代码语言:javascript复制
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()的第一个参数。第二个参数是要传递的参数数组。

代码语言:javascript复制
$ *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示例说明了当代码在类中搜索特定方法时未考虑类型擦除时会发生什么。

代码语言:javascript复制
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的实例是如下创建的,但不会找到方法:

代码语言:javascript复制
Class<?> c = (new MethodTrouble<Integer>()).getClass();

搜索lookup(Object)成功,如预期。

代码语言:javascript复制
$ *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示例展示了尝试在另一个类中调用私有方法导致的典型堆栈跟踪。

代码语言:javascript复制
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()可能产生混乱结果的各种方式。

代码语言:javascript复制
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()的所有参数都是可选的,除了第一个,当要调用的方法没有参数时,可以省略它们。

代码语言:javascript复制
$ *java MethodTroubleToo 1*
PONG!

在这种情况下,代码生成这个编译器警告,因为null是模棱两可的。

代码语言:javascript复制
$ *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

代码语言:javascript复制
$ *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()不需要任何参数。

代码语言:javascript复制
$ *java MethodTroubleToo 3*
PONG!

这能够成功是因为new Object[0]创建了一个空数组,对于可变参数方法来说,这等同于不传递任何可选参数。

代码语言:javascript复制
$ *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示例展示了如何检索被调用方法抛出的原始异常。

代码语言:javascript复制
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 来发现和检索类的构造函数,并获取声明信息,如修饰符、参数、注解和抛出的异常。还可以使用指定的构造函数创建类的新实例。在处理构造函数时使用的关键类是Classjava.lang.reflect.Constructor。涵盖了涉及构造函数的常见操作的以下部分:

  • 查找构造函数 说明了如何检索具有特定参数的构造函数
  • 检索和解析构造函数修饰符 展示了如何获取构造函数声明的修饰符以及有关构造函数的其他信息
  • 创建新的类实例 展示了如何通过调用其构造函数实例化对象的实例
  • 故障排除 描述了在查找或调用构造函数时可能遇到的常见错误

查找构造函数

原文:docs.oracle.com/javase/tutorial/reflect/member/ctorLocation.html

构造函数声明包括名称、修饰符、参数和可抛出异常列表。java.lang.reflect.Constructor类提供了获取这些信息的方法。

ConstructorSift示例演示了如何搜索一个类的声明构造函数中具有给定类型参数的构造函数。

代码语言:javascript复制
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参数的构造函数的输出。

代码语言:javascript复制
$ *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[]的参数。

代码语言:javascript复制
$ *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构造函数的实际声明:

代码语言:javascript复制
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

由于构造函数在语言中的作用,比方法更少的修饰符是有意义的:

  • 访问修饰符:publicprotectedprivate
  • 注解

ConstructorAccess示例在给定类中搜索具有指定访问修饰符的构造函数。它还显示构造函数是否是合成的(由编译器生成)或具有可变参数。

代码语言:javascript复制
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中的私有构造函数:

代码语言:javascript复制
$ *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示例说明了可能发生这种情况的典型情况:

代码语言:javascript复制
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示例展示了如何实现这一点:

代码语言:javascript复制
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 示例找到一个特定的单参数构造函数并调用它:

代码语言:javascript复制
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

代码语言:javascript复制
HashMap.class == defaultAliases.getClass()

然后,示例使用这个构造函数使用 Constructor.newInstance() 创建类的新实例。

代码语言:javascript复制
$ *java RestoreAliases*
Mail keys:
  Duke
  Fang

故障排除

原文:docs.oracle.com/javase/tutorial/reflect/member/ctorTrouble.html

开发人员在尝试通过反射调用构造函数时,有时会遇到以下问题。

由于缺少零参数构造函数而导致的 InstantiationException

ConstructorTrouble 示例说明了当代码尝试使用Class.newInstance()创建类的新实例时,且没有可访问的零参数构造函数时会发生什么:

代码语言:javascript复制
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()中出现的无法解决的问题。即,它传播构造函数抛出的任何异常(已检查或未检查)。

代码语言:javascript复制
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()来包装构造函数抛出的任何异常。

代码语言:javascript复制
$ *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类展示了代码错误可能无法定位或调用预期构造函数的各种方式。

代码语言:javascript复制
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是因为请求零参数构造函数并尝试传递参数。如果构造函数传递了错误类型的参数,也会抛出相同的异常。

代码语言:javascript复制
$ *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()不会尝试进行方法解析;它只是在现有构造函数对象上操作。


提示: newConstructor.newInstance()之间的一个重要区别是,new执行方法参数类型检查、装箱和方法解析。在反射中,这些都不会发生,必须做出明确选择。


尝试调用不可访问构造函数时出现 IllegalAccessException

如果尝试调用私有或其他不可访问的构造函数,则可能会抛出IllegalAccessExceptionConstructorTroubleAccess示例展示了产生的堆栈跟踪。

代码语言:javascript复制
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 是否表示一个 enumClass.getEnumConstants() 可以检索在枚举中定义的枚举常量。java.lang.reflect.Field.isEnumConstant() 表示一个字段是否是一个枚举类型。

  • 检查枚举 演示了如何检索枚举的常量以及任何其他字段、构造函数和方法
  • 使用枚举类型获取和设置字段 展示了如何设置和获取具有枚举常量值的字段
  • 故障排除 描述了与枚举相关的常见错误

数组

原文:docs.oracle.com/javase/tutorial/reflect/special/array.html

一个数组是引用类型的对象,包含固定数量的相同类型的组件;数组的长度是不可变的。创建数组的实例需要知道长度和组件类型。每个组件可以是原始类型(如byteintdouble),引用类型(如StringObjectjava.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示例标识了命名类中的数组类型字段,并报告了每个字段的组件类型。

代码语言:javascript复制
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的数组:

代码语言:javascript复制
$*java ArrayFind java.nio.ByteBuffer*
final byte[] java.nio.ByteBuffer.hb
           Field: hb
            Type: class [B
  Component Type: byte

一个引用类型为StackTraceElement的数组:

代码语言:javascript复制
$ *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

代码语言:javascript复制
$ *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,一个能够动态创建数组的基本解释器。将解析的语法如下:

代码语言:javascript复制
fully_qualified_class_name variable_name[] = 
     { val1, val2, val3, ... }

假设fully_qualified_class_name代表一个具有接受单个String参数的构造函数的类。数组的维度由提供的值的数量确定。以下示例将构造一个fully_qualified_class_name数组的实例,并用val1val2等给定的实例填充其值。(此示例假定熟悉Class.getConstructor()java.lang.reflect.Constructor.newInstance()。有关Constructor的反射 API 的讨论,请参阅本教程的 Creating New Class Instances 部分。)

代码语言:javascript复制
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)。)

代码语言:javascript复制
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示例演示了如何使用反射创建和初始化多维数组。

代码语言:javascript复制
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),以便其类型检查接受该语句。在使用反射时,类型检查仅在运行时发生,因此没有机会将值装箱。

代码语言:javascript复制
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)

代码语言:javascript复制
Array.set(ary, 0, new Integer(1));

提示: 当使用反射设置或获取数组组件时,编译器无法执行装箱。它只能转换与Class.isAssignableFrom()规范描述的相关类型。该示例预计会失败,因为isAssignableFrom()在此测试中将返回false,可以用程序验证特定转换是否可能:

代码语言:javascript复制
Integer.class.isAssignableFrom(int.class) == false 

同样,在反射中从基本类型到引用类型的自动转换也是不可能的。

代码语言:javascript复制
int.class.isAssignableFrom(Integer.class) == false

对空数组的ArrayIndexOutOfBoundsException

ArrayTrouble示例说明了如果尝试访问长度为零的数组元素将会发生的错误:

代码语言:javascript复制
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示例包含的代码会失败,因为它尝试执行一个可能会丢失数据的操作:

代码语言:javascript复制
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字段。以下部分展示了如何在枚举中使用 Classjava.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() 检索枚举中常量的有序列表。

代码语言:javascript复制
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() 返回的值相同。

代码语言:javascript复制
$ *java EnumConstants*
Enum name:  Eon
Enum constants:  [HADEAN, ARCHAEAN, 
                  PROTEROZOIC, PHANEROZOIC]
Eon.values():  [HADEAN, ARCHAEAN, 
                PROTEROZOIC, PHANEROZOIC]

由于枚举是类,可以使用本教程中描述的字段、方法和构造函数部分中描述的相同反射 API 获取其他信息。 EnumSpy 代码示例说明了如何使用这些 API 获取有关枚举声明的其他信息。 该示例使用 Class.isEnum() 来限制要检查的类集。 它还使用 Field.isEnumConstant() 来区分枚举声明中的枚举常量和其他字段(并非所有字段都是枚举常量)。

代码语言:javascript复制
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的字段,这些字段不是枚举常量。

代码语言:javascript复制
$ 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表示转换为枚举类型,并检索和设置存储枚举的字段的值。

代码语言:javascript复制
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();
	}
    }
}

由于枚举常量是单例,可以使用==!=运算符来比较相同类型的枚举常量。

代码语言:javascript复制
$ *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示例尝试这样做。

代码语言:javascript复制
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示例会产生预期的错误。

代码语言:javascript复制
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的值只有在以下语句成立时才能成功:

代码语言:javascript复制
X.class.isAssignableFrom(Y.class) == true

代码可以修改以执行以下测试,以验证类型是否兼容:

代码语言:javascript复制
if (f.getType().isAssignableFrom(E0.class))
    // compatible
else
    // expect IllegalArgumentException

0 人点赞