参考:https://www.cnblogs.com/doudouxiaoye/p/5819813.html
Java 反射机制
基本概念
在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?
答案是肯定的。
这种动态获取类的信息以及动态调用对象的方法的功能来自于Java语言的反射(Reflection)机制。
Java反射机制主要提供了以下功能:
1.在运行时判断任意一个对象所属的类。
2.在运行时构造任意一个类的对象。
3.在运行时判断任意一个类所具有的成员变量和方法。
4.在运行时调用任意一个对象的方法。
Reflection是Java被视为动态(或准动态)语言的一个关键性质。
这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息。
包括其modifiers(诸如public、static等)、 superclass(例如Object)、实现了的 interfaces (例如Serializable)、也包括其fields和methods的所有信息,并可于运行时改变fields内容或调用methods。
动态语言
动态语言的定义“程序运行时,允许改变程序结构或者变量类型,这种语言称为动态语言”。
从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。
尽管在这样的定义与分类下Java不是动态语言,它却有着一个非常突出的动态相关机制:Reflection。这个字的意思是:反射、映像、倒影,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。
换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
这种“看透”class的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。
Java Reflection API简介
在JDK中,主要由以下类来实现Java反射机制,这些类(除了第一个)都位于java.lang.reflect包中
Class类:代表一个类,位于java.lang包下。
Field类:代表类的成员变量(成员变量也称为类的属性)。
Method类:代表类的方法。
Constructor类:代表类的构造方法。
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
Class对象
要想使用反射,首先需要获得待操作的类所对应的Class对象。
Java中,无论生成某个类的多少个对象,这些对象都会对应于同一个Class对象。
这个Class对象是由JVM生成的,通过它能够获悉整个类的结构。
常用的获取Class对象的3种方式:
1.使用Class类的静态方法。例如:
Class.forName("java.lang.String");
2.使用类的.class语法。如:
String.class;
3.使用对象的getClass()方法。如:
String str = "aa";
Class<?> classType1 = str.getClass();
getClass()方法定义在Object类中,不是静态方法,需要通过对象来调用,并且它声明为final,表明不能被子类所覆写。
直接print所获得的Class对象classType会输出:
class 完整类名
如果调用该Class对象的getName()方法,则输出完整类名,不加class。
例程1:获取方法
例程DumpMethods类演示了Reflection API的基本作用,它读取命令行参数指定的类名,然后打印这个类所具有的方法信息。
import java.lang.reflect.Method;
public class DumpMethods
{
public static void main(String[] args) throws Exception //在方法后加上这句,异常就消失了
{
//获得字符串所标识的类的class对象
Class<?> classType = Class.forName("java.lang.String");//在此处传入字符串指定类名,所以参数获取可以是一个运行期的行为,可以用args[0]
//返回class对象所对应的类或接口中,所声明的所有方法的数组(包括私有方法)
Method[] methods = classType.getDeclaredMethods();
//遍历输出所有方法声明
for(Method method : methods)
{
System.out.println(method);
}
}
}
例程2:通过反射调用方法
通过反射调用方法。详情见代码及注释:
import java.lang.reflect.Method;
public class InvokeTester
{
public int add(int param1, int param2)
{
return param1 + param2;
}
public String echo(String message)
{
return "Hello: " + message;
}
public static void main(String[] args) throws Exception
{
// 以前的常规执行手段
InvokeTester tester = new InvokeTester();
System.out.println(tester.add(1, 2));
System.out.println(tester.echo("Tom"));
System.out.println("---------------------------");
// 通过反射的方式
// 第一步,获取Class对象
// 前面用的方法是:Class.forName()方法获取
// 这里用第二种方法,类名.class
Class<?> classType = InvokeTester.class;
// 生成新的对象:用newInstance()方法
Object invokeTester = classType.newInstance();
System.out.println(invokeTester instanceof InvokeTester); // 输出true
// 通过反射调用方法
// 首先需要获得与该方法对应的Method对象
Method addMethod = classType.getMethod("add", new Class[] { int.class,
int.class });
// 第一个参数是方法名,第二个参数是这个方法所需要的参数的Class对象的数组
// 调用目标方法
Object result = addMethod.invoke(invokeTester, new Object[] { 1, 2 });
System.out.println(result); // 此时result是Integer类型
//调用第二个方法
Method echoMethod = classType.getDeclaredMethod("echo", new Class[]{String.class});
Object result2 = echoMethod.invoke(invokeTester, new Object[]{"Tom"});
System.out.println(result2);
}
}
生成对象
若想通过类的不带参数的构造方法来生成对象,我们有两种方式:
1.先获得Class对象,然后通过该Class对象的newInstance()方法直接生成即可:
Class<?> classType = String.class;
Object obj = classType.newInstance();
2.先获得Class对象,然后通过该对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成
(其中Customer是一个自定义的类,有一个无参数的构造方法,也有带参数的构造方法):
Class<?> classType = Customer.class;
// 获得Constructor对象,此处获取第一个无参数的构造方法的
Constructor cons = classType.getConstructor(new Class[] {});
// 通过构造方法来生成一个对象
Object obj = cons.newInstance(new Object[] {});
若想通过类的带参数的构造方法生成对象,只能使用下面这一种方式:
(Customer为一个自定义的类,有无参数的构造方法,也有一个带参数的构造方法,传入字符串和整型)
Class<?> classType = Customer.class;
Constructor cons2 = classType.getConstructor(new Class[] {String.class, int.class});
Object obj2 = cons2.newInstance(new Object[] {"ZhangSan",20});
可以看出调用构造方法生成对象的方法和调用一般方法的类似,不同的是从Class对象获取Constructor对象时不需要指定名字,而获取Method对象时需要指定名字。
一个利用反射进行对象拷贝的例子
如下例程ReflectTester类进一步演示了Reflection API的基本使用方法。
ReflectTester类有一个copy(Object object)方法,这个方法能够创建一个和参数object同样类型的对象,然后把object对象中的所有属性拷贝到新建的对象中,并将它返回。
这个例子只能复制简单的类,假定类的每个属性都有public类型的getXXX()和setXXX()方法。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectTester
{
// 该方法实现对Customer对象的拷贝操作
public Object copy(Object object) throws Exception
{
Class<?> classType = object.getClass();
Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
//获得对象的所有成员变量
Field[] fields = classType.getDeclaredFields();
for(Field field : fields)
{
//获取成员变量的名字
String name = field.getName(); //获取成员变量的名字,此处为id,name,age
//获取get和set方法的名字
String firstLetter = name.substring(0,1).toUpperCase(); //将属性的首字母转换为大写
String getMethodName = "get" + firstLetter + name.substring(1);
String setMethodName = "set" + firstLetter + name.substring(1);
//获取方法对象
Method getMethod = classType.getMethod(getMethodName, new Class[]{});
Method setMethod = classType.getMethod(setMethodName, new Class[]{field.getType()});//注意set方法需要传入参数类型
//调用get方法获取旧的对象的值
Object value = getMethod.invoke(object, new Object[]{});
//调用set方法将这个值复制到新的对象中去
setMethod.invoke(objectCopy, new Object[]{value});
}
return objectCopy;
}
public static void main(String[] args) throws Exception
{
Customer customer = new Customer("Tom",20);
customer.setId(1L);
ReflectTester tester = new ReflectTester();
Customer customer2 = (Customer)tester.copy(customer);
System.out.println(customer2.getId() + "," + customer2.getName() + "," + customer2.getAge());
}
}
class Customer
{
private long id;
private String name;
private int age;
public Customer()
{
}
public Customer(String name, int age)
{
this.name = name;
this.age = age;
}
public long getId()
{
return id;
}
public void setId(long id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
}
反射与数组
java.lang.Array类提供了动态创建和访问数组元素的各种静态方法。
例程ArrayTester1类的main()方法创建了一个长度为10的字符串数组,接着把索引位置为5的元素设为“hello”,然后再读取索引位置为5的元素的值。
import java.lang.reflect.Array;
public class ArrayTester1
{
public static void main(String[] args) throws Exception
{
Class<?> classType = Class.forName("java.lang.String");
//生成数组,指定元素类型和数组长度
Object array = Array.newInstance(classType, 10);
Array.set(array, 5, "hello");
String str = (String)Array.get(array, 5);
System.out.println(str);
}
}
多维数组
首先,区别一下下面两者:
System.out.println(Integer.TYPE);
System.out.println(Integer.class);
输出:
int
class java.lang.Integer
一个多维数组的程序实例:
import java.lang.reflect.Array;
public class ArrayTester2
{
public static void main(String[] args)
{
int[] dims = new int[] { 5, 10, 15 };
// 注意区分下面两种
System.out.println(Integer.TYPE); // int
System.out.println(Integer.class); // Integer
// 创建一个三维数组,这个数组的三个维度分别是5,10,15
Object array = Array.newInstance(Integer.TYPE, dims);
// 可变参数,也可以这样写:
// Object array = Array.newInstance(Integer.TYPE, 5,10,15);
System.out.println(array instanceof int[][][]);
Class<?> classType0 = array.getClass().getComponentType(); // 返回数组元素类型
System.out.println(classType0); // 三维数组的元素为二维数组,输出:class [[I
// 获得第一层的索引为3的数组,返回的是一个二维数组
Object arrayObject = Array.get(array, 3);
Class<?> classType = arrayObject.getClass().getComponentType(); // 返回数组元素类型
System.out.println(classType); // 二维数组的元素为一维数组,输出:class [I
// 此处返回的是一个一维数组
arrayObject = Array.get(arrayObject, 5);
Class<?> classType2 = arrayObject.getClass().getComponentType(); // 返回数组元素类型
System.out.println(classType2); // 一维数组的元素为int
// 给一维数组下标为10的位置设置值为37
Array.setInt(arrayObject, 10, 37);
int[][][] arrayCast = (int[][][]) array;
System.out.println(arrayCast[3][5][10]);
}
}
利用反射调用私有方法、访问私有属性
利用反射,首先是Class对象的获取,之后是Method和Field对象的获取。
以Method为例,从文档中可以看到:
getMethod()方法返回的是public的Method对象,
而getDeclaredMethod()返回的Method对象可以是非public的。
Field的方法同理。
访问私有属性和方法,在使用前要通过AccessibleObject类(Constructor、 Field和Method类的基类)中的setAccessible()方法来抑制Java访问权限的检查。
实例1,调用私有方法
假设有这样一个类,其中包含私有方法。
public class PrivateClass
{
private String sayHello(String name)
{
return "Hello: " + name;
}
}
利用反射机制在外部访问该方法:
import java.lang.reflect.Method;
public class TestPrivate
{
public static void main(String[] args) throws Exception
{
PrivateClass p = new PrivateClass();
Class<?> classType = p.getClass();
// 获取Method对象
Method method = classType.getDeclaredMethod("sayHello",
new Class[] { String.class });
method.setAccessible(true); // 抑制Java的访问控制检查
// 如果不加上上面这句,将会Error: TestPrivate can not access a member of class PrivateClass with modifiers "private"
String str = (String) method.invoke(p, new Object[] { "zhangsan" });
System.out.println(str);
}
}
实例2,访问私有属性
直接访问私有属性,将例子中的私有属性改值。
一个包含私有属性的类:
public class PrivateClass2
{
private String name = "zhangsan";
public String getName()
{
return name;
}
}
利用反射修改其私有属性的值:
import java.lang.reflect.Field;
public class TestPrivate2
{
public static void main(String[] args) throws Exception
{
PrivateClass2 p = new PrivateClass2();
Class<?> classType = p.getClass();
Field field = classType.getDeclaredField("name");
field.setAccessible(true); // 抑制Java对修饰符的检查
field.set(p, "lisi");
System.out.println(p.getName());
}
}
Class类
Java中的Object类,是所有类的继承根源,其中的getClass()方法返回一个Class Object。所有的类都有这个方法。
Class类十分特殊,它和其他类一样继承自Object,其实体用以表达Java程序运行时的classes和interfaces,也用来表达enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及关键词void。
当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM便自动产生一个Class对象(Class object)。
如果您想借由修改Java标准库源码来观察Class对象的实际生成时机,例如在Class的constructor内添加println(),那是不可以的。因为Class没有public constructor。
Class是Reflection起源。针对任何您想探勘的类,唯有先为它产生一个Class object,接下来才能经由后者唤起为数十多个的Reflection APIs。
“Class”object的取得途径
1.运用getClass()方法,返回Class对象。
运用Class.getSuperclass()可以得到父类的Class对象,如果是Object类则返回null。
2.运用静态方法Class.forName()
3.运用.class语法。类名.class。
其中,还可以通过int[].class的形式获得整形数组的Class对象。
包装类的.TYPE语法实际返回的是所对应的原生数据类型的Class对象。