欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

万变不离其宗之反射原理篇

程序员文章站 2022-06-27 16:12:40
Java工程师工作中会需要用到反射,本文从原理和使用场景出发,讲解下什么是反射。在《Java编程思想》中,反射只有极小的篇幅介绍,在334页有“反射:运行时的类信息”这样一个小章节。书中这样描述:在使用IDE构建项目时,可以通过代表不同组件的图标拖拽到表单中创建程序,然后在编程时通过设置构建的属性值来配置它们。这种设计时要求构件都是可实例化的,而且要暴露其部分信息,以允许程序员读取好修改构件的属性。反射提供了一种机制-用来检查可用的方法,并返回方法名。人们想要在运行时获取类的新的另一个动机,是希望...

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

相关标签: Java 反射