43、Java基础教程之常用类库·反射机制

  • 1️⃣ 概念
  • 2️⃣ 优势和缺点
  • 3️⃣ 使用
    • 3.1 Class类
  • 3.2 获取类的结构信息- 构造函数
  • 3.3 获取类的结构信息- 方法
  • 3.4 获取类的结构信息- 字段
  • 3.5 动态创建对象、调用方法和设置属性
  • 3.6 动态代理
  • 4️⃣ 底层原理
  • 5️⃣ 应用场景
  • * 总结
  • * 本文源码下载地址

*

1️⃣ 概念

反射机制是Java语言的一个重要特性,它使得程序可以在运行时获取和操作编译时未知的类的相关信息。在传统的开发模式中,我们需要在编译时确定类的结构,并且通过编码方式直接使用类的方法和属性。而反射机制则打破了这种限制,可以在运行时动态地创建对象、获取类的成员和调用方法。

可以说反射提供了强大的工具,使得开发人员能够编写更加灵活和通用的代码。

2️⃣ 优势和缺点

关于反射机制的优缺点的详细介绍如下:

*优点:

  • 动态性:反射机制允许程序在运行时动态地获取和操作类型信息,而不需要在编译时就确定。这使得程序可以根据运行时环境的变化进行适应和调整,具有更大的灵活性和可扩展性;
  • 通用性:反射机制可以被广泛地用于处理各种类型的对象和数据结构。它提供了一种统一的方式来处理不同类型的对象,而不需要针对每个类型实现特定的代码逻辑;
  • 功能强大:通过反射,程序可以动态地创建新的对象实例、调用方法、访问和修改成员变量,甚至可以访问和调用私有成员。这样可以实现很多复杂的功能,例如依赖注入、插件系统、框架扩展等。

*缺点:

  • 性能影响:由于反射机制需要在运行时进行类型检查和动态调用,相比直接调用编译时已知的类型,它会引入额外的性能开销。反射操作通常比直接访问更慢,因此在性能敏感的场景下不适宜过度依赖反射;
  • 安全问题:反射机制破坏了面向对象编程的封装性原则。它可以绕过访问修饰符的限制,访问和修改私有成员变量或调用私有方法,这可能造成安全隐患;
  • 复杂性增加:由于反射机制提供了灵活性,但也带来了一定的复杂性。使用反射需要对类型的结构和操作有较深入的了解,错误的使用反射可能导致代码难以理解、维护困难。

综上所述,反射机制的优点包括动态性、通用性和功能强大,但同时也存在性能影响、安全问题和增加复杂性的缺点。 在开发中,应该根据具体需求谨慎使用反射,并权衡其带来的好处和代价。

3️⃣ 使用

3.1 Class类

Java中的每个类都有一个Class对象对应,该对象保存了与该特定类相关的类的结构信息。

获取Class类对象的三种方式:

  • 通过类的class属性获取,例如String.class。适用于已知类的名称,但没有创建对象实例的情况;
  • 通过对象的getClass()方法获取,例如str.getClass()。适用于已经具有对象实例的情况;
  • 通过Class类的静态方法forName()获取。该方法需要传入完全限定名(Fully Qualified Name)作为参数,例如Class.forName("java.lang.String")。适用于只知道类的名称字符串,并且在运行时需要动态地加载和使用该类的情况。

示例如下:

public class UseDemo {
   
     
    public static void main(String[] args) {
   
     
        // 通过类名获取Class对象
        Class<?> class1 = String.class;
        System.out.println(class1.getName());

        // 通过对象的getClass()方法获取Class对象
        String str = "Hello";
        Class<?> class2 = str.getClass();
        System.out.println(class2.getName());

        // 通过Class类的forName()方法获取Class对象
        try {
   
     
            Class<?> class3 = Class.forName("java.lang.String");
            System.out.println(class3.getName());
        } catch (ClassNotFoundException e) {
   
     
            e.printStackTrace();
        }
    }
}

在这段代码中,使用了不同的三种方式来获取Class类对象。使用了 getName()方法来获取类的名字。输出结果:

java.lang.String
java.lang.String
java.lang.String

3.2 获取类的结构信息- 构造函数

反射机制允许我们获取类的各种结构信息,如方法、字段、构造函数等。其中,可以使用以下方法获取构造函数:

  • getConstructors():获取所有公共构造方法;
  • getDeclaredConstructors():获取所有构造方法(包括私有的);
  • getConstructor(Class<?>... parameterTypes):获取指定参数类型的构造方法。

以下是一个Java演示代码,展示了通过反射机制获取类的构造方法的不同方式:

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

public class ReflectionDemo {
   
     
    public static void main(String[] args) {
   
     
        try {
   
     
            // 获取类的Class对象
            Class<?> clazz = MyClass.class;
            
            // 获取所有公共构造方法
            Constructor<?>[] publicConstructors = clazz.getConstructors();
            System.out.println("所有公共构造方法:");
            printConstructors(publicConstructors);
            
            // 获取所有构造方法(包括私有的)
            Constructor<?>[] allConstructors = clazz.getDeclaredConstructors();
            System.out.println("所有构造方法(包括私有的):");
            printConstructors(allConstructors);
            
            // 获取指定参数类型的构造方法
            Class<?>[] parameterTypes = {
   
      int.class, String.class };
            Constructor<?> constructor = clazz.getConstructor(parameterTypes);
            System.out.println("指定参数类型的构造方法:");
            printConstructor(constructor);
        } catch (Exception e) {
   
     
            e.printStackTrace();
        }
    }
    
    // 打印构造方法信息
    private static void printConstructors(Constructor<?>[] constructors) {
   
     
        for (Constructor<?> constructor : constructors) {
   
     
            // 获取构造方法的修饰符,然后使用Modifier.toString()将修饰符转换成字符串形式
            String modifiers = Modifier.toString(constructor.getModifiers());
            // 获取构造方法的名称
            String name = constructor.getName();
            // 获取构造方法的参数类型,返回一个Class<?>数组。每个元素都表示一个参数类型
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            
            System.out.print(modifiers + " " + name + "(");
            for (int i = 0; i < parameterTypes.length; i++) {
   
     
                if (i > 0) {
   
     
                    System.out.print(", ");
                }
                System.out.print(parameterTypes[i].getName());
            }
            System.out.println(")");
        }
        System.out.println();
    }
    
    // 打印单个构造方法信息
    private static void printConstructor(Constructor<?> constructor) {
   
     
        String modifiers = Modifier.toString(constructor.getModifiers());
        String name = constructor.getName();
        Class<?>[] parameterTypes = constructor.getParameterTypes();
        
        System.out.print(modifiers + " " + name + "(");
        for (int i = 0; i < parameterTypes.length; i++) {
   
     
            if (i > 0) {
   
     
                System.out.print(", ");
            }
            System.out.print(parameterTypes[i].getName());
        }
        System.out.println(")");
        System.out.println();
    }
}

class MyClass {
   
     
    public MyClass(int val, String str) {
   
     }
    
    private MyClass(double val) {
   
     }
    
    protected MyClass(boolean flag) {
   
     }

    MyClass() {
   
     }
}

运行上述代码,将输出以下结果:

所有公共构造方法:
public com.xiaoshan.demo2.MyClass(int, java.lang.String)

所有构造方法(包括私有的):
 com.xiaoshan.demo2.MyClass()
protected com.xiaoshan.demo2.MyClass(boolean)
private com.xiaoshan.demo2.MyClass(double)
public com.xiaoshan.demo2.MyClass(int, java.lang.String)

指定参数类型的构造方法:
public com.xiaoshan.demo2.MyClass(int, java.lang.String)

上面这段代码展示了获取类的所有公共构造方法、所有构造方法和特定参数类型构造方法的方式,并使用反射机制将构造方法的相关信息打印出来。

3.3 获取类的结构信息- 方法

反射机制允许我们获取类的各种结构信息,如方法、字段、构造函数等。其中,可以使用以下方法获取类中的方法:

  • getMethods():获取类中的公共方法;
  • getDeclaredMethods():获取类中所有声明的方法(包括私有方法);
  • getMethod(String name, Class<?>... parameterTypes):根据方法名和参数类型获取指定的公共方法;
  • getDeclaredMethod(String name, Class<?>... parameterTypes):根据方法名和参数类型获取指定的方法。

以下是一个Java演示代码,展示了通过反射机制获取类中方法的不同方式:

import java.lang.reflect.Method;

public class UseDemo {
   
     
    public static void main(String[] args) {
   
     
        // 获取类对象
        Class<MyClass> myClass = MyClass.class;

        // 获取声明的所有方法(包括公共、保护、默认和私有方法)
        Method[] declaredMethods = myClass.getDeclaredMethods();
        System.out.println("Declared methods:");
        printMethods(declaredMethods);

        // 获取所有公共方法(包括继承的和实现的父接口的方法)
        Method[] publicMethods = myClass.getMethods();
        System.out.println("Public methods:");
        printMethods(publicMethods);

        // 获取指定方法
        try {
   
     
            Method specificMethod = myClass.getMethod("specificMethod");
            System.out.println("Specific method:");
            System.out.println(specificMethod);
        } catch (NoSuchMethodException e) {
   
     
            e.printStackTrace();
        }
    }

    private static void printMethods(Method[] methods) {
   
     
        for (Method method : methods) {
   
     
            System.out.println(method);
        }
        System.out.println("Total methods: " + methods.length);
        System.out.println();
    }
}

class MyClass {
   
     

    public void publicMethod() {
   
     }

    private void privateMethod() {
   
     }

    protected void protectedMethod() {
   
     }

    void defaultMethod() {
   
     }

    public static void staticMethod() {
   
     }

    public void specificMethod() {
   
     }
}

运行上述代码,将输出以下结果:

Declared methods:
public void com.xiaoshan.demo3.MyClass.publicMethod()
private void com.xiaoshan.demo3.MyClass.privateMethod()
protected void com.xiaoshan.demo3.MyClass.protectedMethod()
void com.xiaoshan.demo3.MyClass.defaultMethod()
public static void com.xiaoshan.demo3.MyClass.staticMethod()
public void com.xiaoshan.demo3.MyClass.specificMethod()
Total methods: 6

Public methods:
public void com.xiaoshan.demo3.MyClass.publicMethod()
public static void com.xiaoshan.demo3.MyClass.staticMethod()
public void com.xiaoshan.demo3.MyClass.specificMethod()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
Total methods: 12

Specific method:
public void com.xiaoshan.demo3.MyClass.specificMethod()

上面这段代码展示了获取类的所有公共方法、所有方法和特定参数类型方法的方式,并将方法的相关信息打印出来。需要注意的是,从结果中可以观察到,用于获取所有公共方法的 getMethods() 不仅包括本类公共方法,还包括了继承的和实现的父接口中的方法。

3.4 获取类的结构信息- 字段

反射机制允许我们获取类的各种结构信息,如方法、字段、构造函数等。其中,可以使用以下方法获取类中的字段:

  • getFields():获取类中声明的公共字段;
  • getDeclaredFields():获取类中声明的所有字段(包括私有字段);
  • getDeclaredField(String name):根据名称获取类中的指定字段(包括私有字段)。

以下是一个Java演示代码,展示了通过反射机制获取类中字段的不同方式:

import java.lang.reflect.Field;

public class UseDemo {
   
     

    public static void main(String[] args) {
   
     
        try {
   
     
            // 获取类的Class对象
            Class<?> clazz = MyClass.class;

            // 获取所有公共字段(包括继承自父类的公共字段)
            Field[] publicFields = clazz.getFields();
            System.out.println("公共字段:");
            for (Field field : publicFields) {
   
     
                System.out.println(field.getName());
            }

            // 获取所有字段(包括公共、私有和受保护字段)
            Field[] declaredFields = clazz.getDeclaredFields();
            System.out.println("\n所有字段:");
            for (Field field : declaredFields) {
   
     
                System.out.println(field.getName());
            }

            // 获取指定字段
            Field privateField = clazz.getDeclaredField("privateField");
            System.out.println("\n指定字段:");
            System.out.println(privateField.getName());
        } catch (Exception e) {
   
     
            e.printStackTrace();
        }
    }
}

class MyClass {
   
     
    private String privateField;
    public int publicField;
}

运行上述代码,将输出以下结果:

公共字段:
publicField

所有字段:
privateField
publicField

指定字段:
privateField

3.5 动态创建对象、调用方法和设置属性

利用反射机制,我们可以在运行时动态地创建对象和调用其方法。通过获取类的构造函数并传入相应的参数来创建实例,然后可以对该实例调用方法。

以下是一些常用的动态创建对象和调用其方法的方法:

  • Class对象.newInstance():通过Class对象创建类对象;
  • Constructor对象.newInstance():通过Constructor对象创建类对象;
  • setAccessible(boolean flag):通过设置参数为true,可以绕过Java语言的访问控制检查,从而访问并修改对象的私有成员变量、方法或构造函数;
  • invoke(Object obj, Object... args):动态调用一个对象的方法。第一个参数是要调用方法的对象实例,第二个可变参数是方法的参数;
  • set(Object obj, Object value):将指定对象的字段设置为指定的值。需要注意此方法只能修改非final修饰的字段的值。第一个参数是要修改字段的对象实例,第二个参数是要设置的值。

示例如下:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class UseDemo {
   
     

    public static void main(String[] args) {
   
     
        Class<Cat> clazz = Cat.class;
        try {
   
     
            // 通过Class对象创建类对象
            Cat cat1 = clazz.newInstance();
            System.out.println(cat1);

            // 通过构造方法创建类对象
            Constructor<Cat> constructor = clazz.getConstructor(String.class, int.class);
            Cat cat2 = constructor.newInstance("橘猫", 2);
            System.out.println(cat2);

            // 调用方法
            Method method = clazz.getDeclaredMethod("eat", String.class);
            method.setAccessible(true);
            method.invoke(cat2, "猫粮");

            // 获取并修改私有字段的值
            Field field = clazz.getDeclaredField("name");
            field.setAccessible(true);
            field.set(cat1, "折耳猫");
            System.out.println(cat1);
            
        } catch (InstantiationException e) {
   
     
            e.printStackTrace();
        } catch (IllegalAccessException e) {
   
     
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
   
     
            e.printStackTrace();
        } catch (InvocationTargetException e) {
   
     
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
   
     
            e.printStackTrace();
        }

    }
}

class Cat{
   
     
    private String name;
    private int age;

    private void eat(String food){
   
     
        System.out.println("吃东西:" + food);
    }

    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 Cat() {
   
     
    }

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

    @Override
    public String toString() {
   
     
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

运行结果:

Cat{name='null', age=0}
Cat{name='橘猫', age=2}
吃东西:猫粮
Cat{name='折耳猫', age=0}

3.6 动态代理

反射机制还常用于实现动态代理。动态代理是Java提供的一种强大的机制,允许程序运行时创建一个实现一组接口的代理类。通过实例化代理对象,并能够拦截到代理对象所调用的方法,从而实现额外的操作。

首先,定义一个接口MyInterface,其中包含了一个无返回值的方法myMethod()

interface MyInterface {
   
     
    void myMethod();
}

接着,定义一个RealObject类实现MyInterface接口,并且实现myMethod()方法:

public class RealObject implements MyInterface {
   
     
    public void myMethod() {
   
     
        System.out.println("RealObject method called.");
    }
} 

接着,定义一个DynamicProxy类实现InvocationHandler接口,作为代理对象的处理程序。它接收一个真实对象作为参数,在方法调用前后进行处理。在这里使用了反射机制的invoke()方法调用真实对象的方法,在方法调用之前和调用之后做了一些处理操作:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxy implements InvocationHandler {
   
     
    private Object subject;

    public DynamicProxy(Object subject) {
   
     
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
     
        System.out.println("Before method call.");
        Object result = method.invoke(subject, args);
        System.out.println("After method call.");
        return result;
    }

}

接下来,通过Proxy.newProxyInstance()方法创建一个代理对象proxy。该方法接收三个参数:类加载器、接口数组和代理对象的处理程序。
最后,通过代理对象proxy调用myMethod()方法时,将执行代理类的增强处理,然后调用真实对象的方法。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyDemo {
   
     
    public static void main(String[] args) {
   
     
        RealObject realObject = new RealObject();
        InvocationHandler handler = new DynamicProxy(realObject);

        MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
                realObject.getClass().getClassLoader(),
                new Class<?>[]{
   
     MyInterface.class},
                handler
        );

        proxy.myMethod();
    }
}

运行结果:

Before method call.
RealObject method called.
After method call.

4️⃣ 底层原理

反射机制是面向对象编程语言中的一项重要特性,它允许程序在运行时动态地获取和操作类的成员信息,包括类的方法、属性、构造函数等。通过反射,程序可以直接操作类的成员,无需在编译时明确引用这些成员。

底层原理主要涉及三个方面:反射的元数据、动态加载和运行时调用。

1、 元数据:元数据是描述类的结构和成员信息的数据在Java中,每个类都有一个Class对象来表示它自身,这个Class对象持有了类的元数据类的元数据包括类名、父类、实现的接口、字段名称和类型、方法名称和参数等信息;
2、 动态加载:反射利用ClassLoader机制动态加载类ClassLoader负责读取字节码文件,并生成对应的Class对象当需要使用某个类时,ClassLoader会根据类名查找并载入字节码文件,然后创建对应的Class对象通过Class对象,可以获取类的各种信息;
3、 运行时调用:通过反射获取到类的Class对象后,就可以在运行时动态调用类的方法、访问或修改对象的属性等调用方法时,先获取Method对象,然后通过invoke()方法传递实例和参数来调用方法更新或获取属性值时,通过Field对象的get()和set()方法进行操作;

需要注意以下几点:

  • 反射机制的底层实现依赖于字节码的解析和动态加载等 技术,因此在运行时使用反射会带来一定的性能损失;
  • 反射机制能够突破访问权限限制,即使某个成员是私有的,也可以通过反射获取和操作它。但这种方式容易破坏封装性,应谨慎使用;
  • 使用反射机制进行编程时,应特别注意类型的安全性,避免出现类型错误。

5️⃣ 应用场景

通过反射,程序可以动态地获取类的信息并使用这些信息实现一些灵活性较高的功能。下面是几个反射机制的应用场景:

1、 应用程序结构分析:反射可以在运行时分析一个类的成员(字段、方法、属性等),并据此生成文档、自动生成代码或进行其它类似的操作它可以用于构建工具、集成开发环境(IDE)和测试框架等;
2、 插件系统:反射机制可以使应用程序支持插件式的架构,通过在运行时动态加载扩展,从而实现动态扩展功能比如,一个图像处理软件可以通过反射机制在运行时根据插件提供的类和方法来进行图像滤镜的选择和应用;
3、 框架开发:使用反射可以实现通用且灵活的框架,使框架能够适应各种不同的类和对象,并提供基于配置文件的动态行为例如,Java的Spring框架就广泛使用了反射机制,通过读取配置文件中定义的类和方法以及它们的属性,实现依赖注入和不同模块之间的松耦合;
4、 动态代理:通过反射机制可以动态地创建代理类以及其实例代理模式在实现中经常使用反射来实现,它将对被代理对象的方法调用进行拦截,并在方法调用前后执行一些额外的操作动态代理广泛应用于AOP(面向切面编程)和RPC(远程过程调用)等方面;
5、 序列化/反序列化:某些场景下,我们可能需要将对象以二进制或其他格式进行存储、传输或保存到文件中反射提供了一种机制来获取对象的内部信息,并将其转换为可进行持久化处理的格式;

这些仅是反射机制的一些常见应用场景,还有许多其他领域也能发挥反射的作用,比如动态配置、ORM框架、动态加载类等。反射的强大功能使得程序在运行时具备更高的灵活性和扩展性,但也要注意合理使用,因为反射操作相比直接调用会引入一些性能开销。

* 总结

Java的反射机制为开发人员提供了一种灵活而强大的工具,可以在运行时检查和操作类的结构信息。通过反射,我们可以动态地创建对象、调用方法、访问字段等,并能够实现一些高级功能如动态代理。然而,过度使用反射可能会增加代码的复杂性和性能开销,因此需要慎重使用并仔细权衡利弊。

* 本文源码下载地址

Java的反射机制讲解案例代码 Class类、 获取类的结构信息:构造函数、方法和字段,动态创建对象、调用方法和设置属性

*


[* ]nbsp_nbsp 2