Java小白入门到实战应用教程-反射

前言

我们前面关于Java基础语法的所有知识点基本上都讲完了,今天我们来了解一个相对高级一点的知识点:反射。

我们知道在Java中创建对象我们可以通过关键字new去创建类的对象实例。

那么除了这种方式,在java中还可以通过反射去创建Java类的实例。

正文

在Java中,当我们使用某个类时,首先需要将其加载到JVM中。类的加载是通过类加载器(ClassLoader)完成的,而每个类在JVM中都有一个对应的Class对象,这个对象包含了类的所有信息,如类名、父类、实现的接口、构造方法、成员变量、成员方法等。

所以我们应用反射首先需要获取类对应的Class对象。

获取Class对象的方法主要有三种:

  1. 使用Class类的forName静态方法Class.forName("完整类名")。这种方式会加载指定的类,如果类未找到则抛出ClassNotFoundException。
  2. 使用对象的getClass方法:已经存在的对象实例可以通过调用getClass()方法获取其Class对象。
  3. 使用类字面量:对于任何类型的T,T.class表达式都表示T的Class对象。

下面直接先看应用的样例代码:

public class Demo {

    private String name;
    private int age;

    public Demo() {
    }
    
    public Demo(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

我们首先定义了一个Demo类,然后类中有两个属性。

然后通过在测试类中去获取Demo类对应的Class

public class Test {

    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> aClass = Class.forName("com.example.fanshe.Demo");

        Class<? extends Demo> aa = new Demo().getClass();

        Class<Demo> demoClass = Demo.class;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

在上面的代码中,我们通过三种方式去获取Class对象。

通过反射创建对象实例

通过反射,我们可以在运行过程中去获取对象的实例,

这主要通过Class对象的newInstance()方法(Java 9以后已不推荐使用,建议使用Class.getDeclaredConstructor().newInstance())或Constructor对象的newInstance()方法实现。

我们还是接着上面的样例代码接着写,演示通过反射创建对象实例:

public class Test {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> aClass = Class.forName("com.example.fanshe.Demo");

        /*Class<? extends Demo> aa = new Demo("aa", 12).getClass();

        Class<Demo> demoClass = Demo.class;*/
        
        //通过无参的构造方法
        Demo o = (Demo) aClass.getDeclaredConstructor().newInstance();
        o.setAge(111);//可以去访问类实例的属性
        
        //还可以通过有参的构造方法。
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class, Integer.class);
        Demo object = (Demo) declaredConstructor.newInstance("张三", 11);
        
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

解释:

aClass.getDeclaredConstructor(String.class, Integer.class);中的参数为两个class,表明我们所实例化的类中构造方法中参数的类型。

通过反射调用类的方法

我们还可以通过反射来调用类的方法。这主要通过Method类实现。

public class Test {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Class<?> aClass = Class.forName("com.example.fanshe.Demo");

        /*Class<? extends Demo> aa = new Demo("aa", 12).getClass();

        Class<Demo> demoClass = Demo.class;*/
        //通过反射获取Demo的实例对象
        Demo o = (Demo) aClass.getDeclaredConstructor().newInstance();
        
        //调用getMethod方法,主要有两个参数
        Method echo = aClass.getMethod("echo", String.class);
        echo.setAccessible(true);//设置方法的可访问
        //通过invoke方法去调用类实例的方法
        echo.invoke(o,"gggggg");
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

解释:我们最终调用类实例的方法的时候是通过Method的invoke方法去调用的,invoke方法有两个参数,一个是通过反射获取的对象实例,一个是参数的值。

注意
  1. 性能开销:反射操作通常比直接代码调用要慢,因为它需要额外的类型检查和安全检查。
  2. 安全风险:反射允许访问类的私有成员,这可能会破坏封装性,如果滥用可能导致安全问题。
  3. 代码可读性:过度使用反射会使代码难以理解和维护,因为反射调用不会直接出现在编译时检查中。

结尾

Java反射是Java语言提供的一种强大机制,它极大地增强了Java的动态性和灵活性。通过反射,我们可以在运行时检查或修改类的行为,这对于一些特定的应用场景(如框架开发、测试框架等)非常有用。然而,反射也带来了一定的性能开销和安全风险,因此在使用时需要谨慎评估其利弊.