JAVA™教程(甲骨文官方)

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


 

TRAIL:反射API

反射的使用

反射通常由需要检查或修改运行在Java虚拟机中的应用程序的运行时行为的程序使用。这是一个相对高级的特性,应该只供对语言的基本知识有很强了解的开发人员使用。考虑到这一点,反射是一种强大的技术,可以使应用程序执行本来是不可能的操作。

可扩展性特征

应用程序可以通过使用可扩展对象的完全限定名创建可扩展对象的实例,从而利用外部的、用户定义的类。

类浏览器和可视化开发环境

类浏览器需要能够枚举类的成员。可视化开发环境可以通过使用反射中可用的类型信息来帮助开发人员编写正确的代码。

调试器和测试工具

调试器需要能够检查类上的私有成员。测试工具可以利用反射系统地调用定义在类上的可发现集API,以确保测试套件中的代码覆盖率很高。

反射缺陷

反射是强大的,但不应任意使用。如果可以在不使用反射的情况下执行操作,则最好避免使用它。在通过反射访问代码时,应该记住以下几点。

性能开销

由于反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化。因此,反射操作的性能比它们的非反射操作要慢,在性能敏感的应用程序中经常调用的代码部分应该避免反射操作。

安全限制

反射需要在安全管理器下运行时可能不存在的运行时权限。这是对必须在受限的安全上下文中运行的代码的一个重要考虑,例如在applet中。

内件暴露

因为反射允许代码执行在非反射代码中为非法的操作,例如访问。private字段和方法中,反射的使用会产生意想不到的副作用,这可能会导致代码功能失调并破坏可移植性。反射代码破坏抽象,因此可能会随着平台的升级而改变行为。

线索课

这条线索涵盖了用于访问和操作类、字段、方法和构造函数的反射的常见用途。每一课都包含代码示例、提示和故障排除信息。

trail icon

本课展示了获取Class对象,并使用它检查类的属性,包括其声明和内容。

trail icon成员

本课介绍如何使用反射API查找类的字段、方法和构造函数。为设置和获取字段值、调用方法以及使用特定构造函数创建对象的新实例提供了示例。

trail icon数组和枚举类型

本课介绍了两种特殊类型的类:数组,它们是在运行时生成的,以及enum类型,它们定义唯一的命名对象实例。示例代码演示如何检索数组的组件类型,以及如何使用数组或enum类型。


注: 

本实验中的示例是为试验反射API而设计的。因此,异常的处理与在生产代码中使用的不一样。特别是,在生产代码中,不建议转储对用户可见的堆栈跟踪。


课程:课程

每种类型要么是引用,要么是原语。类、枚举和数组(它们都是从java.lang.Object)以及接口都是引用类型。引用类型的示例包括java.lang.String,基本类型的所有包装类,如java.lang.Double,接口java.io.Serializable,以及Enumjavax.swing.SortOrder。有一组固定的基本类型:booleanbyteshortintlongcharfloat,和double.

对于每种类型的对象,Java虚拟机实例化java.lang.Class它提供了检查对象的运行时属性(包括其成员和类型信息)的方法。Class还提供了创建新类和对象的能力。最重要的是,它是所有反射API的入口点。本课程涵盖涉及类的最常用的反射操作:

  • 检索类对象描述获取Class
  • 检查类修饰符和类型演示如何访问类声明信息。
  • 发现类成员说明如何列出类中的构造函数、字段、方法和嵌套类。
  • 故障排除描述在使用Class

检索类对象

所有反射操作的入口点是java.lang.Class。除了.java.lang.reflect.ReflectPermission中的任何一个类java.lang.reflect有公共建设者。要获得这些类,必须在Class。有几种方法可以获得Class取决于代码是否有权访问对象、类名、类型或现有的Class.

Object.getClass()

如果一个对象的实例是可用的,那么获取其Class就是调用Object.getClass()。当然,这只适用于所有继承自Object。下面是一些例子。

Class c = "foo".getClass();

返回ClassString

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"类型的名称。这也是获取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.forName()

如果类的完全限定名可用,则可以获得相应的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.TYPEdouble.class.

Class c = Void.TYPE;

Void.TYPE是相同的void.class.

返回类的方法。

有几个反射API返回类,但只有在Class已经直接或间接获得了。

Class.getSuperclass()

返回给定类的超类。

Class c = javax.swing.JButton.class.getSuperclass();

超级阶级javax.swing.JButtonjavax.swing.AbstractButton.

Class.getClasses()

返回类的所有公共类、接口和枚举,包括继承的成员。

Class[] c = Character.class.getClasses();

Character包含两个成员类。Character.SubsetCharacter.UnicodeBlock.

Class.getDeclaredClasses()

返回该类中显式声明的所有类接口和枚举。

Class[] c = Character.class.getDeclaredClasses();

Character包含两个公共成员类。Character.SubsetCharacter.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();
}

定义的匿名类的声明类。onull.

Class.getEnclosingClass()

返回类的立即封装类。

Class c = Thread.State.class().getEnclosingClass();

围封类Thread.StateThread.

public class MyClass {
    static Object o = new Object() { 
        public void m() {} 
    };
    static Class = o.getClass().getEnclosingClass();
}

定义的匿名类。oMyClass.

检查类修饰符和类型

类可以使用影响其运行时行为的一个或多个修饰符声明:

  • 访问修饰符:publicprotected,和private
  • 需要重写的修饰符:abstract
  • 仅限于一个实例的修饰符:static
  • 禁止修改价值的改性剂:final
  • 修饰符强制严格的浮点行为:strictfp
  • 注解

并非所有类都允许所有修饰符,例如,接口不能final一个枚举是不可能的abstractjava.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。编译器为每个接口添加此修饰符。此外,此声明包含两个泛型类型参数,KV。示例代码只打印这些参数的名称,但是否可以使用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虚拟机定义。特别是,数组实现Cloneablejava.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.RetentionPolicyRUNTIME是可以接近的。语言中预定义的三个注释。@Deprecated@Override,和@SuppressWarnings@Deprecated在运行时可用。

发现类成员

中提供了两类方法。Class用于访问字段、方法和构造函数:枚举这些成员的方法和搜索特定成员的方法。此外,有不同的方法可以直接访问类上声明的成员,而不是在超接口和超类中搜索继承的成员。下表提供了所有成员定位方法及其特性的摘要。

 

字段定位的类方法
ClassAPI 成员名单? 继承的成员? 私人会员?
getDeclaredField()
getField()
getDeclaredFields()
getFields()

 

用于定位方法的类方法
ClassAPI 成员名单? 继承的成员? 私人会员?
getDeclaredMethod()
getMethod()
getDeclaredMethods()
getMethods()

 

构造函数定位的类方法
ClassAPI 成员名单? 继承的成员? 私人会员?
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()为了这个目的。这,这个,那,那个EnumSpy在这条线索的后面一节中,检查Enum,包含可能的实现。

在输出的“方法”部分中,注意到方法名称包括声明类的名称。因此,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.


当构造器不可访问时引发InstantiationException。

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.Fieldjava.lang.reflect.Method,和java.lang.reflect.Constructor。这些对象将在本课中讨论。对于每个成员,本课程将描述用于检索声明和类型信息的相关API、成员特有的任何操作(例如,设置字段的值或调用方法),以及常见的错误。每个概念都将用代码示例和相关输出来说明,这些输出与一些预期的反射用途大致相同。


注:根据Java语言规范,JavaSE 7版成员类的继承组件,包括字段、方法、嵌套类、接口和枚举类型。因为构造函数不是继承的,所以它们不是成员。的实现类不同。java.lang.reflect.Member.


字段

字段有类型和值。这,这个,那,那个java.lang.reflect.Field类提供用于访问类型信息以及设置和获取给定对象上字段的值的方法。

  • 获取字段类型描述如何获取字段的声明类型和泛型类型。
  • 字段修饰符的检索与解析演示如何获取字段声明的某些部分,如publictransient
  • 获取和设置字段值说明如何访问字段值。
  • 故障排除描述一些可能导致混淆的常见编码错误。

方法

方法具有返回值、参数,并可能引发异常。这,这个,那,那个java.lang.reflect.Method类提供用于获取参数和返回值的类型信息的方法。它还可以用于调用给定对象上的方法。

  • 获取方法类型信息演示如何枚举类中声明的方法并获取类型信息。
  • 获取方法参数的名称演示如何检索方法或构造函数参数的名称和其他信息。
  • 检索和解析方法修饰符描述如何访问和解码修饰符以及与该方法关联的其他信息。
  • 调用方法说明如何执行方法并获取其返回值。
  • 故障排除介绍在查找或调用方法时遇到的常见错误。

构造函数

构造函数的反射api定义在java.lang.reflect.Constructor与方法类似,只有两个主要的例外:第一,构造函数没有返回值;第二,构造函数的调用为给定类创建对象的新实例。

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

 

场域是具有关联值的类、接口或枚举。方法java.lang.reflect.Field类可以检索有关该字段的信息,如其名称、类型、修饰符和注释。(该款检查类修饰符和类型在.。班课程描述了如何检索注释。)还有一些方法可以动态访问和修改字段的值。这些任务将在以下章节中讨论:

  • 获取字段类型描述如何获取字段的声明类型和泛型类型。
  • 字段修饰符的检索与解析演示如何获取字段声明的某些部分,如publictransient
  • 获取和设置字段值说明如何访问字段值。
  • 故障排除描述一些可能导致混淆的常见编码错误。

在编写应用程序(如类浏览器)时,找出哪些字段属于特定类可能是有用的。类的字段通过调用Class.getFields()。这,这个,那,那个getFields()方法返回一个Field对象,每个可访问的公共字段包含一个对象。

如果公共字段是下列任一项的成员,则可访问该公共字段:

  • 这个班
  • 这类的超级阶级
  • 由该类实现的接口。
  • 从该类实现的接口扩展而来的接口。

字段可以是类(实例)字段,如java.io.Reader.lock,静态字段,如java.lang.Integer.MAX_VALUE,或枚举常量,如java.lang.Thread.State.WAITING.

 

获取字段类型

字段可以是原始类型的,也可以是引用类型的。有八种原始类型:booleanbyteshortintlongcharfloat,和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();
	}
    }
}

获取该类中三个公共字段的类型的示例输出(bname,和参数化类型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有价值的,有价值的都以类似的方式实现。

 

字段修饰符的检索与解析

有几个修饰符可能是字段声明的一部分:

  • 访问修饰符:publicprotected,和private
  • 控制运行时行为的特定字段修饰符:transientvolatile
  • 仅限于一个实例的修饰符: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。有关获取注释的示例,请参见检查类修饰符和类型.

 

获取和设置字段值

给定类的实例,可以使用反射设置该类中字段的值。通常只有在不可能以通常方式设置值的特殊情况下才会这样做。因为这种访问通常违反类的设计意图,所以使用时应该非常谨慎。

这,这个,那,那个Book类说明如何为long、Array和enum字段类型设置值。获取和设置其他基本类型的方法将在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*()也许不会。

故障排除

下面是开发人员遇到的几个常见问题,并解释了为什么会发生这种情况,以及如何解决这些问题。

因不可转换类型而引发的IllegalArgumentException

这,这个,那,那个FieldTrouble示例将生成一个IllegalArgumentExceptionField.setInt()调用以设置引用类型的字段。Integer具有基元类型的值。在非反射等价物中Integer val = 42,编译器将转换(或盒、箱(子))原语类型42引用类型为new Integer(42)以便其类型检查将接受该语句。当使用反射时,类型检查只在运行时进行,因此没有机会将值框起来。

import java.lang.reflect.Field;

public class FieldTrouble {
    public Integer val;

    public static void main(String... args) {
	FieldTrouble ft = new FieldTrouble();
	try {
	    Class c = ft.getClass();
	    Field f = c.getDeclaredField("val");
  	    f.setInt(ft, 42);               // IllegalArgumentException

        // production code should handle these exceptions more gracefully
	} catch (NoSuchFieldException x) {
	    x.printStackTrace();
 	} catch (IllegalAccessException x) {
 	    x.printStackTrace();
	}
    }
}
$ java FieldTrouble
Exception in thread "main" java.lang.IllegalArgumentException: Can not set
  java.lang.Object field FieldTrouble.val to (long)42
        at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException
          (UnsafeFieldAccessorImpl.java:146)
        at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException
          (UnsafeFieldAccessorImpl.java:174)
        at sun.reflect.UnsafeObjectFieldAccessorImpl.setLong
          (UnsafeObjectFieldAccessorImpl.java:102)
        at java.lang.reflect.Field.setLong(Field.java:831)
        at FieldTrouble.main(FieldTrouble.java:11)

若要消除此异常,应将有问题的行替换为Field.set(Object obj, Object value):

f.set(ft, new Integer(43));

提示:当使用反射设置或获取字段时,编译器没有机会执行装箱操作。的规范所描述的关联类型。Class.isAssignableFrom()。该示例预计将失败,因为isAssignableFrom()会回来false在此测试中,可以编程方式使用该测试来验证是否可以进行特定的转换:

Integer.class.isAssignableFrom(int.class) == false

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

int.class.isAssignableFrom(Integer.class) == false

非公共字段的NoSuchFieldException

精明的读者可能会注意到,如果FieldSpy前面显示的示例用于获取非公共字段的信息,它将失败:

$ java FieldSpy java.lang.String count
java.lang.NoSuchFieldException: count
        at java.lang.Class.getField(Class.java:1519)
        at FieldSpy.main(FieldSpy.java:12)

提示:这,这个,那,那个Class.getField()Class.getFields()方法返回公众类、枚举或接口的成员字段。Class对象。中声明(但未继承)的所有字段。Class,使用Class.getDeclaredFields()方法。


修改最终字段时抛出IllegalAccessException

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()只有在安全上下文允许操作时才能成功。

 

方法

方法包含可被调用的可执行代码。方法是继承的,在非反射代码中,诸如重载、重写和隐藏等行为由编译器强制执行。相反,反射代码可以将方法选择限制在特定的类中,而不考虑它的超类。可以访问超类方法,但可以确定它们的声明类;这是不可能在没有反射的情况下以编程方式发现的,并且是许多细微错误的来源。

这,这个,那,那个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。(班级)MethodConstructor扩展类Executable从而继承该方法Executable.getParameters)然而,.class默认情况下,文件不存储正式参数名称。这是因为许多生成和使用类文件的工具可能不会期望使用更大的静态和动态占用空间。.class包含参数名称的文件。特别是,这些工具必须处理更大的.class文件和Java虚拟机(JVM)将使用更多的内存。此外,一些参数名称,如secretpassword,则可能公开有关安全敏感方法的信息。

将形式参数名称存储在特定的.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是隐式声明的,其参数也是隐式的。

:

  • Java编译器为内部类的构造函数创建一个正式参数,以使编译器能够将一个引用(表示立即包围的实例)从创建表达式传递给成员类的构造函数。
  • 值32784表示InnerClass构造函数既是最终构造函数(16),也是隐式构造函数(32768)。
  • Java编程语言允许带有美元符号的变量名($);但是,按照惯例,美元符号不用于变量名称。

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)。方法valuesvalueOf被隐式宣布。因此,它们的形式参数名称也被隐式声明。

这,这个,那,那个enum构造器Colors(String name, int ordinal)是默认构造函数,并被隐式声明。但是,此构造函数的形式参数(nameordinal)是含蓄地宣布。因为这些形式参数既不是显式的,也不是隐式的,所以它们是合成的。的默认构造函数的形式参数。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

 

检索和解析方法修饰符

有几个修饰符可能是方法声明的一部分:

  • 访问修饰符:publicprotected,和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()回报trueClass.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.Objectjava.lang.String。的参数类型compareTo方法ComparableString擦除后不再匹配,不可能发生重写。在所有其他情况下,这将产生编译时错误,因为接口没有实现。增加的桥梁方法避免了这个问题。

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()用于确定定位方法的参数是否与所需的调用兼容。从技术上讲,代码可以测试以下语句是否是trueLocalefinal:

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”的类,其参数为Stringmain()staticnull的第一个参数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

 

故障排除

本节包含开发人员在使用反射定位、调用或获取有关方法的信息时可能遇到的问题。

由于类型擦除而引发的NoSuchMethod异常

这,这个,那,那个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)

当使用泛型参数类型声明方法时,编译器将用其上限替换泛型类型,在本例中,TObject。因此,当代码搜索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。

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()。如果成功,则此方法对象的后续调用不会因此问题而失败。


IllegalArgumentException来自Method.Invoke()

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,因为类型是Objectping()根本没有任何争论。

$ 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()。此异常并不表示反射包或其使用出现问题。

 

建设者

构造器用于创建作为类实例的对象。通常,它在调用方法或访问字段之前执行初始化类所需的操作。构造函数从不被继承。

与方法类似,反射提供API来发现和检索类的构造函数,并获得声明信息,例如修饰符、参数、注释和抛出的异常。还可以使用指定的构造函数创建类的新实例。在使用构造函数时使用的键类为Classjava.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)
      *GenericParameterType[0]: java.util.Map

可以与方法类似的方式检索构造函数的异常类型。见MethodSpy中描述的示例获取方法类型信息一节以了解更多细节。

 

检索和解析构造修饰符

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

  • 访问修饰符:publicprotected,和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

 

故障排除

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

由于缺少零参数构造器而引发的InstantiationException

这,这个,那,那个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都会失败。


newInstance()引发意外异常

这,这个,那,那个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()不尝试执行方法解析;它只是对现有的构造函数对象进行操作。


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


试图调用不可访问的构造器时抛出IllegalAccessException

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().

你可能感兴趣的:(java)