JAVA™教程
Java教程是为JDK 8编写的。本页中描述的示例和实践没有利用后面版本中引入的改进。
版权(1995、2017甲骨文和/或其附属公司)。版权所有。
目录
TRAIL:反射API
反射的使用
反射缺陷
线索课
课程:课程
检索类对象
Object.getClass()
.class语法
Class.forName()
基元类型包装器的类型字段
返回类的方法。
检查类修饰符和类型
发现类成员
故障排除
编译器警告:“注意:.使用未经检查或不安全的操作”
当构造器不可访问时引发InstantiationException。
字段
方法
构造函数
域
获取字段类型
字段修饰符的检索与解析
获取和设置字段值
故障排除
因不可转换类型而引发的IllegalArgumentException
非公共字段的NoSuchFieldException
修改最终字段时抛出IllegalAccessException
方法
获取方法类型信息
获取方法参数的名称
隐式和综合参数
检索和解析方法修饰符
调用方法
使用特定声明查找和调用方法
使用可变数量的参数调用方法
故障排除
由于类型擦除而引发的NoSuchMethod异常
调用方法时抛出IllegalAccessException。
IllegalArgumentException来自Method.Invoke()
调用方法失败时异常
建设者
寻找构造器
检索和解析构造修饰符
创建新类实例
故障排除
由于缺少零参数构造器而引发的InstantiationException
newInstance()引发意外异常
查找或调用正确的构造函数时出现问题
试图调用不可访问的构造器时抛出IllegalAccessException
反射通常由需要检查或修改运行在Java虚拟机中的应用程序的运行时行为的程序使用。这是一个相对高级的特性,应该只供对语言的基本知识有很强了解的开发人员使用。考虑到这一点,反射是一种强大的技术,可以使应用程序执行本来是不可能的操作。
可扩展性特征
应用程序可以通过使用可扩展对象的完全限定名创建可扩展对象的实例,从而利用外部的、用户定义的类。
类浏览器和可视化开发环境
类浏览器需要能够枚举类的成员。可视化开发环境可以通过使用反射中可用的类型信息来帮助开发人员编写正确的代码。
调试器和测试工具
调试器需要能够检查类上的私有成员。测试工具可以利用反射系统地调用定义在类上的可发现集API,以确保测试套件中的代码覆盖率很高。
反射是强大的,但不应任意使用。如果可以在不使用反射的情况下执行操作,则最好避免使用它。在通过反射访问代码时,应该记住以下几点。
性能开销
由于反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化。因此,反射操作的性能比它们的非反射操作要慢,在性能敏感的应用程序中经常调用的代码部分应该避免反射操作。
安全限制
反射需要在安全管理器下运行时可能不存在的运行时权限。这是对必须在受限的安全上下文中运行的代码的一个重要考虑,例如在applet中。
内件暴露
因为反射允许代码执行在非反射代码中为非法的操作,例如访问。private
字段和方法中,反射的使用会产生意想不到的副作用,这可能会导致代码功能失调并破坏可移植性。反射代码破坏抽象,因此可能会随着平台的升级而改变行为。
这条线索涵盖了用于访问和操作类、字段、方法和构造函数的反射的常见用途。每一课都包含代码示例、提示和故障排除信息。
类
本课展示了获取Class
对象,并使用它检查类的属性,包括其声明和内容。
成员
本课介绍如何使用反射API查找类的字段、方法和构造函数。为设置和获取字段值、调用方法以及使用特定构造函数创建对象的新实例提供了示例。
数组和枚举类型
本课介绍了两种特殊类型的类:数组,它们是在运行时生成的,以及enum
类型,它们定义唯一的命名对象实例。示例代码演示如何检索数组的组件类型,以及如何使用数组或enum
类型。
注:
本实验中的示例是为试验反射API而设计的。因此,异常的处理与在生产代码中使用的不一样。特别是,在生产代码中,不建议转储对用户可见的堆栈跟踪。
每种类型要么是引用,要么是原语。类、枚举和数组(它们都是从java.lang.Object
)以及接口都是引用类型。引用类型的示例包括java.lang.String
,基本类型的所有包装类,如java.lang.Double
,接口java.io.Serializable
,以及Enumjavax.swing.SortOrder
。有一组固定的基本类型:boolean
, byte
, short
, int
, long
, char
, float
,和double
.
对于每种类型的对象,Java虚拟机实例化java.lang.Class
它提供了检查对象的运行时属性(包括其成员和类型信息)的方法。Class
还提供了创建新类和对象的能力。最重要的是,它是所有反射API的入口点。本课程涵盖涉及类的最常用的反射操作:
Class
Class
所有反射操作的入口点是java.lang.Class
。除了.java.lang.reflect.ReflectPermission
中的任何一个类java.lang.reflect
有公共建设者。要获得这些类,必须在Class
。有几种方法可以获得Class
取决于代码是否有权访问对象、类名、类型或现有的Class
.
如果一个对象的实例是可用的,那么获取其Class
就是调用Object.getClass()
。当然,这只适用于所有继承自Object
。下面是一些例子。
Class c = "foo".getClass();
返回Class
为String
Class c = System.console().getClass();
有一个与虚拟机关联的唯一控制台,该控制台由static
方法System.console()
。返回的值getClass()
是Class
对应于java.io.Console
.
enum E { A, B }
Class c = A.getClass();
A
是枚举的实例。E
因此getClass()
返回Class
对应于枚举类型E
.
byte[] bytes = new byte[1024];
Class c = bytes.getClass();
因为数组是Objects
,也可以调用getClass()
在数组的实例上。回归Class
对应于具有组件类型的数组。byte
.
import java.util.HashSet;
import java.util.Set; Set s = new HashSet();
Class c = s.getClass();
在这种情况下,java.util.Set
是类型对象的接口。java.util.HashSet
。返回的值getClass()
是对应于java.util.HashSet
.
如果类型可用,但没有实例,则可以获得Class
通过附加".class"
类型的名称。这也是获取Class
对于原始类型。
boolean b;
Class c = b.getClass(); // compile-time error
Class c = boolean.class; // correct
注意语句boolean.getClass()
将产生编译时错误,因为boolean
是一个基本类型,不能取消引用。这,这个,那,那个.class
语法返回Class
对应于类型boolean
.
Class c = java.io.PrintStream.class;
变量c
将是Class
对应于类型java.io.PrintStream
.
Class c = int[][][].class;
这,这个,那,那个.class
语法可用于检索Class
对应于给定类型的多维数组。
如果类的完全限定名可用,则可以获得相应的Class
使用静态方法Class.forName()
。这不能用于基本类型。数组类名的语法由Class.getName()
。此语法适用于引用和基元类型。
Class c = Class.forName("com.duke.MyLocaleServiceProvider");
此语句将从给定的完全限定名创建一个类。
Class cDoubleArray = Class.forName("[D");
Class cStringArray = Class.forName("[[Ljava.lang.String;");
变量cDoubleArray
将包含Class
对应于基元类型的数组double
(即与double[].class
)这,这个,那,那个cStringArray
变量将包含Class
对应于String
(即与String[][].class
).
这,这个,那,那个.class
语法是获得Class
但是,有另一种方法可以获取Class
。每种原语类型和void
中有一个包装类。java.lang
用于将原始类型装箱为引用类型的。每个包装类包含一个名为TYPE
,它等于Class
对于正在包装的原始类型。
Class c = Double.TYPE;
有一门课java.lang.Double
,它用于包装原语类型。double
无论何时Object
是必需的。价值Double.TYPE
与double.class
.
Class c = Void.TYPE;
Void.TYPE
是相同的void.class
.
有几个反射API返回类,但只有在Class
已经直接或间接获得了。
Class.getSuperclass()
返回给定类的超类。
Class c = javax.swing.JButton.class.getSuperclass();
超级阶级javax.swing.JButton
是javax.swing.AbstractButton
.
Class.getClasses()
返回类的所有公共类、接口和枚举,包括继承的成员。
Class>[] c = Character.class.getClasses();
Character
包含两个成员类。Character.Subset
和Character.UnicodeBlock
.
Class.getDeclaredClasses()
返回该类中显式声明的所有类接口和枚举。
Class>[] c = Character.class.getDeclaredClasses();
Character
包含两个公共成员类。Character.Subset
和Character.UnicodeBlock
还有一个私人班级Character.CharacterCache
.
Class.getDeclaringClass()
java.lang.reflect.Field.getDeclaringClass()
java.lang.reflect.Method.getDeclaringClass()
java.lang.reflect.Constructor.getDeclaringClass()
返回Class
其中宣布了这些成员。匿名类声明不会有声明类,而是会有一个封闭类。
import java.lang.reflect.Field;
Field f = System.class.getField("out");
Class c = f.getDeclaringClass();
out
声明为System
.
public class MyClass {
static Object o = new Object() {
public void m() {}
};
static Class = o.getClass().getEnclosingClass();
}
定义的匿名类的声明类。o
是null
.
Class.getEnclosingClass()
返回类的立即封装类。
Class c = Thread.State.class().getEnclosingClass();
围封类Thread.State
是Thread
.
public class MyClass {
static Object o = new Object() {
public void m() {}
};
static Class = o.getClass().getEnclosingClass();
}
定义的匿名类。o
被MyClass
.
类可以使用影响其运行时行为的一个或多个修饰符声明:
public
, protected
,和private
abstract
static
final
strictfp
并非所有类都允许所有修饰符,例如,接口不能final
一个枚举是不可能的abstract
. java.lang.reflect.Modifier
包含所有可能的修饰符的声明。它还包含方法,这些方法可用于解码由Class.getModifiers()
.
这,这个,那,那个
示例演示如何获取类的声明组件,包括修饰符、泛型类型参数、实现的接口和继承路径。自ClassDeclarationSpy
Class
实现java.lang.reflect.AnnotatedElement
接口也可以查询运行时注释。
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import static java.lang.System.out;
public class ClassDeclarationSpy {
public static void main(String... args) {
try {
Class> c = Class.forName(args[0]);
out.format("Class:%n %s%n%n", c.getCanonicalName());
out.format("Modifiers:%n %s%n%n",
Modifier.toString(c.getModifiers()));
out.format("Type Parameters:%n");
TypeVariable[] tv = c.getTypeParameters();
if (tv.length != 0) {
out.format(" ");
for (TypeVariable t : tv)
out.format("%s ", t.getName());
out.format("%n%n");
} else {
out.format(" -- No Type Parameters --%n%n");
}
out.format("Implemented Interfaces:%n");
Type[] intfs = c.getGenericInterfaces();
if (intfs.length != 0) {
for (Type intf : intfs)
out.format(" %s%n", intf.toString());
out.format("%n");
} else {
out.format(" -- No Implemented Interfaces --%n%n");
}
out.format("Inheritance Path:%n");
List l = new ArrayList();
printAncestor(c, l);
if (l.size() != 0) {
for (Class> cl : l)
out.format(" %s%n", cl.getCanonicalName());
out.format("%n");
} else {
out.format(" -- No Super Classes --%n%n");
}
out.format("Annotations:%n");
Annotation[] ann = c.getAnnotations();
if (ann.length != 0) {
for (Annotation a : ann)
out.format(" %s%n", a.toString());
out.format("%n");
} else {
out.format(" -- No Annotations --%n%n");
}
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
private static void printAncestor(Class> c, List l) {
Class> ancestor = c.getSuperclass();
if (ancestor != null) {
l.add(ancestor);
printAncestor(ancestor, l);
}
}
}
输出的一些示例如下。用户输入以斜体表示。
$ java ClassDeclarationSpy java.util.concurrent.ConcurrentNavigableMap
Class:
java.util.concurrent.ConcurrentNavigableMap
Modifiers:
public abstract interface
Type Parameters:
K V
Implemented Interfaces:
java.util.concurrent.ConcurrentMap
java.util.NavigableMap
Inheritance Path:
-- No Super Classes --
Annotations:
-- No Annotations --
这是java.util.concurrent.ConcurrentNavigableMap
在源代码中:
public interface ConcurrentNavigableMap
extends ConcurrentMap, NavigableMap
注意,由于这是一个接口,所以它是隐式的。abstract
。编译器为每个接口添加此修饰符。此外,此声明包含两个泛型类型参数,K
和V
。示例代码只打印这些参数的名称,但是否可以使用java.lang.reflect.TypeVariable
。接口还可以实现上面所示的其他接口。
$ java ClassDeclarationSpy "[Ljava.lang.String;"
Class:
java.lang.String[]
Modifiers:
public abstract final
Type Parameters:
-- No Type Parameters --
Implemented Interfaces:
interface java.lang.Cloneable
interface java.io.Serializable
Inheritance Path:
java.lang.Object
Annotations:
-- No Annotations --
因为数组是运行时对象,所以所有类型信息都由Java虚拟机定义。特别是,数组实现Cloneable
和java.io.Serializable
他们的直接超类总是Object
.
$ java ClassDeclarationSpy java.io.InterruptedIOException
Class:
java.io.InterruptedIOException
Modifiers:
public
Type Parameters:
-- No Type Parameters --
Implemented Interfaces:
-- No Implemented Interfaces --
Inheritance Path:
java.io.IOException
java.lang.Exception
java.lang.Throwable
java.lang.Object
Annotations:
-- No Annotations --
从继承路径可以推断java.io.InterruptedIOException
是选中的异常,因为RuntimeException
不存在。
$ java ClassDeclarationSpy java.security.Identity
Class:
java.security.Identity
Modifiers:
public abstract
Type Parameters:
-- No Type Parameters --
Implemented Interfaces:
interface java.security.Principal
interface java.io.Serializable
Inheritance Path:
java.lang.Object
Annotations:
@java.lang.Deprecated()
这个输出显示java.security.Identity
,不推荐的api拥有注释。java.lang.Deprecated
。反射代码可以使用这一点来检测不推荐的API。
注:并不是所有的注释都可以通过反射获得。只有那些有java.lang.annotation.RetentionPolicy
的RUNTIME
是可以接近的。语言中预定义的三个注释。@Deprecated
, @Override
,和@SuppressWarnings
只@Deprecated
在运行时可用。
中提供了两类方法。Class
用于访问字段、方法和构造函数:枚举这些成员的方法和搜索特定成员的方法。此外,有不同的方法可以直接访问类上声明的成员,而不是在超接口和超类中搜索继承的成员。下表提供了所有成员定位方法及其特性的摘要。
Class API |
成员名单? | 继承的成员? | 私人会员? |
---|---|---|---|
getDeclaredField() |
否 | 否 | 是 |
getField() |
否 | 是 | 否 |
getDeclaredFields() |
是 | 否 | 是 |
getFields() |
是 | 是 | 否 |
Class API |
成员名单? | 继承的成员? | 私人会员? |
---|---|---|---|
getDeclaredMethod() |
否 | 否 | 是 |
getMethod() |
否 | 是 | 否 |
getDeclaredMethods() |
是 | 否 | 是 |
getMethods() |
是 | 是 | 否 |
Class API |
成员名单? | 继承的成员? | 私人会员? |
---|---|---|---|
getDeclaredConstructor() |
否 | N/A1 | 是 |
getConstructor() |
否 | N/A1 | 否 |
getDeclaredConstructors() |
是 | N/A1 | 是 |
getConstructors() |
是 | N/A1 | 否 |
1构造函数不是继承的。
给定一个类名和哪个成员感兴趣的指示,
示例使用ClassSpy
get*s()
方法确定所有公共元素的列表,包括继承的任何元素。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Member;
import static java.lang.System.out;
enum ClassMember { CONSTRUCTOR, FIELD, METHOD, CLASS, ALL }
public class ClassSpy {
public static void main(String... args) {
try {
Class> c = Class.forName(args[0]);
out.format("Class:%n %s%n%n", c.getCanonicalName());
Package p = c.getPackage();
out.format("Package:%n %s%n%n",
(p != null ? p.getName() : "-- No Package --"));
for (int i = 1; i < args.length; i++) {
switch (ClassMember.valueOf(args[i])) {
case CONSTRUCTOR:
printMembers(c.getConstructors(), "Constructor");
break;
case FIELD:
printMembers(c.getFields(), "Fields");
break;
case METHOD:
printMembers(c.getMethods(), "Methods");
break;
case CLASS:
printClasses(c);
break;
case ALL:
printMembers(c.getConstructors(), "Constuctors");
printMembers(c.getFields(), "Fields");
printMembers(c.getMethods(), "Methods");
printClasses(c);
break;
default:
assert false;
}
}
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
private static void printMembers(Member[] mbrs, String s) {
out.format("%s:%n", s);
for (Member mbr : mbrs) {
if (mbr instanceof Field)
out.format(" %s%n", ((Field)mbr).toGenericString());
else if (mbr instanceof Constructor)
out.format(" %s%n", ((Constructor)mbr).toGenericString());
else if (mbr instanceof Method)
out.format(" %s%n", ((Method)mbr).toGenericString());
}
if (mbrs.length == 0)
out.format(" -- No %s --%n", s);
out.format("%n");
}
private static void printClasses(Class> c) {
out.format("Classes:%n");
Class>[] clss = c.getClasses();
for (Class> cls : clss)
out.format(" %s%n", cls.getCanonicalName());
if (clss.length == 0)
out.format(" -- No member interfaces, classes, or enums --%n");
out.format("%n");
}
}
此示例相对紧凑;但是printMembers()
方法稍微有些尴尬,因为java.lang.reflect.Member
接口自最早的反射实现以来就已经存在,它不能被修改以包含更有用的接口。getGenericString()
方法:引入仿制药。唯一的替代方法是测试和强制转换,如下图所示,将此方法替换为printConstructors()
, printFields()
,和printMethods()
,或对相对较少的结果感到满意。Member.getName()
.
输出的样本及其解释如下。用户输入以斜体表示。
$ java ClassSpy java.lang.ClassCastException CONSTRUCTOR
Class:
java.lang.ClassCastException
Package:
java.lang
Constructor:
public java.lang.ClassCastException()
public java.lang.ClassCastException(java.lang.String)
由于构造函数不是继承的,所以异常链接机制构造函数(那些具有Throwable
参数),该参数是在直接超类中定义的。RuntimeException
其他超类也找不到。
$ java ClassSpy java.nio.channels.ReadableByteChannel METHOD
Class:
java.nio.channels.ReadableByteChannel
Package:
java.nio.channels
Methods:
public abstract int java.nio.channels.ReadableByteChannel.read
(java.nio.ByteBuffer) throws java.io.IOException
public abstract void java.nio.channels.Channel.close() throws
java.io.IOException
public abstract boolean java.nio.channels.Channel.isOpen()
接口java.nio.channels.ReadableByteChannel
定义read()
。其余的方法是从超级接口继承的。可以轻松地修改此代码,使其只列出实际在类中声明的方法,方法是get*s()
带着getDeclared*s()
.
$ java ClassSpy ClassMember FIELD METHOD
Class:
ClassMember
Package:
-- No Package --
Fields:
public static final ClassMember ClassMember.CONSTRUCTOR
public static final ClassMember ClassMember.FIELD
public static final ClassMember ClassMember.METHOD
public static final ClassMember ClassMember.CLASS
public static final ClassMember ClassMember.ALL
Methods:
public static ClassMember ClassMember.valueOf(java.lang.String)
public static ClassMember[] ClassMember.values()
public final int java.lang.Enum.hashCode()
public final int java.lang.Enum.compareTo(E)
public int java.lang.Enum.compareTo(java.lang.Object)
public final java.lang.String java.lang.Enum.name()
public final boolean java.lang.Enum.equals(java.lang.Object)
public java.lang.String java.lang.Enum.toString()
public static T java.lang.Enum.valueOf
(java.lang.Class,java.lang.String)
public final java.lang.Class java.lang.Enum.getDeclaringClass()
public final int java.lang.Enum.ordinal()
public final native java.lang.Class> java.lang.Object.getClass()
public final native void java.lang.Object.wait(long) throws
java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws
java.lang.InterruptedException
public final void java.lang.Object.wait() hrows java.lang.InterruptedException
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
在这些结果的字段部分,列出了枚举常数。虽然这些都是技术领域,但将它们与其他领域区分开来可能是有用的。此示例可修改为使用java.lang.reflect.Field.isEnumConstant()
为了这个目的。这,这个,那,那个
在这条线索的后面一节中,检查Enum,包含可能的实现。EnumSpy
在输出的“方法”部分中,注意到方法名称包括声明类的名称。因此,toString()
方法由Enum
,不是从Object
。代码可以通过使用Field.getDeclaringClass()
。下面的片段说明了潜在解决方案的一部分。
if (mbr instanceof Field) {
Field f = (Field)mbr;
out.format(" %s%n", f.toGenericString());
out.format(" -- declared in: %s%n", f.getDeclaringClass());
}
下面的示例显示了在对类进行反思时可能遇到的典型错误。
调用方法时,将检查参数值的类型,并可能对其进行转换。
调用ClassWarning
getMethod()
若要导致典型的未选中转换警告,请执行以下操作:
import java.lang.reflect.Method;
public class ClassWarning {
void m() {
try {
Class c = ClassWarning.class;
Method m = c.getMethod("m"); // warning
// production code should handle this exception more gracefully
} catch (NoSuchMethodException x) {
x.printStackTrace();
}
}
}
$ javac ClassWarning.java
Note: ClassWarning.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
$ javac -Xlint:unchecked ClassWarning.java
ClassWarning.java:6: warning: [unchecked] unchecked call to getMethod
(String,Class>...) as a member of the raw type Class
Method m = c.getMethod("m"); // warning
^
1 warning
许多库方法都经过了泛型声明的修改,其中包括Class
。自c
被声明为生类型(没有类型参数)和相应的getMethod()
是参数化类型,则会发生未经检查的转换。编译器需要生成警告。(见Java语言规范,JavaSE 7版、分段未检查转换和方法调用转换.)
有两种可能的解决办法。的声明修改得越好。c
若要包含适当的泛型类型,请执行以下操作。在这种情况下,声明应是:
Class> c = warn.getClass();
或者,可以使用预定义的注释显式地抑制警告。@SuppressWarnings
在有问题的语句之前。
Class c = ClassWarning.class;
@SuppressWarnings("unchecked")
Method m = c.getMethod("m");
// warning gone
提示:作为一般原则,警告不应被忽略,因为它们可能表明存在bug。应酌情使用参数化声明。如果这是不可能的(可能是因为应用程序必须与库供应商的代码交互),则使用@SuppressWarnings
.
Class.newInstance()
抛出InstantiationException
如果尝试创建类的新实例,而零参数构造函数是不可见的。这,这个,那,那个
示例说明了结果堆栈跟踪。ClassTrouble
class Cls {
private Cls() {}
}
public class ClassTrouble {
public static void main(String... args) {
try {
Class> c = Class.forName("Cls");
c.newInstance(); // InstantiationException
// production code should handle these exceptions more gracefully
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
$ java ClassTrouble
java.lang.IllegalAccessException: Class ClassTrouble can not access a member of
class Cls with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.Class.newInstance0(Class.java:349)
at java.lang.Class.newInstance(Class.java:308)
at ClassTrouble.main(ClassTrouble.java:9)
Class.newInstance()
行为非常类似于new
关键字,并将基于相同的原因失败。new
都会失败。反射中的典型解决方案是利用java.lang.reflect.AccessibleObject
类,它提供了抑制访问控制检查的能力;但是,此方法将无法工作,因为java.lang.Class
不扩展AccessibleObject
。唯一的解决方案是修改要使用的代码。Constructor.newInstance()
它确实扩展了AccessibleObject
.
提示:一般来说,最好使用Constructor.newInstance()
中描述的原因创建新类实例部分成员教训。
使用的潜在问题的附加示例Constructor.newInstance()
可以在构造器故障排除的一节成员教训。
反射定义接口java.lang.reflect.Member
它由java.lang.reflect.Field
, java.lang.reflect.Method
,和java.lang.reflect.Constructor
。这些对象将在本课中讨论。对于每个成员,本课程将描述用于检索声明和类型信息的相关API、成员特有的任何操作(例如,设置字段的值或调用方法),以及常见的错误。每个概念都将用代码示例和相关输出来说明,这些输出与一些预期的反射用途大致相同。
注:根据Java语言规范,JavaSE 7版,成员类的继承组件,包括字段、方法、嵌套类、接口和枚举类型。因为构造函数不是继承的,所以它们不是成员。的实现类不同。java.lang.reflect.Member
.
字段有类型和值。这,这个,那,那个java.lang.reflect.Field
类提供用于访问类型信息以及设置和获取给定对象上字段的值的方法。
public
或transient
方法具有返回值、参数,并可能引发异常。这,这个,那,那个java.lang.reflect.Method
类提供用于获取参数和返回值的类型信息的方法。它还可以用于调用给定对象上的方法。
构造函数的反射api定义在java.lang.reflect.Constructor
与方法类似,只有两个主要的例外:第一,构造函数没有返回值;第二,构造函数的调用为给定类创建对象的新实例。
场域是具有关联值的类、接口或枚举。方法java.lang.reflect.Field
类可以检索有关该字段的信息,如其名称、类型、修饰符和注释。(该款检查类修饰符和类型在.。班课程描述了如何检索注释。)还有一些方法可以动态访问和修改字段的值。这些任务将在以下章节中讨论:
public
或transient
在编写应用程序(如类浏览器)时,找出哪些字段属于特定类可能是有用的。类的字段通过调用Class.getFields()
。这,这个,那,那个getFields()
方法返回一个Field
对象,每个可访问的公共字段包含一个对象。
如果公共字段是下列任一项的成员,则可访问该公共字段:
字段可以是类(实例)字段,如java.io.Reader.lock
,静态字段,如java.lang.Integer.MAX_VALUE
,或枚举常量,如java.lang.Thread.State.WAITING
.
字段可以是原始类型的,也可以是引用类型的。有八种原始类型:boolean
, byte
, short
, int
, long
, char
, float
,和double
。的直接或间接子类。java.lang.Object
包括接口、数组和枚举类型。
这,这个,那,那个
示例打印给定完全限定的二进制类名和字段名的字段的类型和泛型类型。FieldSpy
import java.lang.reflect.Field;
import java.util.List;
public class FieldSpy {
public boolean[][] b = {{ false, false }, { true, true } };
public String name = "Alice";
public List list;
public T val;
public static void main(String... args) {
try {
Class> c = Class.forName(args[0]);
Field f = c.getField(args[1]);
System.out.format("Type: %s%n", f.getType());
System.out.format("GenericType: %s%n", f.getGenericType());
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (NoSuchFieldException x) {
x.printStackTrace();
}
}
}
获取该类中三个公共字段的类型的示例输出(b
, name
,和参数化类型list
),如下。用户输入以斜体表示。
$ java FieldSpy FieldSpy b
Type: class [[Z
GenericType: class [[Z
$ java FieldSpy FieldSpy name
Type: class java.lang.String
GenericType: class java.lang.String
$ java FieldSpy FieldSpy list
Type: interface java.util.List
GenericType: java.util.List
$ java FieldSpy FieldSpy val
Type: class java.lang.Object
GenericType: T
字段的类型。b
是二维布尔数组。类型名称的语法将在Class.getName()
.
字段的类型。val
报告为java.lang.Object
因为泛型是通过类型擦除它删除编译期间有关泛型类型的所有信息。因此T
被类型变量的上限替换,在这种情况下,java.lang.Object
.
Field.getGenericType()
将咨询类文件中的签名属性(如果存在的话)。如果该属性不可用,则返回Field.getType()
这并没有因为仿制药的引入而改变。在反射中使用名称的其他方法getGenericFoo
有价值的,有价值的福都以类似的方式实现。
有几个修饰符可能是字段声明的一部分:
public
, protected
,和private
transient
和volatile
static
final
方法Field.getModifiers()
可用于返回表示该字段的声明修饰符集的整数。表示此整数中的修饰符的位是在java.lang.reflect.Modifier
.
这,这个,那,那个
示例说明如何使用给定修饰符搜索字段。它还通过调用来确定定位字段是合成的(编译器生成的)还是枚举常量。FieldModifierSpy
Field.isSynthetic()
和Field.isEnumCostant()
分别。
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import static java.lang.System.out;
enum Spy { BLACK , WHITE }
public class FieldModifierSpy {
volatile int share;
int instance;
class Inner {}
public static void main(String... args) {
try {
Class> c = Class.forName(args[0]);
int searchMods = 0x0;
for (int i = 1; i < args.length; i++) {
searchMods |= modifierFromString(args[i]);
}
Field[] flds = c.getDeclaredFields();
out.format("Fields in Class '%s' containing modifiers: %s%n",
c.getName(),
Modifier.toString(searchMods));
boolean found = false;
for (Field f : flds) {
int foundMods = f.getModifiers();
// Require all of the requested modifiers to be present
if ((foundMods & searchMods) == searchMods) {
out.format("%-8s [ synthetic=%-5b enum_constant=%-5b ]%n",
f.getName(), f.isSynthetic(),
f.isEnumConstant());
found = true;
}
}
if (!found) {
out.format("No matching fields%n");
}
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
private static int modifierFromString(String s) {
int m = 0x0;
if ("public".equals(s)) m |= Modifier.PUBLIC;
else if ("protected".equals(s)) m |= Modifier.PROTECTED;
else if ("private".equals(s)) m |= Modifier.PRIVATE;
else if ("static".equals(s)) m |= Modifier.STATIC;
else if ("final".equals(s)) m |= Modifier.FINAL;
else if ("transient".equals(s)) m |= Modifier.TRANSIENT;
else if ("volatile".equals(s)) m |= Modifier.VOLATILE;
return m;
}
}
样本输出如下:
$ java FieldModifierSpy FieldModifierSpy volatile
Fields in Class 'FieldModifierSpy' containing modifiers: volatile
share [ synthetic=false enum_constant=false ]
$ java FieldModifierSpy Spy public
Fields in Class 'Spy' containing modifiers: public
BLACK [ synthetic=false enum_constant=true ]
WHITE [ synthetic=false enum_constant=true ]
$ java FieldModifierSpy FieldModifierSpy\$Inner final
Fields in Class 'FieldModifierSpy$Inner' containing modifiers: final
this$0 [ synthetic=true enum_constant=false ]
$ java FieldModifierSpy Spy private static final
Fields in Class 'Spy' containing modifiers: private static final
$VALUES [ synthetic=true enum_constant=false ]
请注意,即使未在原始代码中声明某些字段,也会报告它们。这是因为编译器将生成一些合成场它们在运行时是需要的。要测试字段是否是合成的,示例调用Field.isSynthetic()
。合成字段集依赖于编译器;但是常用的字段包括this$0
对于内部类(即不是静态成员类的嵌套类),可以引用最外层的封闭类和$VALUES
枚举用于实现隐式定义的静态方法。values()
。合成类成员的名称未指定,在所有编译器实现或发行版中也可能不相同。这些和其他合成字段将包含在由Class.getDeclaredFields()
但没有被Class.getField()
因为合成成员通常不是public
.
因为Field
实现接口java.lang.reflect.AnnotatedElement
,则可以使用以下方法检索任何运行时注释java.lang.annotation.RetentionPolicy.RUNTIME
。有关获取注释的示例,请参见检查类修饰符和类型.
给定类的实例,可以使用反射设置该类中字段的值。通常只有在不可能以通常方式设置值的特殊情况下才会这样做。因为这种访问通常违反类的设计意图,所以使用时应该非常谨慎。
这,这个,那,那个
类说明如何为long、Array和enum字段类型设置值。获取和设置其他基本类型的方法将在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*()
也许不会。
下面是开发人员遇到的几个常见问题,并解释了为什么会发生这种情况,以及如何解决这些问题。
这,这个,那,那个
示例将生成一个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
精明的读者可能会注意到,如果
前面显示的示例用于获取非公共字段的信息,它将失败: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
字段(不管其访问修饰符如何)。
这,这个,那,那个
示例说明了试图设置最终字段所导致的堆栈跟踪的类型。FieldTroubleToo
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()
只有在安全上下文允许操作时才能成功。
A 方法包含可被调用的可执行代码。方法是继承的,在非反射代码中,诸如重载、重写和隐藏等行为由编译器强制执行。相反,反射代码可以将方法选择限制在特定的类中,而不考虑它的超类。可以访问超类方法,但可以确定它们的声明类;这是不可能在没有反射的情况下以编程方式发现的,并且是许多细微错误的来源。
这,这个,那,那个java.lang.reflect.Method
类提供API来访问有关方法的修饰符、返回类型、参数、注释和抛出异常的信息。它还用于调用方法。以下各节将讨论这些主题:
方法声明包括名称、修饰符、参数、返回类型和可抛异常列表。这,这个,那,那个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
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 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
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 getConstructor(Class>... parameterTypes)
首先,请注意返回类型和参数类型是泛型的。Method.getGenericReturnType()
将咨询类文件中的签名属性(如果存在的话)。如果该属性不可用,则返回Method.getReturnType()
这并没有因为仿制药的引入而改变。其他有名称的方法getGenericFoo()
有价值的,有价值的福在反射中实现类似的。
接下来,注意最后一个(也是唯一的)参数,parameterType
类型的变量(有可变的参数)。java.lang.Class
。它表示为类型的一维数组。java.lang.Class
。可以将其与显式地表示为java.lang.Class
通过调用Method.isVarArgs()
。的返回值的语法。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 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.lang.reflect.Executable.getParameters
。(班级)Method
和Constructor
扩展类Executable
从而继承该方法Executable.getParameters
)然而,.class
默认情况下,文件不存储正式参数名称。这是因为许多生成和使用类文件的工具可能不会期望使用更大的静态和动态占用空间。.class
包含参数名称的文件。特别是,这些工具必须处理更大的.class
文件和Java虚拟机(JVM)将使用更多的内存。此外,一些参数名称,如secret
或password
,则可能公开有关安全敏感方法的信息。
将形式参数名称存储在特定的.class
文件,从而使反射API能够检索形式参数名称,则使用-parameters
选项的javac
编译器。
这,这个,那,那个
示例说明了如何检索给定类的所有构造函数和方法的形式参数的名称。该示例还打印有关每个参数的其他信息。MethodParameterSpy
下面的命令打印构造函数的形式参数名称和类的方法
. 注*请记住编译示例ExampleMethods
ExampleMethods
带着-parameters
编译器选项:
java MethodParameterSpy ExampleMethods
此命令打印以下内容:
Number of constructors: 1
Constructor #1
public ExampleMethods()
Number of declared constructors: 1
Declared constructor #1
public ExampleMethods()
Number of methods: 4
Method #1
public boolean ExampleMethods.simpleMethod(java.lang.String,int)
Return type: boolean
Generic return type: boolean
Parameter class: class java.lang.String
Parameter name: stringParam
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Parameter class: int
Parameter name: intParam
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Method #2
public int ExampleMethods.varArgsMethod(java.lang.String...)
Return type: int
Generic return type: int
Parameter class: class [Ljava.lang.String;
Parameter name: manyStrings
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Method #3
public boolean ExampleMethods.methodWithList(java.util.List)
Return type: boolean
Generic return type: boolean
Parameter class: interface java.util.List
Parameter name: listParam
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Method #4
public void ExampleMethods.genericMethod(T[],java.util.Collection)
Return type: void
Generic return type: void
Parameter class: class [Ljava.lang.Object;
Parameter name: a
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Parameter class: interface java.util.Collection
Parameter name: c
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
这,这个,那,那个MethodParameterSpy
示例中的Parameter
班级:
getType
*返回Class
对象,该对象标识参数的声明类型。
getName
:返回参数的名称。如果存在参数的名称,则此方法将返回.class
档案。否则,此方法将合成窗体的名称。argN
,在哪里N
声明参数的方法的描述符中参数的索引。
例如,假设您编译了类ExampleMethods
而不指定-parameters
编译器选项。例子MethodParameterSpy
将为该方法打印以下内容ExampleMethods.simpleMethod
:
public boolean ExampleMethods.simpleMethod(java.lang.String,int)
Return type: boolean
Generic return type: boolean
Parameter class: class java.lang.String
Parameter name: arg0
Modifiers: 0
Is implicit?: false
Is name present?: false
Is synthetic?: false
Parameter class: int
Parameter name: arg1
Modifiers: 0
Is implicit?: false
Is name present?: false
Is synthetic?: false
getModifiers
:返回一个整数,该整数表示形式参数所具有的各种特征。如果适用于形式参数,此值是下列值的之和:
值(小数) | 值(以十六进制为单位) | 描述 |
---|---|---|
16 | 0x0010 | 形式参数声明final |
4096 | 0x1000 | 形式参数是合成的。或者,您可以调用该方法。isSynthetic . |
32768 | 0x8000 | 该参数在源代码中隐式声明。或者,您可以调用该方法。isImplicit |
isImplicit
*回返true
如果此参数是在源代码中隐式声明的。见本节隐式和综合参数想了解更多信息。
isNamePresent
*回返true
如果参数有一个名称,则根据.class
档案。
isSynthetic
*回返true
如果该参数既不是隐式的,也不是在源代码中显式声明的。见本节隐式和综合参数想了解更多信息。
如果没有显式编写某些构造,则在源代码中隐式声明它们。例如,
示例不包含构造函数。默认构造函数被隐式声明。这,这个,那,那个ExampleMethods
MethodParameterSpy
的隐式声明构造函数的信息。ExampleMethods
:
Number of declared constructors: 1
public ExampleMethods()
请考虑以下摘录
:MethodParameterExamples
public class MethodParameterExamples {
public class InnerClass { }
}
全班InnerClass
是非静态的嵌套类或者内在阶级。内部类的构造函数也被隐式声明。但是,此构造函数将包含一个参数。当Java编译器编译InnerClass
,它创建了一个.class
表示类似于以下代码的文件:
public class MethodParameterExamples {
public class InnerClass {
final MethodParameterExamples parent;
InnerClass(final MethodParameterExamples this$0) {
parent = this$0;
}
}
}
这,这个,那,那个InnerClass
构造函数包含一个参数,其类型是括起来的类。InnerClass
,也就是MethodParameterExamples
。因此,这个例子MethodParameterExamples
印刷如下:
public MethodParameterExamples$InnerClass(MethodParameterExamples)
Parameter class: class MethodParameterExamples
Parameter name: this$0
Modifiers: 32784
Is implicit?: true
Is name present?: true
Is synthetic?: false
因为类的构造函数InnerClass
是隐式声明的,其参数也是隐式的。
注:
InnerClass
构造函数既是最终构造函数(16),也是隐式构造函数(32768)。$
);但是,按照惯例,美元符号不用于变量名称。Java编译器发出的构造标记为合成如果它们不对应于源代码中显式或隐式声明的构造,除非它们是类初始化方法。合成构造是由编译器生成的工件,在不同的实现中各不相同。请考虑以下摘录
:MethodParameterExamples
public class MethodParameterExamples {
enum Colors {
RED, WHITE;
}
}
当Java编译器遇到enum
构造时,它创建了几个与.class
文件结构,并提供enum
构造。例如,Java编译器将创建一个.class
的文件enum
构造Colors
表示类似于以下代码的代码:
final class Colors extends java.lang.Enum {
public final static Colors RED = new Colors("RED", 0);
public final static Colors BLUE = new Colors("WHITE", 1);
private final static values = new Colors[]{ RED, BLUE };
private Colors(String name, int ordinal) {
super(name, ordinal);
}
public static Colors[] values(){
return values;
}
public static Colors valueOf(String name){
return (Colors)java.lang.Enum.valueOf(Colors.class, name);
}
}
Java编译器为此创建三个构造函数和方法。enum
建造:Colors(String name, int ordinal)
, Colors[] values()
,和Colors valueOf(String name)
。方法values
和valueOf
被隐式宣布。因此,它们的形式参数名称也被隐式声明。
这,这个,那,那个enum
构造器Colors(String name, int ordinal)
是默认构造函数,并被隐式声明。但是,此构造函数的形式参数(name
和ordinal
)是不含蓄地宣布。因为这些形式参数既不是显式的,也不是隐式的,所以它们是合成的。的默认构造函数的形式参数。enum
构造没有被隐式声明,因为不同的编译器不需要就这个构造函数的形式达成一致;另一个Java编译器可能会为它指定不同的形式参数。编译器编译使用enum
常量,它们仅依赖于enum
构造,它是隐式声明的,而不是它们的构造函数或这些常量是如何初始化的。)
因此,这个例子MethodParameterExample
打印有关enum
构造Colors
:
enum Colors:
Number of constructors: 0
Number of declared constructors: 1
Declared constructor #1
private MethodParameterExamples$Colors()
Parameter class: class java.lang.String
Parameter name: $enum$name
Modifiers: 4096
Is implicit?: false
Is name present?: true
Is synthetic?: true
Parameter class: int
Parameter name: $enum$ordinal
Modifiers: 4096
Is implicit?: false
Is name present?: true
Is synthetic?: true
Number of methods: 2
Method #1
public static MethodParameterExamples$Colors[]
MethodParameterExamples$Colors.values()
Return type: class [LMethodParameterExamples$Colors;
Generic return type: class [LMethodParameterExamples$Colors;
Method #2
public static MethodParameterExamples$Colors
MethodParameterExamples$Colors.valueOf(java.lang.String)
Return type: class MethodParameterExamples$Colors
Generic return type: class MethodParameterExamples$Colors
Parameter class: class java.lang.String
Parameter name: name
Modifiers: 32768
Is implicit?: true
Is name present?: true
Is synthetic?: false
有几个修饰符可能是方法声明的一部分:
public
, protected
,和private
static
final
abstract
synchronized
native
strictfp
这,这个,那,那个
示例列出具有给定名称的方法的修饰符。它还显示该方法是合成的(编译器生成的)、变量性的,还是桥方法(编译器生成的以支持通用接口)。MethodModifierSpy
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import static java.lang.System.out;
public class MethodModifierSpy {
private static int count;
private static synchronized void inc() { count++; }
private static synchronized int cnt() { return count; }
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(" Modifiers: %s%n",
Modifier.toString(m.getModifiers()));
out.format(" [ synthetic=%-5b var_args=%-5b bridge=%-5b ]%n",
m.isSynthetic(), m.isVarArgs(), m.isBridge());
inc();
}
out.format("%d matching overload%s found%n", cnt(),
(cnt() == 1 ? "" : "s"));
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
输出的几个例子
产生跟随。MethodModifierSpy
$ java MethodModifierSpy java.lang.Object wait
public final void java.lang.Object.wait() throws java.lang.InterruptedException
Modifiers: public final
[ synthetic=false var_args=false bridge=false ]
public final void java.lang.Object.wait(long,int)
throws java.lang.InterruptedException
Modifiers: public final
[ synthetic=false var_args=false bridge=false ]
public final native void java.lang.Object.wait(long)
throws java.lang.InterruptedException
Modifiers: public final native
[ synthetic=false var_args=false bridge=false ]
3 matching overloads found
$ java MethodModifierSpy java.lang.StrictMath toRadians
public static double java.lang.StrictMath.toRadians(double)
Modifiers: public static strictfp
[ synthetic=false var_args=false bridge=false ]
1 matching overload found
$ java MethodModifierSpy MethodModifierSpy inc
private synchronized void MethodModifierSpy.inc()
Modifiers: private synchronized
[ synthetic=false var_args=false bridge=false ]
1 matching overload found
$ java MethodModifierSpy java.lang.Class getConstructor
public java.lang.reflect.Constructor java.lang.Class.getConstructor
(java.lang.Class[]) throws java.lang.NoSuchMethodException,
java.lang.SecurityException
Modifiers: public transient
[ synthetic=false var_args=true bridge=false ]
1 matching overload found
$ java MethodModifierSpy java.lang.String compareTo
public int java.lang.String.compareTo(java.lang.String)
Modifiers: public
[ synthetic=false var_args=false bridge=false ]
public int java.lang.String.compareTo(java.lang.Object)
Modifiers: public volatile
[ synthetic=true var_args=false bridge=true ]
2 matching overloads found
请注意Method.isVarArgs()
回报true
为Class.getConstructor()
。这表明方法声明如下所示:
public Constructor getConstructor(Class>... parameterTypes)
不是这样的:
public Constructor getConstructor(Class> [] parameterTypes)
注意,String.compareTo()
包含两个方法。中声明的方法String.java
:
public int compareTo(String anotherString);
一秒合成或编译器生成的桥牌方法。这是因为String
实现参数化接口Comparable
。在类型删除期间,继承方法的参数类型。Comparable.compareTo()
从java.lang.Object
到java.lang.String
。的参数类型compareTo
方法Comparable
和String
擦除后不再匹配,不可能发生重写。在所有其他情况下,这将产生编译时错误,因为接口没有实现。增加的桥梁方法避免了这个问题。
Method
实施器java.lang.reflect.AnnotatedElement
。因此,任何运行时注释java.lang.annotation.RetentionPolicy.RUNTIME
可能会被取回。有关获取注释的示例,请参见检查类修饰符和类型.
参考Java语言规范有关隐式声明结构的更多信息,包括在反射API中显示为隐式的参数。
反射提供了在类上调用方法的方法。通常,只有当无法在非反射代码中将类的实例强制转换为所需类型时,才需要这样做。方法调用java.lang.reflect.Method.invoke()
。第一个参数是要调用此特定方法的对象实例。(如果方法是static
,第一个论点应该是null
)后续参数是方法的参数。如果基础方法引发异常,则它将由java.lang.reflect.InvocationTargetException
。方法的原始异常可以使用异常链接机制的InvocationTargetException.getCause()
方法。
考虑使用反射在给定类中调用私有测试方法的测试套件。这,这个,那,那个
示例搜索Deet
public
类中以字符串开头的test
“,有一个布尔返回类型,并且有一个Locale
参数。然后调用每个匹配方法。
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 {
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 %n");
return;
}
try {
Class> c = Class.forName(args[0]);
Object t = c.newInstance();
Method[] allMethods = c.getDeclaredMethods();
for (Method m : allMethods) {
String mname = m.getName();
if (!mname.startsWith("test")
|| (m.getGenericReturnType() != boolean.class)) {
continue;
}
Type[] pType = m.getGenericParameterTypes();
if ((pType.length != 1)
|| Locale.class.isAssignableFrom(pType[0].getClass())) {
continue;
}
out.format("invoking %s()%n", mname);
try {
m.setAccessible(true);
Object o = m.invoke(t, new Locale(args[1], args[2], args[3]));
out.format("%s() returned %b%n", mname, (Boolean) o);
// Handle any exceptions thrown by method to be invoked.
} catch (InvocationTargetException x) {
Throwable cause = x.getCause();
err.format("invocation of %s failed: %s%n",
mname, cause.getMessage());
}
}
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
Deet
调用getDeclaredMethods()
它将返回类中显式声明的所有方法。还有,Class.isAssignableFrom()
用于确定定位方法的参数是否与所需的调用兼容。从技术上讲,代码可以测试以下语句是否是true
自Locale
是final
:
Locale.class == pType[0].getClass()
然而,Class.isAssignableFrom()
更普遍。
$ java Deet Deet ja JP JP
invoking testDeet()
Locale = Japanese (Japan,JP),
ISO Language Code = jpn
testDeet() returned true
$ java Deet Deet xx XX XX
invoking testDeet()
invocation of testDeet failed:
Couldn't find 3-letter language code for xx
首先,请注意,只有testDeet()
符合代码强制执行的声明限制。接下来,什么时候testDeet()
传递无效的参数,它会抛出未经检查的java.util.MissingResourceException
。在反射中,对检查异常和未检查异常的处理没有区别。它们都被包裹在一个InvocationTargetException
Method.invoke()
可用于将可变数量的参数传递给方法。需要理解的关键概念是,变量性方法的实现就好像变量参数被打包在数组中一样。
这,这个,那,那个
示例演示如何调用InvokeMain
main()
任何类的入口点,并传递在运行时确定的一组参数。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
public class InvokeMain {
public static void main(String... args) {
try {
Class> c = Class.forName(args[0]);
Class[] argTypes = new Class[] { String[].class };
Method main = c.getDeclaredMethod("main", argTypes);
String[] mainArgs = Arrays.copyOfRange(args, 1, args.length);
System.out.format("invoking %s.main()%n", c.getName());
main.invoke(null, (Object)mainArgs);
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
}
}
}
首先,找到main()
方法搜索名称为“main”的类,其参数为String
自main()
是static
, null
的第一个参数Method.invoke()
。第二个参数是要传递的参数数组。
$ java InvokeMain Deet Deet ja JP JP
invoking Deet.main()
invoking testDeet()
Locale = Japanese (Japan,JP),
ISO Language Code = jpn
testDeet() returned true
本节包含开发人员在使用反射定位、调用或获取有关方法的信息时可能遇到的问题。
这,这个,那,那个
示例说明了在类中搜索特定方法的代码不考虑类型擦除时会发生什么情况。MethodTrouble
import java.lang.reflect.Method;
public class MethodTrouble {
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()).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();
}
}
}
$ 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)
$ java MethodTrouble lookup java.lang.Object
Found:
public void MethodTrouble.lookup(T)
当使用泛型参数类型声明方法时,编译器将用其上限替换泛型类型,在本例中,T
是Object
。因此,当代码搜索lookup(Integer)
,尽管事实是MethodTrouble
创建如下:
Class> c = (new MethodTrouble()).getClass();
寻觅lookup(Object)
如预期的那样成功。
$ java MethodTrouble find java.lang.Integer
Found:
public void MethodTrouble.find(java.lang.Integer)
$ java MethodTrouble find java.lang.Object
java.lang.NoSuchMethodException: MethodTrouble.find(java.lang.Object)
at java.lang.Class.getMethod(Class.java:1605)
at MethodTrouble.main(MethodTrouble.java:12)
在这种情况下,find()
没有泛型参数,因此getMethod()
必须完全匹配。
提示:在搜索方法时,始终传递参数化类型的上限。
阿IllegalAccessException
如果尝试调用private
或者其他无法访问的方法。
这,这个,那,那个
示例显示了一个典型的堆栈跟踪,它是试图在另一个类中调用私有方法的结果。MethodTroubleAgain
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
class AnotherClass {
private void m() {}
}
public class MethodTroubleAgain {
public static void main(String... args) {
AnotherClass ac = new AnotherClass();
try {
Class> c = ac.getClass();
Method m = c.getDeclaredMethod("m");
// m.setAccessible(true); // solution
Object o = m.invoke(ac); // IllegalAccessException
// production code should handle these exceptions more gracefully
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
引发的异常的堆栈跟踪如下。
$ 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()
已经被改造成一种可变的方法。这是一个巨大的方便,但它会导致意想不到的行为。这,这个,那,那个
示例显示了各种方法MethodTroubleToo
Method.invoke()
会产生混乱的结果。
import java.lang.reflect.Method;
public class MethodTroubleToo {
public void ping() { System.out.format("PONG!%n"); }
public static void main(String... args) {
try {
MethodTroubleToo mtt = new MethodTroubleToo();
Method m = MethodTroubleToo.class.getMethod("ping");
switch(Integer.parseInt(args[0])) {
case 0:
m.invoke(mtt); // works
break;
case 1:
m.invoke(mtt, null); // works (expect compiler warning)
break;
case 2:
Object arg2 = null;
m.invoke(mtt, arg2); // IllegalArgumentException
break;
case 3:
m.invoke(mtt, new Object[0]); // works
break;
case 4:
Object arg4 = new Object[0];
m.invoke(mtt, arg4); // IllegalArgumentException
break;
default:
System.out.format("Test not found%n");
}
// production code should handle these exceptions more gracefully
} catch (Exception x) {
x.printStackTrace();
}
}
}
$ java MethodTroubleToo 0
PONG!
的所有参数Method.invoke()
都是可选的,除非第一种方法是可选的,如果要调用的方法没有参数,则可以省略它们。
$ java MethodTroubleToo 1
PONG!
本例中的代码生成此编译器警告,因为null
是模棱两可的。
$ javac MethodTroubleToo.java
MethodTroubleToo.java:16: warning: non-varargs call of varargs method with
inexact argument type for last parameter;
m.invoke(mtt, null); // works (expect compiler warning)
^
cast to Object for a varargs call
cast to Object[] for a non-varargs call and to suppress this warning
1 warning
无法确定是否null
表示参数的空数组或null
.
$ java MethodTroubleToo 2
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke
(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke
(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at MethodTroubleToo.main(MethodTroubleToo.java:21)
尽管争论是null
,因为类型是Object
和ping()
根本没有任何争论。
$ java MethodTroubleToo 3
PONG!
这是因为new Object[0]
创建一个空数组,而对于varargs方法,这相当于不传递任何可选参数。
$ 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
。这个失败的原因和第二个案子失败的原因一样,ping()
不会有争论。
提示:当一种方法foo(Object... o)
,则编译器将传递给foo()
在类型数组中Object
。执行foo()
与声明的内容相同。foo(Object[] o)
。了解这一点可能有助于避免上述问题的类型。
阿InvocationTargetException
包装调用方法对象时产生的所有异常(检查和未检查)。这,这个,那,那个
示例演示如何检索被调用的方法引发的原始异常。MethodTroubleReturns
$ java MethodTroubleReturns
drinkMe() failed: I can't drink a negative amount of liquid
提示:如果InvocationTargetException
则调用该方法。对该问题的诊断将与直接调用该方法并抛出由getCause()
。此异常并不表示反射包或其使用出现问题。
A 构造器用于创建作为类实例的对象。通常,它在调用方法或访问字段之前执行初始化类所需的操作。构造函数从不被继承。
与方法类似,反射提供API来发现和检索类的构造函数,并获得声明信息,例如修饰符、参数、注释和抛出的异常。还可以使用指定的构造函数创建类的新实例。在使用构造函数时使用的键类为Class
和java.lang.reflect.Constructor
。涉及构造函数的常见操作将在以下各节中讨论:
构造函数声明包括名称、修饰符、参数和可抛异常列表。这,这个,那,那个java.lang.reflect.Constructor
类提供了获取此信息的方法。
这,这个,那,那个
示例说明如何在类的声明构造函数中搜索具有给定类型参数的构造函数。ConstructorSift
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import static java.lang.System.out;
public class ConstructorSift {
public static void main(String... args) {
try {
Class> cArg = Class.forName(args[1]);
Class> c = Class.forName(args[0]);
Constructor[] allConstructors = c.getDeclaredConstructors();
for (Constructor ctor : allConstructors) {
Class>[] pType = ctor.getParameterTypes();
for (int i = 0; i < pType.length; i++) {
if (pType[i].equals(cArg)) {
out.format("%s%n", ctor.toGenericString());
Type[] gpType = ctor.getGenericParameterTypes();
for (int j = 0; j < gpType.length; j++) {
char ch = (pType[j].equals(cArg) ? '*' : ' ');
out.format("%7c%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()
这并没有因为仿制药的引入而改变。其他有名称的方法getGenericFoo()
有价值的,有价值的福在反射中实现类似的。的返回值的语法。Method.get*Types()
在Class.getName()
.
中的所有构造函数的输出如下java.util.Formatter
其中有一个Locale
争论。
$ java ConstructorSift java.util.Formatter java.util.Locale
public
java.util.Formatter(java.io.OutputStream,java.lang.String,java.util.Locale)
throws java.io.UnsupportedEncodingException
GenericParameterType[0]: class java.io.OutputStream
GenericParameterType[1]: class java.lang.String
*GenericParameterType[2]: class java.util.Locale
public java.util.Formatter(java.lang.String,java.lang.String,java.util.Locale)
throws java.io.FileNotFoundException,java.io.UnsupportedEncodingException
GenericParameterType[0]: class java.lang.String
GenericParameterType[1]: class java.lang.String
*GenericParameterType[2]: class java.util.Locale
public java.util.Formatter(java.lang.Appendable,java.util.Locale)
GenericParameterType[0]: interface java.lang.Appendable
*GenericParameterType[1]: class java.util.Locale
public java.util.Formatter(java.util.Locale)
*GenericParameterType[0]: class java.util.Locale
public java.util.Formatter(java.io.File,java.lang.String,java.util.Locale)
throws java.io.FileNotFoundException,java.io.UnsupportedEncodingException
GenericParameterType[0]: class java.io.File
GenericParameterType[1]: class java.lang.String
*GenericParameterType[2]: class java.util.Locale
下一个示例输出说明如何搜索类型为char[]
在……里面String
.
$ 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()
。注意,第一个列出的构造函数是package-private
,不是public
。因为示例代码使用Class.getDeclaredConstructors()
而不是Class.getConstructors()
,它只返回public
建筑工人。
这个例子显示,搜索变量性的参数(它有可变的参数)需要使用数组语法:
$ java ConstructorSift java.lang.ProcessBuilder "[Ljava.lang.String;"
public java.lang.ProcessBuilder(java.lang.String[])
*GenericParameterType[0]: class [Ljava.lang.String;
这是ProcessBuilder
构造函数在源代码中:
public ProcessBuilder(String... command)
该参数表示为类型为一维数组。java.lang.String
。可以将其与显式地表示为java.lang.String
通过调用Constructor.isVarArgs()
.
最后一个示例报告了用泛型参数类型声明的构造函数的输出:
$ 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
由于构造函数在语言中的作用,比方法更少的修饰符是有意义的:
public
, protected
,和private
这,这个,那,那个
示例使用指定的访问修饰符搜索给定类中的构造函数。它还显示构造函数是合成的(编译器生成的)还是可变的。ConstructorAccess
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import static java.lang.System.out;
public class ConstructorAccess {
public static void main(String... args) {
try {
Class> c = Class.forName(args[0]);
Constructor[] allConstructors = c.getDeclaredConstructors();
for (Constructor ctor : allConstructors) {
int searchMod = modifierFromString(args[1]);
int mods = accessModifiers(ctor.getModifiers());
if (searchMod == mods) {
out.format("%s%n", ctor.toGenericString());
out.format(" [ synthetic=%-5b var_args=%-5b ]%n",
ctor.isSynthetic(), ctor.isVarArgs());
}
}
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
private static int accessModifiers(int m) {
return m & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED);
}
private static int modifierFromString(String s) {
if ("public".equals(s)) return Modifier.PUBLIC;
else if ("protected".equals(s)) return Modifier.PROTECTED;
else if ("private".equals(s)) return Modifier.PRIVATE;
else if ("package-private".equals(s)) return 0;
else return -1;
}
}
没有明确的Modifier
常量,它对应于“包-私有”访问,因此有必要检查是否没有所有三个访问修饰符来标识包-私有构造函数。
此输出显示java.io.File
:
$ java ConstructorAccess java.io.File private
private java.io.File(java.lang.String,int)
[ synthetic=false var_args=false ]
private java.io.File(java.lang.String,java.io.File)
[ synthetic=false var_args=false ]
合成构造器很少,但是
示例说明了可能发生这种情况的典型情况:SyntheticConstructor
public class SyntheticConstructor {
private SyntheticConstructor() {}
class Inner {
// Compiler will generate a synthetic constructor since
// SyntheticConstructor() is private.
Inner() { new SyntheticConstructor(); }
}
}
$ java ConstructorAccess SyntheticConstructor package-private
SyntheticConstructor(SyntheticConstructor$1)
[ synthetic=true var_args=false ]
由于内部类的构造函数引用了封闭类的私有构造函数,所以编译器必须生成包私有构造函数。参数类型SyntheticConstructor$1
是任意的,并且依赖于编译器实现。依赖于任何合成或非公共类成员的代码可能是不可移植的。
构造器实现java.lang.reflect.AnnotatedElement
,它提供了用于检索运行时注释的方法。java.lang.annotation.RetentionPolicy.RUNTIME
。有关获取注释的示例,请参见检查类修饰符和类型部分。
有两种创建类实例的反射方法: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虚拟机默认字符集相同。java.nio.charset.Charset.defaultCharset()
)这,这个,那,那个
示例显示了如何实现这一目标:ConsoleCharset
import java.io.Console;
import java.nio.charset.Charset;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import static java.lang.System.out;
public class ConsoleCharset {
public static void main(String... args) {
Constructor[] ctors = Console.class.getDeclaredConstructors();
Constructor ctor = null;
for (int i = 0; i < ctors.length; i++) {
ctor = ctors[i];
if (ctor.getGenericParameterTypes().length == 0)
break;
}
try {
ctor.setAccessible(true);
Console c = (Console)ctor.newInstance();
Field f = c.getClass().getDeclaredField("cs");
f.setAccessible(true);
out.format("Console charset : %s%n", f.get(c));
out.format("Charset.defaultCharset(): %s%n",
Charset.defaultCharset());
// production code should handle these exceptions more gracefully
} catch (InstantiationException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (NoSuchFieldException x) {
x.printStackTrace();
}
}
}
注:
Class.newInstance()
只有在构造函数为零参数且已可访问的情况下才会成功。否则,就必须使用Constructor.newInstance()
如上例所示。
UNIX系统的示例输出:
$ java ConsoleCharset
Console charset : ISO-8859-1
Charset.defaultCharset() : ISO-8859-1
Windows系统的示例输出:
C:\> java ConsoleCharset
Console charset : IBM437
Charset.defaultCharset() : windows-1252
的另一种常见应用Constructor.newInstance()
调用接受参数的构造函数。这,这个,那,那个
示例找到一个特定的单参数构造函数并调用它:RestoreAliases
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static java.lang.System.out;
class EmailAliases {
private Set aliases;
private EmailAliases(HashMap 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 defaultAliases = new HashMap();
static {
defaultAliases.put("Duke", "duke@i-love-java");
defaultAliases.put("Fang", "fang@evil-jealous-twin");
}
public static void main(String... args) {
try {
Constructor ctor = EmailAliases.class.getDeclaredConstructor(HashMap.class);
ctor.setAccessible(true);
EmailAliases email = (EmailAliases)ctor.newInstance(defaultAliases);
email.printKeys();
// production code should handle these exceptions more gracefully
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
}
}
}
此示例使用Class.getDeclaredConstructor()
若要查找具有单个类型参数的构造函数,请执行以下操作java.util.HashMap
。请注意,它已经足够通过。HashMap.class
因为参数到任何get*Constructor()
方法仅为类型目的需要类。由于类型擦除,下面的表达式计算为true
:
HashMap.class == defaultAliases.getClass()
然后,该示例使用以下构造函数创建类的新实例Constructor.newInstance()
.
$ java RestoreAliases
Mail keys:
Duke
Fang
开发人员在试图通过反射调用构造函数时有时会遇到以下问题。
这,这个,那,那个ConstructorTrouble
示例说明了当代码试图使用Class.newInstance()
并且没有可访问的零参数构造函数:
public class ConstructorTrouble {
private ConstructorTrouble(int i) {}
public static void main(String... args){
try {
Class> c = Class.forName("ConstructorTrouble");
Object o = c.newInstance(); // InstantiationException
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
$ 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
都会失败。
这,这个,那,那个ConstructorTroubleToo
示例显示了一个无法解决的问题。Class.newInstance()
。也就是说,它传播由构造函数引发的任何异常检查或未检查。
import java.lang.reflect.InvocationTargetException;
import static java.lang.System.err;
public class ConstructorTroubleToo {
public ConstructorTroubleToo() {
throw new RuntimeException("exception in constructor");
}
public static void main(String... args) {
try {
Class> c = Class.forName("ConstructorTroubleToo");
// Method propagetes any exception thrown by the constructor
// (including checked exceptions).
if (args.length > 0 && args[0].equals("class")) {
Object o = c.newInstance();
} else {
Object o = c.getConstructor().newInstance();
}
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
err.format("%n%nCaught exception: %s%n", x.getCause());
}
}
}
$ java ConstructorTroubleToo class
Exception in thread "main" java.lang.RuntimeException: exception in constructor
at ConstructorTroubleToo.(ConstructorTroubleToo.java:6)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance
(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance
(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at java.lang.Class.newInstance0(Class.java:355)
at java.lang.Class.newInstance(Class.java:308)
at ConstructorTroubleToo.main(ConstructorTroubleToo.java:15)
这种情况对反思来说是独一无二的。通常,不可能编写忽略检查异常的代码,因为它不会编译。可以将构造函数引发的任何异常包装为Constructor.newInstance()
而不是Class.newInstance()
.
$ java ConstructorTroubleToo
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance
(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance
(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at ConstructorTroubleToo.main(ConstructorTroubleToo.java:17)
Caused by: java.lang.RuntimeException: exception in constructor
at ConstructorTroubleToo.(ConstructorTroubleToo.java:6)
... 5 more
Caught exception: java.lang.RuntimeException: exception in constructor
如果InvocationTargetException
则调用该方法。对问题的诊断将与直接调用构造函数并抛出由InvocationTargetException.getCause()
。此异常并不表示反射包或其使用出现问题。
提示:最好用Constructor.newInstance()
过关Class.newInstance()
因为前一个API允许检查和处理由构造函数引发的任意异常。
这,这个,那,那个ConstructorTroubleAgain
类说明错误代码可能无法定位或调用预期构造函数的各种方法。
import java.lang.reflect.InvocationTargetException;
import static java.lang.System.out;
public class ConstructorTroubleAgain {
public ConstructorTroubleAgain() {}
public ConstructorTroubleAgain(Integer i) {}
public ConstructorTroubleAgain(Object o) {
out.format("Constructor passed Object%n");
}
public ConstructorTroubleAgain(String s) {
out.format("Constructor passed String%n");
}
public static void main(String... args){
String argType = (args.length == 0 ? "" : args[0]);
try {
Class> c = Class.forName("ConstructorTroubleAgain");
if ("".equals(argType)) {
// IllegalArgumentException: wrong number of arguments
Object o = c.getConstructor().newInstance("foo");
} else if ("int".equals(argType)) {
// NoSuchMethodException - looking for int, have Integer
Object o = c.getConstructor(int.class);
} else if ("Object".equals(argType)) {
// newInstance() does not perform method resolution
Object o = c.getConstructor(Object.class).newInstance("foo");
} else {
assert false;
}
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
$ java ConstructorTroubleAgain
Exception in thread "main" java.lang.IllegalArgumentException: wrong number of
arguments
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance
(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance
(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at ConstructorTroubleAgain.main(ConstructorTroubleAgain.java:23)
阿IllegalArgumentException
引发,因为请求了零参数构造函数,并且试图传递参数。如果传递了错误类型的参数,则将引发相同的异常。
$ java ConstructorTroubleAgain int
java.lang.NoSuchMethodException: ConstructorTroubleAgain.(int)
at java.lang.Class.getConstructor0(Class.java:2706)
at java.lang.Class.getConstructor(Class.java:1657)
at ConstructorTroubleAgain.main(ConstructorTroubleAgain.java:26)
如果开发人员错误地认为反射将自动装箱或打开框类型,则可能发生此异常。装箱(将原语转换为引用类型)仅在编译期间发生。反射中没有发生此操作的机会,因此在定位构造函数时必须使用特定类型。
$ java ConstructorTroubleAgain Object
Constructor passed Object
在这里,可能会期望构造函数采用String
参数将被引用,因为newInstance()
使用更具体的String
类型。但是已经太晚了!找到的构造函数已经是具有Object
争论。newInstance()
不尝试执行方法解析;它只是对现有的构造函数对象进行操作。
提示:一个重要的区别new
和Constructor.newInstance()
那是new
执行方法参数类型检查、装箱和方法解析。所有这些都不是在反思中发生的,必须作出明确的选择。
阿IllegalAccessException
如果试图调用私有或其他不可访问的构造函数,则可能引发。这,这个,那,那个ConstructorTroubleAccess
示例说明了结果堆栈跟踪。
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
class Deny {
private Deny() {
System.out.format("Deny constructor%n");
}
}
public class ConstructorTroubleAccess {
public static void main(String... args) {
try {
Constructor c = Deny.class.getDeclaredConstructor();
// c.setAccessible(true); // solution
c.newInstance();
// production code should handle these exceptions more gracefully
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
$ 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()
.