RTTI(Run-Time Type Identification)运行时类型识别。在《Thinking in Java》一书第十四章中有提到,其作用是在运行时识别一个对象的类型和类的信息。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。
反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
这里我们首先需要理解 Class类,以及类的加载机制; 然后基于此我们如何通过反射获取Class类以及类中的成员变量、方法、构造方法等。
Class类
Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中。Class类的实例表示java应用运行时的类(class ans enum)或接口(interface and annotation)(每个java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName("类名")等方法获取class对象)。数组同样也被映射为class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本类型boolean,byte,char,short,int,long,float,double和关键字void同样表现为 class 对象。
复制 public final class Class < T > implements java.io. Serializable ,
GenericDeclaration ,
Type ,
AnnotatedElement {
private static final int ANNOTATION = 0x00002000 ;
private static final int ENUM = 0x00004000 ;
private static final int SYNTHETIC = 0x00001000 ;
private static native void registerNatives ();
static {
registerNatives() ;
}
/*
* Private constructor. Only the Java Virtual Machine creates Class objects. //私有构造器,只有JVM才能调用创建Class对象
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class ( ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
到这我们也就可以得出以下几点信息:
Class类也是类的一种,与class关键字是不一样的。
手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件)
每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。
类加载
类加载机制和类字节码技术可以参考如下两篇文章:
其中,这里我们需要回顾的是:
反射的使用
Class类及其用法
在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。Class类对象的获取
在类加载的时候,jvm会创建一个class对象
class对象是可以说是反射中最常用的,获取class对象的方式的主要有三种
根据全限定类名:Class.forName(全限定类名)
复制 @ Test
public void classTest() throws Exception {
// 获取Class对象的三种方式
logger . info ( "根据类名: \t" + User . class );
logger . info ( "根据对象: \t" + new User() . getClass ());
logger . info ( "根据全限定类名:\t" + Class . forName ( "com.test.User" ));
// 常用的方法
logger . info ( "获取全限定类名:\t" + userClass . getName ());
logger . info ( "获取类名:\t" + userClass . getSimpleName ());
logger . info ( "实例化:\t" + userClass . newInstance ());
}
// ...
package com . test ;
public class User {
private String name = "init" ;
private int age;
public User () {}
public User ( String name , int age) {
super();
this . name = name;
this . age = age;
}
private String getName () {
return name;
}
private void setName ( String name) {
this . name = name;
}
public int getAge () {
return age;
}
public void setAge ( int age) {
this . age = age;
}
@ Override
public String toString () {
return "User [name=" + name + ", age=" + age + "]" ;
}
}
输出结果:
复制 根据类名: class com.test.User
根据对象: class com.test.User
根据全限定类名: class com.test.User
获取全限定类名: com.test.User
获取类名: User
实例化: User [name=init, age=0]
Constructor类及其用法
Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。
获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:
下面看一个简单例子来了解Constructor对象的使用:
复制 public class ConstructionTest implements Serializable {
public static void main ( String [] args) throws Exception {
Class < ? > clazz = null ;
//获取Class对象的引用
clazz = Class . forName ( "com.example.javabase.User" );
//第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常
User user = (User) clazz . newInstance ();
user . setAge ( 20 );
user . setName ( "Jack" );
System . out . println (user);
System . out . println ( "--------------------------------------------" );
//获取带String参数的public构造函数
Constructor cs1 = clazz . getConstructor ( String . class );
//创建User
User user1 = (User) cs1 . newInstance ( "hiway" );
user1 . setAge ( 22 );
System . out . println ( "user1:" + user1 . toString ());
System . out . println ( "--------------------------------------------" );
//取得指定带int和String参数构造函数,该方法是私有构造private
Constructor cs2 = clazz . getDeclaredConstructor ( int . class , String . class );
//由于是private必须设置可访问
cs2 . setAccessible ( true );
//创建user对象
User user2 = (User) cs2 . newInstance ( 25 , "hiway2" );
System . out . println ( "user2:" + user2 . toString ());
System . out . println ( "--------------------------------------------" );
//获取所有构造包含private
Constructor < ? > cons[] = clazz . getDeclaredConstructors ();
// 查看每个构造方法需要的参数
for ( int i = 0 ; i < cons . length ; i ++ ) {
//获取构造函数参数类型
Class < ? > clazzs[] = cons[i] . getParameterTypes ();
System . out . println ( "构造函数[" + i + "]:" + cons[i] . toString () );
System . out . print ( "参数类型[" + i + "]:(" );
for ( int j = 0 ; j < clazzs . length ; j ++ ) {
if (j == clazzs . length - 1 )
System . out . print (clazzs[j] . getName ());
else
System . out . print (clazzs[j] . getName () + "," );
}
System . out . println ( ")" );
}
}
}
class User {
private int age;
private String name;
public User () {
super();
}
public User ( String name) {
super();
this . name = name;
}
/**
* 私有构造
* @param age
* @param name
*/
private User ( int age , String name) {
super();
this . age = age;
this . name = name;
}
public int getAge () {
return age;
}
public void setAge ( int age) {
this . age = age;
}
public String getName () {
return name;
}
public void setName ( String name) {
this . name = name;
}
@ Override
public String toString () {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}' ;
}
}
输出结果
复制 /* output
User{age=20, name='Jack'}
--------------------------------------------
user1:User{age=22, name='hiway'}
--------------------------------------------
user2:User{age=25, name='hiway2'}
--------------------------------------------
构造函数[0]:private com.example.javabase.User(int,java.lang.String)
参数类型[0]:(int,java.lang.String)
构造函数[1]:public com.example.javabase.User(java.lang.String)
参数类型[1]:(java.lang.String)
构造函数[2]:public com.example.javabase.User()
参数类型[2]:()
关于Constructor类本身一些常用方法 如下(仅部分,其他可查API)
Field类及其用法
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下:
下面的代码演示了上述方法的使用过程
复制 public class ReflectField {
public static void main ( String [] args) throws ClassNotFoundException , NoSuchFieldException {
Class < ? > clazz = Class . forName ( "reflect.Student" );
//获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段,
// 否则抛NoSuchFieldException
Field field = clazz . getField ( "age" );
System . out . println ( "field:" + field);
//获取所有修饰符为public的字段,包含父类字段,注意修饰符为public才会获取
Field fields[] = clazz . getFields ();
for ( Field f : fields) {
System . out . println ( "f:" + f . getDeclaringClass ());
}
System . out . println ( "================getDeclaredFields====================" );
//获取当前类所字段(包含private字段),注意不包含父类的字段
Field fields2[] = clazz . getDeclaredFields ();
for ( Field f : fields2) {
System . out . println ( "f2:" + f . getDeclaringClass ());
}
//获取指定字段名称的Field类,可以是任意修饰符的自动,注意不包含父类的字段
Field field2 = clazz . getDeclaredField ( "desc" );
System . out . println ( "field2:" + field2);
}
/**
输出结果:
field:public int reflect.Person.age
f:public java.lang.String reflect.Student.desc
f:public int reflect.Person.age
f:public java.lang.String reflect.Person.name
================getDeclaredFields====================
f2:public java.lang.String reflect.Student.desc
f2:private int reflect.Student.score
field2:public java.lang.String reflect.Student.desc
*/
}
class Person {
public int age;
public String name;
//省略set和get方法
}
class Student extends Person {
public String desc;
private int score;
//省略set和get方法
}
上述方法需要注意的是,如果我们不期望获取其父类的字段,则需使用Class类的getDeclaredField/getDeclaredFields方法来获取字段即可,倘若需要连带获取到父类的字段,那么请使用Class类的getField/getFields,但是也只能获取到public修饰的的字段,无法获取父类的私有字段。下面将通过Field类本身的方法对指定类属性赋值,代码演示如下:
复制 //获取Class对象引用
Class < ? > clazz = Class . forName ( "reflect.Student" );
Student st = (Student) clazz . newInstance ();
//获取父类public字段并赋值
Field ageField = clazz . getField ( "age" );
ageField . set (st , 18 );
Field nameField = clazz . getField ( "name" );
nameField . set (st , "Lily" );
//只获取当前类的字段,不获取父类的字段
Field descField = clazz . getDeclaredField ( "desc" );
descField . set (st , "I am student" );
Field scoreField = clazz . getDeclaredField ( "score" );
//设置可访问,score是private的
scoreField . setAccessible ( true );
scoreField . set (st , 88 );
System . out . println ( st . toString ());
//输出结果:Student{age=18, name='Lily ,desc='I am student', score=88}
//获取字段值
System . out . println ( scoreField . get (st));
// 88
其中的set(Object obj, Object value)方法是Field类本身的方法,用于设置字段的值,而get(Object obj)则是获取字段的值,当然关于Field类还有其他常用的方法如下:
上述方法可能是较为常用的,事实上在设置值的方法上,Field类还提供了专门针对基本数据类型的方法,如setInt()/getInt()
、setBoolean()/getBoolean
、setChar()/getChar()
等等方法,这里就不全部列出了,需要时查API文档即可。需要特别注意的是被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。
Method类及其用法
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。
下面是Class类获取Method对象相关的方法:
同样通过案例演示上述方法:
复制 import java . lang . reflect . Method ;
public class ReflectMethod {
public static void main ( String [] args) throws ClassNotFoundException , NoSuchMethodException {
Class clazz = Class . forName ( "reflect.Circle" );
//根据参数获取public的Method,包含继承自父类的方法
Method method = clazz . getMethod ( "draw" , int . class , String . class );
System . out . println ( "method:" + method);
//获取所有public的方法:
Method [] methods = clazz . getMethods ();
for ( Method m : methods){
System . out . println ( "m::" + m);
}
System . out . println ( "=========================================" );
//获取当前类的方法包含private,该方法无法获取继承自父类的method
Method method1 = clazz . getDeclaredMethod ( "drawCircle" );
System . out . println ( "method1::" + method1);
//获取当前类的所有方法包含private,该方法无法获取继承自父类的method
Method [] methods1 = clazz . getDeclaredMethods ();
for ( Method m : methods1){
System . out . println ( "m1::" + m);
}
}
}
class Shape {
public void draw (){
System . out . println ( "draw" );
}
public void draw ( int count , String name){
System . out . println ( "draw " + name + ",count=" + count);
}
}
class Circle extends Shape {
private void drawCircle (){
System . out . println ( "drawCircle" );
}
public int getAllCount (){
return 100 ;
}
}
输出结果:
复制 method:public void reflect.Shape.draw(int,java.lang.String)
m::public int reflect.Circle.getAllCount()
m::public void reflect.Shape.draw()
m::public void reflect.Shape.draw(int,java.lang.String)
m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
m::public boolean java.lang.Object.equals(java.lang.Object)
m::public java.lang.String java.lang.Object.toString()
m::public native int java.lang.Object.hashCode()
m::public final native java.lang.Class java.lang.Object.getClass()
m::public final native void java.lang.Object.notify()
m::public final native void java.lang.Object.notifyAll()
=========================================
method1::private void reflect.Circle.drawCircle()
m1::public int reflect.Circle.getAllCount()
m1::private void reflect.Circle.drawCircle()
在通过getMethods方法获取Method对象时,会把父类的方法也获取到,如上的输出结果,把Object类的方法都打印出来了。而getDeclaredMethod/getDeclaredMethods
方法都只能获取当前类的方法。我们在使用时根据情况选择即可。下面将演示通过Method对象调用指定类的方法:
复制 Class clazz = Class . forName ( "reflect.Circle" );
//创建对象
Circle circle = (Circle) clazz . newInstance ();
//获取指定参数的方法对象Method
Method method = clazz . getMethod ( "draw" , int . class , String . class );
//通过Method对象的invoke(Object obj,Object... args)方法调用
method . invoke (circle , 15 , "圈圈" );
//对私有无参方法的操作
Method method1 = clazz . getDeclaredMethod ( "drawCircle" );
//修改私有方法的访问标识
method1 . setAccessible ( true );
method1 . invoke (circle);
//对有返回值得方法操作
Method method2 = clazz . getDeclaredMethod ( "getAllCount" );
Integer count = (Integer) method2 . invoke (circle);
System . out . println ( "count:" + count);
输出结果
复制 draw 圈圈,count=15
drawCircle
count:100
在上述代码中调用方法,使用了Method类的invoke(Object obj,Object... args)
第一个参数代表调用的对象,第二个参数传递的调用方法的参数。这样就完成了类方法的动态调用。
getReturnType方法/getGenericReturnType方法
都是获取Method对象表示的方法的返回类型,只不过前者返回的Class类型后者返回的Type(前面已分析过),Type就是一个接口而已,在Java8中新增一个默认的方法实现,返回的就参数类型信息
复制 public interface Type {
//1.8新增
default String getTypeName () {
return toString() ;
}
}
而getParameterTypes/getGenericParameterTypes
也是同样的道理,都是获取Method对象所表示的方法的参数类型,其他方法与前面的Field和Constructor是类似的。
更多API
更多API详见java文档 。