反射-类

这篇Java教程基于JDK1.8。教程中的示例和实践不会使用未来发行版中的优化建议。
教程:反射-类

该教程展示了多种获取类对象的方法,通过类对象我们可以检查类的属性,包括它的声明和内容。

Java中的类型有两种,要么是引用类型,要么是原始类型。类,数组,枚举和接口都是引用类型。示例中的引用类型包括:字符串,所有原始类型的包装类(比如Double),接口(比如Serializable),枚举(比如SortOrder);再加上所有的原始类型:boolean、byte、char、short、int、long、float和double。

对每一个对象类型,Java虚拟机都会实例化一个Class类的实例,Class提供了方法能检查运行期期间该对象的属性(包括成员和类型信息)。Class也提供了创建类和对象的能力。更重要的是,Class是反射API的入口。该教程包括了通过反射来操作类的常见用法。

  • 获取类对象

Object.getClass()

如果一个对象的实例存在,那么最简单获取它的Class就是通过调用Object.getClass()。当然,这只对那些继承自Object的引用类型奏效。下面有一些例子:

Class c = “foo”.getClass();

将返回String Class。

Class c = System.out.getClass();

静态方法 System.out 返回Java虚拟机控制台的输出流;调用其getClass()方法将返回java.io.PrintStream类。

enum E { A, B }
Class c = A.getClass();

A是枚举类型E的实例,getClass()返回枚举类型E的Class:reflection.ClassReflection$E。

byte[] bytes = new byte[1024];
Class c = bytes.getClass();

返回byte类型数组的Class:[B

char[] chars = new char[1024];
Class c = chars.getClass();

返回char类型数组的Class:[C

import java.util.HashSet;
import java.util.Set;

Set s = new HashSet();
Class c = s.getClass();

返回java.util.HashSet

.class语法

对于那些没有实例的对象要获取Class对象都可以通过.class语法;对原始类型来说获取其Class对象最简单的方式就是通过.class语法。

boolean b;
Class c = b.getClass(); // compile-time error
Class c = boolean.class; // correct

注意在boolean类型变量上调用.getClass() 会导致编译错误,是因为原始类型不存在引用。boolean变量返回的Class就是boolean。

Class c = java.io.PrintStream.class;

将返回java.io.PrintStream。

Class c = int[][][].class;

将返回[[[I

Class.forName()

如果知道类的全限定名,可以通过 Class.forName() 静态方法来获得它的Class。当然这不适用于原始类型。获取一系列类的名字由语法Class.getName()描述。该语法适用于引用和基本类型。

Class c = Class.forName(“com.duke.MyLocaleServiceProvider”);

该语句将根据类的全限定名创建一个Class。

Class cDoubleArray = Class.forName("[D");

Class cStringArray = Class.forName("[[Ljava.lang.String;");

变量cDoubleArray存储原始类型double数组对应的class。变量cStringArray存储二维字符串数组对应的class。

原始包装类型的TYPE字段

对原始类型来说.class语法是获取它的Class的最方便有效的方法,但是还有另外一种办法。每一个原始类型和void都有一个与之对应的包装类型,这些包装类型都有一个TYPE字段,它的值与被包装原始类型的Class是等价的。

Class c = Double.TYPE;

Double.TYPE与double.class是等价的。

Class c = Void.TYPE;

Void.TYPE与void.class是等价的。

返回类的方法

  • Class.getSuperclass() 返回类的超类

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

返回javax.swing.JButton的超类javax.swing.AbstractButton

  • Class.getClasses() 返回该类成员中所有的(包括继承的)公开类、接口和枚举。

示例:Class<?>[] c = Character.class.getClasses();

Character包括2个公开类和1个公开枚举,所以返回的数组length=3,分别为:java.lang.Character U n i c o d e B l o c k 、 j a v a . l a n g . C h a r a c t e r UnicodeBlock、java.lang.Character UnicodeBlockjava.lang.CharacterSubset和java.lang.Character$UnicodeScript

  • Class.getDeclaredClasses() 返回该类中声明的类、接口和枚举。

示例:Class<?>[] c = Character.class.getDeclaredClasses();

Character声明了2个公开类、1个私有类和1个公开枚举。所以返回的Class数组length=4,分别为:java.lang.Character U n i c o d e B l o c k 、 j a v a . l a n g . C h a r a c t e r UnicodeBlock、java.lang.Character UnicodeBlockjava.lang.CharacterSubset、java.lang.Character U n i c o d e S c r i p t 和 j a v a . l a n g . C h a r a c t e r UnicodeScript和java.lang.Character UnicodeScriptjava.lang.CharacterCharacterCache。

  • Class.getDeclaringClass()
    java.lang.reflect.Field.getDeclaringClass()
    java.lang.reflect.Method.getDeclaringClass()
    java.lang.reflect.Constructor.getDeclaringClass()
    返回该成员被声明的类。匿名类没有声明类但有包装类。

示例:
import java.lang.reflect.Field;
Field f = System.class.getField(“out”);
Class c = f.getDeclaringClass();

字段out被System类声明,所以c为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类。

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

静态变量o的class的包装类是MyClass。

  • 检查类的修饰符和类型

一个类可以有多种类型的修饰符来控制它的运行行为。

  • 访问修饰符:public, protected和private
  • 重写修饰符:abstract
  • 单实例限制修饰符:static
  • 禁止修改值修饰符:final
  • 强制严格浮点行为的修饰符:strictfp
  • 注解

不是所有的修饰符可以修饰所有的类型。比如接口不能被final修饰,枚举不能被abstract修饰。java.lang.reflect.Modifier包含所有修饰符的声明。它也提供了很多方法可以用来解析通过Class.getModifiers()返回的修饰符集合。

ClassDeclarationSpy这个类演示了如何获取一个类声明的各种组件,包括修饰符、泛型参数、实现的接口以及继承路径。如果一个类实现java.lang.reflect.AnnotatedElement这个接口还可以查询到它的运行期注解。

$ java reflection.ClassDeclarationSpy java.util.concurrent.ConcurrentNavigableMap
Class:
  java.util.concurrent.ConcurrentNavigableMap

Modifiers:
  public abstract interface

Type Parameters:
  K V 

Implemented Interfaces:
  java.util.concurrent.ConcurrentMap<K, V>
  java.util.NavigableMap<K, V>

Inheritance Path:
  -- No Super Classes --

Annotations:
  -- No Annotations --

$ java reflection.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 reflection.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 reflection.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()
  • 发现类成员

类为访问字段、方法和构造函数提供了两类方法:枚举这些成员的方法和搜索特定成员的方法。另外,直接访问类上声明成员的方法与搜索继承成员的超接口和超类的方法也有区别。下表提供了所有成员定位的方法及其特征的摘要。

							定位字段的类方法
API列出成员?显示继承成员?显示私有成员?
getDeclaredField()nonoyes
getField()noyesno
getDeclaredFields()yesnoyes
getFields()yesyesno
							定位方法的类方法
API列出成员?显示继承成员?显示私有成员?
getDeclaredMethod()nonoyes
getMethod()noyesno
getDeclaredMethods()yesnoyes
getMethodsyesyesno
							定位构造器的类方法
API列出成员?继承成员?私有成员
getDeclaredConstructor()noN/Ayes
getConstructor()noN/Ano
getDeclaredConstructors()yesN/Ayes
getConstructors()yesN/Ano

给一个类名和感兴趣的成员类型,ClassSpy使用get*s()方法确定所有公共元素的列表,也包括继承的任何公共元素。

以下是一些实际的运行结果:

$ java reflection/ClassSpy java.lang.ClassCastException CONSTRUCTOR
Class:
  java.lang.ClassCastException

Package:
  java.lang

Constructor:
  public java.lang.ClassCastException()
  public java.lang.ClassCastException(java.lang.String)
$ java reflection/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 reflection/ClassSpy reflection.ClassMember FIELD METHOD
Class:
  reflection.ClassMember

Package:
  reflection

Fields:
  public static final reflection.ClassMember reflection.ClassMember.CONSTRUCTOR
  public static final reflection.ClassMember reflection.ClassMember.FIELD
  public static final reflection.ClassMember reflection.ClassMember.METHOD
  public static final reflection.ClassMember reflection.ClassMember.CLASS
  public static final reflection.ClassMember reflection.ClassMember.ALL

Methods:
  public static reflection.ClassMember[] reflection.ClassMember.values()
  public static reflection.ClassMember reflection.ClassMember.valueOf(java.lang.String)
  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 final int java.lang.Enum.hashCode()
  public int java.lang.Enum.compareTo(java.lang.Object)
  public final int java.lang.Enum.compareTo(E)
  public static <T> T java.lang.Enum.valueOf(java.lang.Class<T>,java.lang.String)
  public final java.lang.Class<E> java.lang.Enum.getDeclaringClass()
  public final int java.lang.Enum.ordinal()
  public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
  public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
  public final void java.lang.Object.wait() throws java.lang.InterruptedException
  public final native java.lang.Class<?> java.lang.Object.getClass()
  public final native void java.lang.Object.notify()
  public final native void java.lang.Object.notifyAll()
  • 故障排查

下面的示例展示了在反射类时可能遇到的典型错误。

编译器警告:“注意:…使用未经检查或不安全的操作”
ClassWarning 调用 getMethod() 引发典型的未检查转换警告。

$ javac -Xlint:unchecked reflection/ClassWarning.java 
注: reflection/ClassWarning.java使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。

$ javac -Xlint:unchecked reflection/ClassWarning.java
reflection/ClassWarning.java:10: 警告: [unchecked] 对作为原始类型Class的成员的getMethod(String,Class<?>...)的调用未经过检查
            Method m = c.getMethod("m");
                                  ^
1 个警告

这个问题有两种解决方案
方案1:将声明变量c的类型改为合适的泛型类型,比如,变量声明如下所示:
Class<?> c = warn.getClass();
方案2:使用注解 @SuppressWarnings 排除警告,如下所示:
Class c = ClassWarning.class;
@SuppressWarnings(“unchecked”)
Method m = c.getMethod(“m”);
// warning gone

当构造函数不可访问时,实例化异常
如果试图创建类的新实例,但是无参构造函数不可见,则newInstance()将抛出一个InstantiationException异常。ClassTrouble示例演示了生成的异常堆栈。

$ java reflection/ClassTrouble
java.lang.ClassNotFoundException: Cls
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:264)
        at reflection.ClassTrouble.main(ClassTrouble.java:10)

newInstance()的行为与new关键字的行为是一致的,任何new关键字实例化对象失败的原因也会导致Class.newInstance()实例化对象失败。典型的解决方案是利用java.lang.reflect.AccessibleObject 来抑制访问控制行为,这种方法只有在你的Class继承了java.lang.reflect.AccessibleObject这个类才能使用。未继承java.lang.reflect.AccessibleObject这个类的要解决问题只能修改你的代码来使用Constructor.newInstance()。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值