到底什么是反射?反射究竟能用来干什么?
反射,顾名思义,它是一种逆向的操作。就好像人在照镜子的时候,正是由于光的反射,才能看到镜子中的自己。而在Java中,反射功能就好比是一面镜子,通过它,我们可以在程序运行过程中看到Class以及对象的相关信息。
在以往的经验中,当我们需要完成某些操作,往往是在编译之前完成,比如根据创建对象、读取属性、设置属性;我们把这些程序编写完之后编译器会将之编译为Class文件,然后直接在虚拟机中运行就可以了,大部分情况确实是这样,这也是为什么Java是"静态语言"。
但是我们却可以通过反射来完成在动态语言中才能做到的一些操作,比如首先第一步,通过反射获取某个.class文件的结构信息。
示例如下:
/**
* 蝙蝠侠
*/
public class Batman {
public Batman(String name,int age,String power){
this.name = name;
this.age = age;
this.power = power;
}
//蝙蝠侠的名字
private String name;
//蝙蝠侠的年龄
private int age;
//蝙蝠侠的超能力
private String power;
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;
}
public String getPower() {
return power;
}
public void setPower(String power) {
this.power = power;
}
/** 跟小猫谈恋爱 */
private void beInLove(){
System.out.println("蝙蝠侠正在跟小猫谈恋爱");
}
/** 教训小丑 */
public void work(){
System.out.println("蝙蝠侠正在教训小丑");
}
private void workWithGordon(String name){
System.out.printf("蝙蝠侠正在和%s一起教训小丑",name);
}
}
//测试类
public class ReflectTest {
public static void main(String[] args) {
Class cls = Batman.class;
//1.获取class中的所有属性,包括全局属性和局部属性
Field[] fields = cls.getDeclaredFields();
System.out.println("属性:");
for (Field f : fields) {
System.out.println(f);
}
System.out.println("方法:");
//获取class中的所有方法
Method[] mets = cls.getDeclaredMethods();
for (Method met : mets) {
System.out.println(met);
}
System.out.println("构造器:");
//获取class中的所有构造器
Constructor[] cons = cls.getDeclaredConstructors();
for (Constructor c : cons) {
System.out.println(c);
}
}
}
运行结果: 属性: private java.lang.String day2.demo2.Batman.name private int day2.demo2.Batman.age private java.lang.String day2.demo2.Batman.power 方法: public int day2.demo2.Batman.getAge() public void day2.demo2.Batman.setAge(int) public java.lang.String day2.demo2.Batman.getPower() public void day2.demo2.Batman.setPower(java.lang.String) public java.lang.String day2.demo2.Batman.getName() public void day2.demo2.Batman.setName(java.lang.String) 构造器: public day2.demo2.Batman(java.lang.String,int,java.lang.String)
java程序在运行时,虚拟机在加载类时,会为这个类创建一个Class对象,用来表示这个类的信息。可以通过类名.class、Class.forName("类名")、Object.getClass等方式获取到一个Class对象,这个对象记录了类的信息,通过它可以逆向获取类的结构。
在示例中,通过Class对象中方法的调用,获取了Batman类中的所有属性、方法、构造器,但是反射的功能远不止于此,比如通过上述三个方法获取到的Field、Method、Constructor数组对象完成进一步的操作:
public class ReflectTest {
public static void main(String[] args) {
Class cls = Batman.class;
Field[] fields = cls.getDeclaredFields();
System.out.println("属性:");
System.out.println("访问修饰符:" + Modifier.toString(fields[0].getModifiers()));
System.out.println("是否静态的:" + Modifier.isStatic(fields[0].getModifiers()));
System.out.println("是否为public:" + Modifier.isPublic(fields[0].getModifiers()));
System.out.println("是否常量:" + Modifier.isFinal(fields[0].getModifiers()));
System.out.println("方法:");
Method[] mets = cls.getDeclaredMethods();
System.out.println("是否为本地方法:" + Modifier.isNative(mets[0].getModifiers()));
System.out.println("是否为抽象方法:" + Modifier.isAbstract(mets[0].getModifiers()));
System.out.println("是否为接口:" + Modifier.isInterface(mets[0].getModifiers()));
System.out.println("是否线程同步:" + Modifier.isSynchronized(mets[0].getModifiers()));
System.out.println("构造器:");
Constructor[] cons = cls.getDeclaredConstructors();
System.out.println("是否公有:"+Modifier.isPublic(cons[0].getModifiers()));
/**
* ...
* */
}
}
以上这些都是直接对Class类的操作,其实java反射也同样支持对运行中的对象的操作,甚至可以修改对象中属性的值。
示例代码:
public class ReflectTest {
public static void main(String[] args) throws IllegalAccessException {
Batman batman = new Batman("布鲁斯韦恩",27,"有钱");
Class cls = batman.getClass();
Field[] fields = cls.getDeclaredFields();
/**获取第一个属性name的值,由于是private属性,
所以会报IllegalAccessException异常,很显然是与权限有关*/
try {
var name = fields[0].get(batman);
System.out.println(name);
}catch (IllegalAccessException e){
//这里通过一个方法设置可访问对象的可访问标志
fields[0].setAccessible(true);
var name = fields[0].get(batman);
System.out.println(name);
}
//但是这里设置的只是数组中第一个属性的访问权限,下面这句话依然会报错
try {
var name = fields[1].get(batman);
System.out.println(name);
}catch (IllegalAccessException e){
//所以通过下面的方法对整个数组对象的访问权限进行设置
AccessibleObject.setAccessible(fields,true);
var age = fields[1].get(batman);
var power = fields[2].get(batman);
System.out.println(age);
System.out.println(power);
}
//修改fields[2]的值
fields[2].set(batman,"哥谭首富");
System.out.println("超能力:"+batman.getPower());
}
}
运行结果: 布鲁斯韦恩 27 有钱 超能力:哥谭首富
通过调用对象的getClass()方法获取这个类唯一的Class对象,再通过获取到field对象的get(obj)方法获取到这个field的值(当然如果属性是私有的,还需要使用setAccessible方法设置访问标志),并且不仅可以获取,还能通过其set(obj,val)方法重新设置这个属性的值。而这一切都是在程序运行期间完成的,成功的通过反射修改了对象中的属性。
至此,已经实现了通过反射来查看类的信息、对象的属性以及设置对象的属性。那么如何通过反射来调用方法以及构造器呢?
示例代码:
public class ReflectTest {
public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException, InstantiationException {
Class cls = Batman.class;
//通过反射调用构造器创建蝙蝠侠对象
Batman batman = (Batman) cls.getDeclaredConstructor(String.class,int.class,String.class).newInstance("蝙蝠侠", 27, "有钱");
Method method = cls.getDeclaredMethod("beInLove");
//因为beInLove()方法是私有的,所以需要设置以下权限
method.setAccessible(true);
method.invoke(batman);
Method work = cls.getDeclaredMethod("work");
//work方法不是private的,不需要设置权限
work.invoke(batman);
//调用带参数的方法
Method workWithGordon = cls.getDeclaredMethod("workWithGordon", String.class);
//私有方法依然要设置权限
workWithGordon.setAccessible(true);
workWithGordon.invoke(batman,"Gordon");
}
}
至此,就完成了方法及构造方法的调用。需要注意的是,若调用了一个带返回值的方法,如果返回值类型是基本类型,invoke方法会返回其包装类型,如int返回Integer、double返回Double。
另外,java.lang.reflect包中还提供了一个很好用的类Array,ArrayList中的数组扩容就使用到了这个类。
示例代码:
public class ReflectTest {
public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException, InstantiationException {
//假如我想创建一个数组
String[] strs = {"bruce","jack","jerry"};
strs = (String[]) CopyOf(strs,10);
}
//现在我想写一个方法来为泛型数组扩容
public static Object[] CopyOf(Object[] obj,int nlength){
var newArray = new Object[nlength];
System.arraycopy(obj,0,newArray,0,Math.min(obj.length,nlength));
return newArray;
}
}
运行结果:Exception in thread "main" java.lang.ClassCastException
这段代码看起来好像没有问题,通过Object超类接收任意类型的数组。但是有一个细节问题,当创建一个数组然后将其转为Object[],再把它从Object[]转回来是没有问题的,但是如果直接创建一个Object[]转成目标类型的数组是会出错的。所以上述代码无法完成泛型数组的扩容。
现在对代码做一些改进,示例代码:
public class ReflectTest {
public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException, InstantiationException {
//假如我想创建一个数组
String[] strs = {"bruce","jack","jerry"};
strs = (String[]) CopyOf(strs,10);
System.out.println(strs.length);
}
//现在我想写一个方法来为泛型数组扩容
public static Object CopyOf(Object obj,int nlength){
Class cls = obj.getClass();
if(!cls.isArray()) return null;
//获取数组的类型
Class type = cls.getComponentType();
//获取数组的长度
int length = Array.getLength(obj);
//通过Array.newInstance创建一个泛型数组,类型通过参数指定
Object newArray = Array.newInstance(type,nlength);
System.arraycopy(obj,0,newArray,0,Math.min(length,nlength));
return newArray;
}
}
运行结果:10
这次程序成功运行,并且成功为数组扩容。最主要的原因是代码中的关键方法,Array类的静态方法newInstance,这个方法能够返回一个有给定类型,给定大小的新数组,而不是一个简单的Object[]。
总结:反射机制可以在运行时查看、操作字段和方法。但是不应该滥用反射,因为反射在编译阶段无法查找出错误,如果存在问题,往往到了运行时才会发现。JVM无法对反射的相关代码做优化,所以效率相对低。并且反射可能导致程序不安全。
转载自:https://juejin.cn/post/7228967103349080120