参考资料:
前文:
后续:
写在开头:本文为学习后的总结,可能有不到位的地方,错误的地方,欢迎各位指正。
目录
一、反射的简介
1、反射的概念
java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
简单的说,反射就是把java类中的各种成分映射成一个个的Java对象。
2、反射提供了哪些功能
(1)在运行时能够判断任意一个对象所属的类
(2)在运行时构造任意一个类的对象
(3)在运行时判断任意一个类所具有的成员变量和方法
(4)在运行时调用任一对象的方法
(5)在运行时创建新类对象
3、new和反射创建类的区别
new: 静态编译,在编译期就将模块编译进来,执行该字节码文件,所有的模块都被加载;
反射:动态编译,编译期没有加载,等到模块被调用时才加载;
二、Class对象
1、class对象简介
java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RTTI(运行时类型识别,Run-Time Type Identification),多态是基于RTTI实现的。(注:RRIT(Run-Time Type Identification)运行时类型识别。在《Thinking in Java》一书第十四章中有提到,其作用是在运行时识别一个对象的类型和类的信息。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。)
Class对象的由来是将class文件读入内存,并为之创建一个Class对象。Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。类的加载过程在之前的文章中已经介绍过了,对于加载过程不了解的朋友可以看这里《Java8之类的加载》
借用下面这张图可以更加深入的了解(图片来源《Java基础之—反射》):
2、Class对象的获取
(1)实例对象.getClass()
getClass()是从顶级类Object继承而来的,它将返回表示该对象的实际类型的Class对象引用。
Test t1 = new Test();
Class clazz=test.getClass();
System.out.println("clazz:"+clazz.getName());
(2)类名.class (类字面常量)
使用类名.class可以直接获取该类对应的class对象。
//字面常量的方式获取Class对象
Class clazz = Test.class;
(3)Class.forName(“类的全限定名”)
forName方法是Class类的一个static成员方法,所有的Class对象都源于这个Class类,因此Class类中定义的方法将适应所有Class对象。这里通过forName方法,我们可以获取到参数中的类对应的Class对象引用。 注意调用forName方法时需要捕获一个名称为ClassNotFoundException的异常,因为forName方法在编译器是无法检测到其传递的字符串对应的类是否存在的(是否有.class文件),只能在程序运行时进行检查,如果不存在就会抛出ClassNotFoundException异常。
使用forName方式会触发类的初始化,与之相比的是使用类字面常量获取。
try{
//通过Class.forName获取Test类的Class对象
Class clazz=Class.forName("com.hiway.Test");
System.out.println("clazz:"+clazz.getName());
}catch (ClassNotFoundException e){
e.printStackTrace();
}
三种方式常用第三种,第一种方式需要我们提前获取到对象,既然已经能手动获取到对象了再使用反射就是多此一举。第二种需要导入类的包,依赖太强,不导包就抛编译错误。一般都第三种,可以通过读取配置文件的方式动态调整。
三、反射的使用
以下案例参考自:《Java 基础 - 反射机制详解》
1、class类常用方法
方法 | 说明 |
forName() | (1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。 (2)为了产生Class引用,forName()立即就进行了初始化。 |
getClass() | 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。 |
getName() | 取全限定的类名(包括包名),即类的完整名字。 |
getSimpleName() | 获取类名(不包括包名) |
getCanonicalName() | 获取全限定的类名(包括包名) |
isInterface() | 判断Class对象是否是表示一个接口 |
getInterfaces() | 返回Class对象数组,表示Class对象所引用的类所实现的所有接口。 |
getSupercalss() | 返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。 |
newInstance() | 返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。 |
getFields() | 获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。 |
getDeclaredFields | 获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。 |
package com.cry;
import java.lang.reflect.Field;
interface I1 {
}
interface I2 {
}
class Cell{
public int mCellPublic;
}
class Animal extends Cell{
private int mAnimalPrivate;
protected int mAnimalProtected;
int mAnimalDefault;
public int mAnimalPublic;
private static int sAnimalPrivate;
protected static int sAnimalProtected;
static int sAnimalDefault;
public static int sAnimalPublic;
}
class Dog extends Animal implements I1, I2 {
private int mDogPrivate;
public int mDogPublic;
protected int mDogProtected;
private int mDogDefault;
private static int sDogPrivate;
protected static int sDogProtected;
static int sDogDefault;
public static int sDogPublic;
}
public class Test {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class<Dog> dog = Dog.class;
//类名打印
System.out.println(dog.getName()); //com.cry.Dog
System.out.println(dog.getSimpleName()); //Dog
System.out.println(dog.getCanonicalName());//com.cry.Dog
//接口
System.out.println(dog.isInterface()); //false
for (Class iI : dog.getInterfaces()) {
System.out.println(iI);
}
/*
interface com.cry.I1
interface com.cry.I2
*/
//父类
System.out.println(dog.getSuperclass());//class com.cry.Animal
//创建对象
Dog d = dog.newInstance();
//字段
for (Field f : dog.getFields()) {
System.out.println(f.getName());
}
/*
mDogPublic
sDogPublic
mAnimalPublic
sAnimalPublic
mCellPublic //父类的父类的公共字段也打印出来了
*/
System.out.println("---------");
for (Field f : dog.getDeclaredFields()) {
System.out.println(f.getName());
}
/** 只有自己类声明的字段
mDogPrivate
mDogPublic
mDogProtected
mDogDefault
sDogPrivate
sDogProtected
sDogDefault
sDogPublic
*/
}
}
getName、getCanonicalName与getSimpleName的区别:
- getSimpleName:只获取类名
- getName:类的全限定名,jvm中Class的表示,可以用于动态加载Class对象,例如Class.forName。
- getCanonicalName:返回更容易理解的表示,主要用于输出(toString)或log打印,大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了。
package com.cry;
public class Test {
private class inner{
}
public static void main(String[] args) throws ClassNotFoundException {
//普通类
System.out.println(Test.class.getSimpleName()); //Test
System.out.println(Test.class.getName()); //com.cry.Test
System.out.println(Test.class.getCanonicalName()); //com.cry.Test
//内部类
System.out.println(inner.class.getSimpleName()); //inner
System.out.println(inner.class.getName()); //com.cry.Test$inner
System.out.println(inner.class.getCanonicalName()); //com.cry.Test.inner
//数组
System.out.println(args.getClass().getSimpleName()); //String[]
System.out.println(args.getClass().getName()); //[Ljava.lang.String;
System.out.println(args.getClass().getCanonicalName()); //java.lang.String[]
//我们不能用getCanonicalName去加载类对象,必须用getName
//Class.forName(inner.class.getCanonicalName()); 报错
Class.forName(inner.class.getName());
}
}
2、Constructor类及其用法
Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。
方法返回值 | 方法名称 | 方法说明 |
static Class<?> | forName(String className) | 返回与带有给定字符串名的类或接口相关联的 Class 对象。 |
Constructor | getConstructor(Class<?>... parameterTypes) | 返回指定参数类型、具有public访问权限的构造函数对象 |
Constructor<?>[] | getConstructors() | 返回所有具有public访问权限的构造函数的Constructor对象数组 |
Constructor | getDeclaredConstructor(Class<?>... parameterTypes) | 返回指定参数类型、所有声明的(包括private)构造函数对象 |
Constructor<?>[] | getDeclaredConstructor() | 返回所有声明的(包括private)构造函数对象 |
T | newInstance() | 调用无参构造器创建此 Class 对象所表示的类的一个新实例。 |
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)
方法返回值 | 方法名称 | 方法说明 |
Class | getDeclaringClass() | 返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数) |
Type[] | getGenericParameterTypes() | 按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。 |
String | getName() | 以字符串形式返回此构造方法的名称。 |
Class<?>[] | getParameterTypes() | 按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型 |
T | newInstance(Object... initargs) | 使用此 Constructor对象表示的构造函数来创建新实例 |
String | toGenericString() | 返回描述此 Constructor 的字符串,其中包括类型参数。 |
Constructor cs3 = clazz.getDeclaredConstructor(int.class,String.class);
System.out.println("-----getDeclaringClass-----");
Class uclazz=cs3.getDeclaringClass();
//Constructor对象表示的构造方法的类
System.out.println("构造方法的类:"+uclazz.getName());
System.out.println("-----getGenericParameterTypes-----");
//对象表示此 Constructor 对象所表示的方法的形参类型
Type[] tps=cs3.getGenericParameterTypes();
for (Type tp:tps) {
System.out.println("参数名称tp:"+tp);
}
System.out.println("-----getParameterTypes-----");
//获取构造函数参数类型
Class<?> clazzs[] = cs3.getParameterTypes();
for (Class claz:clazzs) {
System.out.println("参数名称:"+claz.getName());
}
System.out.println("-----getName-----");
//以字符串形式返回此构造方法的名称
System.out.println("getName:"+cs3.getName());
System.out.println("-----getoGenericString-----");
//返回描述此 Constructor 的字符串,其中包括类型参数。
System.out.println("getoGenericString():"+cs3.toGenericString());
结果如下:
-----getDeclaringClass-----
构造方法的类:com.example.javabase.User
-----getGenericParameterTypes-----
参数名称tp:int
参数名称tp:class java.lang.String
-----getParameterTypes-----
参数名称:int
参数名称:java.lang.String
-----getName-----
getName:com.example.javabase.User
-----getoGenericString-----
getoGenericString():private com.example.javabase.User(int,java.lang.String)
3、Field类及其用法
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
方法返回值 | 方法名称 | 方法说明 |
Field | getDeclaredField(String name) | 获取指定name名称的(包含private修饰的)字段,不包括继承的字段 |
Field[] | getDeclaredField() | 获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段 |
Field | getField(String name) | 获取指定name名称、具有public修饰的字段,包含继承字段 |
Field[] | getField() | 获取修饰符为public的字段,包含继承字段 |
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类还有其他常用的方法如下:
方法返回值 | 方法名称 | 方法说明 |
void | set(Object obj, Object value) | 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 |
Object | get(Object obj) | 返回指定对象上此 Field 表示的字段的值 |
Class<?> | getType() | 返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。 |
boolean | isEnumConstant() | 如果此字段表示枚举类型的元素则返回 true;否则返回 false |
String | toGenericString() | 返回一个描述此 Field(包括其一般类型)的字符串 |
String | getName() | 返回此 Field 对象表示的字段的名称 |
Class<?> | getDeclaringClass() | 返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段 |
void | setAccessible(boolean flag) | 将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性 |
上述方法可能是较为常用的,事实上在设置值的方法上,Field类还提供了专门针对基本数据类型的方法,如setInt()/getInt()
、setBoolean()/getBoolean
、setChar()/getChar()
等等方法,这里就不全部列出了,需要时查API文档即可。需要特别注意的是被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。
4、Method类及其用法
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Method | getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 |
Method[] | getDeclaredMethod() | 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 |
Method | getMethod(String name, Class<?>... parameterTypes) | 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 |
Method[] | getMethods() | 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。 |
Object | invoke(Object obj, Object... args) | 对带有指定参数的指定对象调用由此 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)
第一个参数代表调用的对象,第二个参数传递的调用方法的参数。这样就完成了类方法的动态调用。
四、反射机制执行的流程
源码部分较长,该部分由后续文章补完《Java8之反射源码》,下面仅针对主题流程进行简介。
1. 反射类及反射方法的获取,都是通过从列表中搜寻查找匹配的方法,所以查找性能会随类的大小方法多少而变化;
2. 每个类都会有一个与之对应的Class实例,从而每个类都可以获取method反射方法,并作用到其他实例身上;
3. 反射也是考虑了线程安全的,放心使用;
4. 反射使用软引用relectionData缓存class信息,避免每次重新从jvm获取带来的开销;
5. 反射调用多次生成新代理Accessor, 而通过字节码生存的则考虑了卸载功能,所以会使用独立的类加载器;
6. 当找到需要的方法,都会copy一份出来,而不是使用原来的实例,从而保证数据隔离;
7. 调度反射方法,最终是由jvm执行invoke0()执行;