万变不离其宗之反射原理篇
Java工程师工作中会需要用到反射,本文从原理和使用场景出发,讲解下什么是反射。
在《Java编程思想》中,反射只有极小的篇幅介绍,在334页有“反射:运行时的类信息”这样一个小章节。书中这样描述:
在使用IDE构建项目时,可以通过代表不同组件的图标拖拽到表单中创建程序,然后在编程时通过设置构建的属性值来配置它们。这种设计时要求构件都是可实例化的,而且要暴露其部分信息,以允许程序员读取好修改构件的属性。
反射提供了一种机制-用来检查可用的方法,并返回方法名。
人们想要在运行时获取类的新的另一个动机,是希望提供跨网络的远程平台上创建对象的能力,这被称为RMI。
Class类和java.lang.reflect类库对反射的概念进行了支持,该类库包含了Field,Method,Constructor类,这样可以用各种方便的API获取类的变量,调用方法。这样匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。
重要的是,要认识到反射机制并没有什么神奇之处,当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类,用它做其他事情之前必须先加载.class对象,要么在本地机器上,要么通过网络获取。
简单摘取了部分内容,我认为书中强调的特性是反射提供了获取运行时类信息的能力,无论是本地加载的类,还是网络传输过来的外部类。在程序运行时,可能还不知道真实操作的类是什么样子,这些都是在运行时才知道的,而非编译时就已经确定的。
在工作中常用的场景也是各异的,可能已经知道了类是什么样子的结构,用反射可能是为了更优雅的获取类的方法,参数,不需要硬编(但是可能损失了性能,反射消耗CPU资源较多),也可能不知道具体是类或者类的实例传了过来,想获得注解或者某个参数。
在Baeldung的反射教程中,也举了一个例子,例如我们需要持久化一个对象Student,其对应在数据库的表名是tbl_student_data,因此我们需要在一个传入对象的方法中进行转换,达到传入任何Class的实例,能转换成对应数据库的表名的功能。
通过上面的叙述,可以看出反射其实没那么复杂的,接下来会通过例子系统地演示反射的API使用。
1 准备
接口Eating
public interface Eating {
String eats();
}
抽象类Animal
public abstract class Animal implements Eating {
public static String CATEGORY = "domestic";
private String name;
protected abstract String getSound();
//隐藏构造方法和getter/setter方法
}
另一个接口Locomotion
public interface Locomotion {
String getLocomotion();
}
创建一个子类Goat
public class Goat extends Animal implements Locomotion{
@Override
protected String getSound() {
return "bleat";
}
@Override
public String eats() {
return "grass";
}
@Override
public String getLocomotion() {
return "walks";
}
//省略构造函数
}
2 类名(Class Name)
Object goat = new Goat("goat");
Class<?> clazz = goat.getClass();
Assert.assertEquals("Goat",clazz.getSimpleName());
Assert.assertEquals("com.example.demo.reflect.Goat",clazz.getName());
Assert.assertEquals("com.example.demo.reflect.Goat",clazz.getCanonicalName());
除此之外,也可以这样获得class信息
Class<?> clazz = Class.forName("com.example.demo.reflect.Goat");
3 类修饰符(Class Modifier)
如下所示,getModifiers和Modifier类下面的静态方法可以获知类的修饰类型
Class<?> goatClass = Class.forName("com.example.demo.reflect.Goat");
Class<?> animalClass = Class.forName("com.example.demo.reflect.Animal");
int goatMods = goatClass.getModifiers();
int animalMods = animalClass.getModifiers();
Assert.assertTrue(Modifier.isPublic(goatMods));
Assert.assertTrue(Modifier.isAbstract(animalMods));
Assert.assertTrue(Modifier.isPublic(animalMods));
4 包信息(Package Information)
如下所示,getPackage方法获得类所在的包
Goat goat = new Goat("goat");
Class<?> goatClass = goat.getClass();
Package pkg = goatClass.getPackage();
Assert.assertEquals("com.example.demo.reflect", pkg.getName());
5 父类(Super Class)
如下所示,getSuperclass方法用于获得父类的信息,如果没有父类,java类公共的父类是Object.class
Goat goat = new Goat("goat");
String str = "any string";
Class<?> goatClass = goat.getClass();
Class<?> goatSuperClass = goatClass.getSuperclass();
Assert.assertEquals("Animal", goatSuperClass.getSimpleName());
Assert.assertEquals("Object", str.getClass().getSuperclass().getSimpleName());
6 接口(Implemented Interfaces)
如下所示,getInterfaces方法用于获得接口的数组,通过getSimpleName方法获得接口名
Class<?> goatClass = Class.forName("com.example.demo.reflect.Goat");
Class<?> animalClass = Class.forName("com.example.demo.reflect.Animal");
Class<?>[] goatInterfaces = goatClass.getInterfaces();
Class<?>[] animalInterfaces = animalClass.getInterfaces();
Assert.assertEquals(1, goatInterfaces.length);
Assert.assertEquals(1, animalInterfaces.length);
Assert.assertEquals("Locomotion", goatInterfaces[0].getSimpleName());
Assert.assertEquals("Eating", animalInterfaces[0].getSimpleName());
7 构造方法、方法、变量(Constructors, Methods, and Fields)
如下所示,getConstructors()可以获得构造方法的数组,和获取接口类似
Class<?> goatClass = Class.forName("com.example.demo.reflect.Goat");
Constructor<?>[] constructors = goatClass.getConstructors();
assertEquals(1, constructors.length);
assertEquals("com.example.demo.reflect.Goat", constructors[0].getName());
同样getDeclaredFields()可以获得变量的数组
Class<?> animalClass = Class.forName("com.example.demo.reflect.Animal");
Field[] fields = animalClass.getDeclaredFields();
List<String> actualFields = Arrays.asList(fields).stream().map(Field::getName).collect(Collectors.toList());
assertEquals(2, actualFields.size());
assertTrue(actualFields.containsAll(Arrays.asList("name", "CATEGORY")));
同理getDeclaredMethods()可以获得所有的方法的数组
Class<?> animalClass = Class.forName("com.example.demo.reflect.Animal");
Method[] methods = animalClass.getDeclaredMethods();
List<String> actualMethods = Arrays.asList(methods).stream().map(Method::getName).collect(Collectors.toList());
assertEquals(5, actualMethods.size());
assertTrue(actualMethods.containsAll(Arrays.asList("getName",
"setName", "getSound")));
8 修改构造方法
上面讲述了如何获取构造方法,下面讲述如何使用构造方法创建类
public class Bird extends Animal {
private boolean walks;
public Bird(){
super("bird");
}
public Bird(String name,boolean walks){
super(name);
this.walks = walks;
}
public Bird(String name) {
super(name);
}
@Override
protected String getSound() {
return null;
}
@Override
public String eats() {
return null;
}
}
可以看到有三个构造方法,通过构造函数的newInstance方法可以获得真实的实例
Class<?> birdClass = Class.forName("com.example.demo.reflect.Bird");
Constructor<?> cons1 = birdClass.getConstructor();
Constructor<?> cons2 = birdClass.getConstructor(String.class);
Constructor<?> cons3 = birdClass.getConstructor(String.class,
boolean.class);
Bird bird1 = (Bird) cons1.newInstance();
Bird bird2 = (Bird) cons2.newInstance("Weaver bird");
Bird bird3 = (Bird) cons3.newInstance("dove", true);
assertEquals("bird", bird1.getName());
assertEquals("Weaver bird", bird2.getName());
assertEquals("dove", bird3.getName());
Assert.assertFalse(bird1.walks());
Assert.assertTrue(bird3.walks());
9 修改变量
field.get()方法可以获得具体的参数,如果是private的变量,需要field.setAccessible设置可见性
Class<?> birdClass = Class.forName("com.example.demo.reflect.Bird");
Bird bird = (Bird) birdClass.getConstructor().newInstance();
Field field = birdClass.getDeclaredField("walks");
field.setAccessible(true);
assertFalse(field.getBoolean(bird));
assertFalse(bird.walks());
field.set(bird, true);
assertTrue(field.getBoolean(bird));
assertTrue(bird.walks());
10 修改方法
如下示例展示了method下的invoke方法,可以对object施加方法:
Class<?> birdClass = Class.forName("com.example.demo.reflect.Bird");
Bird bird = (Bird) birdClass.getConstructor().newInstance();
Method setWalksMethod = birdClass.getDeclaredMethod("setWalks", boolean.class);
Method walksMethod = birdClass.getDeclaredMethod("walks");
boolean walks = (boolean) walksMethod.invoke(bird);
assertFalse(walks);
assertFalse(bird.walks());
setWalksMethod.invoke(bird, true);
boolean walks2 = (boolean) walksMethod.invoke(bird);
assertTrue(walks2);
assertTrue(bird.walks());
总结:从上面的例子可以看到反射的API和调用都是比较简单的,当然实际应用中的例子没这么简单,开源框架例如guava封装了一些方法,Invokable,Dynamic Proxies,比原生的API使用更方便也更优雅。
本文是一篇反射的基础理论,引用了《Java编程思想》的内容,介绍了Java的反射包里API的基本用法,万变不离其宗,只有理解了反射设计的思路和使用场景才能更好地使用这个工具。工作里反射是建议慎用的,除非经过仔细评估,因为反射的性能开销较大。
参考文献:
《Java编程思想》
https://www.baeldung.com/java-reflection
http://ifeve.com/guava-reflection/
祝大家编码愉快,工作愉快,欢迎关注我的公众号,一起分享交流
本文地址:https://blog.csdn.net/weixin_36268933/article/details/109892186