Hefery 的个人网站

Hefery's Personal Website

Contact:hefery@126.com
  menu
73 文章
0 浏览
3 当前访客
ღゝ◡╹)ノ❤️

Java基础—反射机制

反射简介

反射:将类的组成部分(成员变量、构造方法、成员方法)封装成其他对象(.class --> .java

反射:把Java类中的各个成分映射成Java对象。在运行状态中,对于任意一个类,都能够知道这个类的所以属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性。实现动态获取信息及动态调用对象方法的功能

Java代码在计算机的阶段

  1. 源代码Source阶段:Person.java --> Person.class
  2. 类对象 Class 阶段:通过类加载器ClassLoader加载 Person.class 文件,并将类的组成部分(成员变量、构造方法、成员方法)封装成 Class 对象{ Field[] fields、Constructor[] constructors、Method[] methods)}
  3. 运行时Runtime阶段:创建对象 new Person()

反射优点

  • 在程序运行过程中,操作这些对象
  • 解耦,捉高程序的可扩展性

反射缺点:

  • 性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射
  • 安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了
  • 内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化

反射应用

  • 通过反射机制访问 Java 对象的属性,方法,构造方法等
  • 最重要的用途就是开发框架。比如 Spring 都是配置化(通过 XML 文件配置 Bean 等),为了保证框架的通用性,需要根据配置文件加载不同的类或者对象,调用不同的方法,就需要使用反射,运行时动态加载需要的加载的对象

理解反射重点就在于理解什么是“运行时”,为什么我们要在“运行时”获取类的信息

反射操作

Class对象

Class对象获取

  • 源代码Source阶段:Class.forName("全类名"),将字节码文件加载进内存,返回 Class 对象
    多用于配置文件,将类名定义在配置文件中。读取文件,加载类
  • 类对象 Class 阶段:类名.class,已经加载至内存,通过类名的 class 属性获取 Class 对象
    多用于参数的传递
  • 运行时Runtime阶段:对象.getClass(),Object类定义了 getClass() 方法发挥对象的 Class 对象
    多用于对象的获取字节码

类对象 Class 阶段将 .class 文件加载至内存,通过双亲委派模型,可以产生唯一的 Class 对象

public class ReflectDemo2 {

    public static void main(String[] args) throws ClassNotFoundException {
        // 1. Class.forName("全类名")
        Class cls1 = Class.forName("com.hefery.kafkademo.reflect.Person");
        System.out.println(cls1);  // class com.hefery.kafkademo.reflect.Person

        // 2.类名.class
        Class<Person> cls2 = Person.class;
        System.out.println(cls2);  // class com.hefery.kafkademo.reflect.Person

        // 3.对象.getClass()
        Person person = new Person();
        Class cls3 = person.getClass();
        System.out.println(cls3);  // class com.hefery.kafkademo.reflect.Person

        // 比较这三个 Class 对象是否相同
        System.out.println(cls1 == cls2);  // true
        System.out.println(cls1 == cls3);  // true
    }

}

比较不同阶段获取的 Class 对象,发现它们相同:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次且唯一(双亲委派),不论通过哪一种方式获取的Class对象都是同一个

Class对象使用

  • 获取成员变量
    Field[] getFields():获取所有 public 修饰的成员变量
    Field getField(String name):返回指定名称的 public 修饰的成员变量
    Field getDeclaredField(String name):获取所有成员变量
    Field[] getDeclaredFields():获取指定名称的除 private 修饰的成员变量
  • 获取构造方法
    Constructor<?>[] getConstructors()
    Constructor<T> getConstructor(Class<?>... parameterTypes)
    Constructor<?>[] getDeclaredConstructors()
    Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
  • 获取成员方法
    Method[] getMethods():获取所有 public 修饰的方法
    Method getMethod(String name, Class<?>... parameterTypes):获取指定名称的方法
    Method[] getDeclaredMethods()
    Method getDeclaredMethod(String name, Class<?>... parameterTypes)
  • 获取类名信息
    String getName():Class 对象所表示实体(类、接口、数组、基本类型、void)String 形式的名称

Field对象

  • 设置值
    void set(Object obj, Object value)
  • 获取值
    Object get(Object obj)

Constructor对象

  • 创建对象:
    T newInstance(Object... initargs):使用空参构造创建对象,可简化:C1ass对象.newInstance()

Method对象

  • 执行方法
    Object invoke(Object obj, Object... args)
  • 获取方法名称
    String getName()

忽略访问权限修饰符的安全检查,使用 getDeclaredFields() 访问类私有属性会抛IllegalAccessException
setAccessible(true),反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题

public class ReflectDemo {

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        // 获取 Person 的 Class 对象
        Class<Person> personClass = Person.class;
        // 创建 Person 对象
        Person person = new Person();

        /**
         * 1. 获取成员变量
         * Field[] getFields():获取所有 public 修饰的成员变量
         * Field getField(String name):返回指定名称的 public 修饰的成员变量
         * Field getDeclaredField(String name):获取所有成员变量
         * Field[] getDeclaredFields():获取指定名称的除 private 修饰的成员变量
         */
        // Field[] getFields():获取所有 public 修饰的成员变量
        Field[] personClassFields = personClass.getFields();
        Arrays.stream(personClassFields).forEach(System.out::println);  // Person.nickName1
        // Field getField(String name):返回指定名称的 public 修饰的成员变量
        Field nickName1 = personClass.getField("nickName1");  // public String nickName1
        // Field对象获取/修改成员变量的值
        Object obj1 = nickName1.get(person);
        System.out.println(obj1);  // null,没有设置初始值
        nickName1.set(person, "2018");
        System.out.println(person.toString());  // Person(name=null, age=0, idCard=null, nickName1=2018, nickName2=null, nickName3=null, nickName4=null)
        // Field getDeclaredField(String name):获取所有成员变量
        Field[] personClassDeclaredFields = personClass.getDeclaredFields();
        Arrays.stream(personClassDeclaredFields).forEach(System.out::println);
        /*
            private java.lang.String com.hefery.kafkademo.reflect.Person.name
            private int com.hefery.kafkademo.reflect.Person.age
            private java.lang.String com.hefery.kafkademo.reflect.Person.idCard
            public java.lang.String com.hefery.kafkademo.reflect.Person.nickName1
            protected java.lang.String com.hefery.kafkademo.reflect.Person.nickName2
            java.lang.String com.hefery.kafkademo.reflect.Person.nickName3
            private java.lang.String com.hefery.kafkademo.reflect.Person.nickName4
         */
        // Field[] getDeclaredFields():获取指定名称的除 private 修饰的成员变量
        Field nickName4 = personClass.getDeclaredField("nickName4");// private String nickName4
        // 使用 getDeclaredFields() 访问类私有属性会抛出 java.lang.IllegalAccessException,需要忽略访问权限修饰符的安全检查
        nickName4.setAccessible(true);
        Object obj2 = nickName4.get(person);
        System.out.println(obj2);  // null

        System.out.println("======================================================================================");

        /**
         * 2. 获取构造方法
         *      Constructor<?>[] getConstructors():
         *      Constructor<T> getConstructor(Class<?>... parameterTypes):
         *      Constructor<?>[] getDeclaredConstructors():
         *      Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):
         */
        // Constructor<T> getConstructor(Class<?>... parameterTypes):
        Constructor<Person> personClassConstructor1 = personClass.getConstructor(String.class, int.class, String.class, String.class, String.class, String.class, String.class);
        System.out.println(personClassConstructor1);  // Person(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String)
        // 全参构造创建对象
        Person newInstance1 = personClassConstructor1.newInstance("Hefery", 23, "123456", "Hefery1", "Hefery2", "Hefery3", "Hefery4");
        System.out.println(newInstance1);  // Person(name=Hefery, age=23, idCard=123456, nickName1=Hefery1, nickName2=Hefery2, nickName3=Hefery3, nickName4=Hefery4)

        Constructor<Person> personClassConstructor2 = personClass.getConstructor();
        System.out.println(personClassConstructor2);  // Person()
        // 空参构造创建对象
        Person newInstance2 = personClassConstructor2.newInstance();
        System.out.println(newInstance2);  // Person(name=null, age=0, idCard=null, nickName1=null, nickName2=null, nickName3=null, nickName4=null)

        // 使用空参构造创建对象,可简化:C1ass对象.newInstance()
        Person newInstance3 = personClass.newInstance();
        System.out.println(newInstance3);  // Person(name=null, age=0, idCard=null, nickName1=null, nickName2=null, nickName3=null, nickName4=null)

        // Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):
        Constructor<Person> personClassDeclaredConstructor = personClass.getDeclaredConstructor(String.class, int.class, String.class, String.class, String.class, String.class, String.class);
        System.out.println(personClassDeclaredConstructor);  // public com.hefery.kafkademo.reflect.Person(java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String)
        // 全参构造创建对象
        Person newInstance4 = personClassDeclaredConstructor.newInstance("Hefery", 24, "123456", "Hefery1", "Hefery2", "Hefery3", "Hefery4");
        System.out.println(newInstance4);  // Person(name=Hefery, age=24, idCard=123456, nickName1=Hefery1, nickName2=Hefery2, nickName3=Hefery3, nickName4=Hefery4)

        System.out.println("======================================================================================");

        /*
            3. 获取成员方法
                Method[] getMethods():获取所有public修饰的方法
                Method getMethod(String name, Class<?>... parameterTypes):获取指定名称的方法
                Method[] getDeclaredMethods()
                Method getDeclaredMethod(String name, Class<?>... parameterTypes)
         */
        // Method getMethod(String name, Class<?>... parameterTypes):获取指定名称的方法
        Method eatMethod = personClass.getMethod("eat");
        // 执行空参方法
        eatMethod.invoke(person);  // eat ...
        Method eatFoodMethod = personClass.getMethod("eat", String.class);
        // 执行带参方法
        eatFoodMethod.invoke(person, "火龙果");  // eat 火龙果 ...

        // Method[] getMethods():获取所有public修饰的方法
        Method[] personClassMethods = personClass.getMethods();
        Arrays.stream(personClassMethods).forEach(System.out::println);
        /*
            public boolean com.hefery.kafkademo.reflect.Person.equals(java.lang.Object)
            public java.lang.String com.hefery.kafkademo.reflect.Person.toString()
            public int com.hefery.kafkademo.reflect.Person.hashCode()
            public java.lang.String com.hefery.kafkademo.reflect.Person.getName()
            public void com.hefery.kafkademo.reflect.Person.setName(java.lang.String)
            public void com.hefery.kafkademo.reflect.Person.eat()
            public void com.hefery.kafkademo.reflect.Person.eat(java.lang.String)
            public void com.hefery.kafkademo.reflect.Person.setIdCard(java.lang.String)
            public int com.hefery.kafkademo.reflect.Person.getAge()
            public void com.hefery.kafkademo.reflect.Person.setAge(int)
            public void com.hefery.kafkademo.reflect.Person.setNickName3(java.lang.String)
            public java.lang.String com.hefery.kafkademo.reflect.Person.getNickName1()
            public void com.hefery.kafkademo.reflect.Person.setNickName1(java.lang.String)
            public java.lang.String com.hefery.kafkademo.reflect.Person.getNickName3()
            public java.lang.String com.hefery.kafkademo.reflect.Person.getIdCard()
            public java.lang.String com.hefery.kafkademo.reflect.Person.getNickName4()
            public void com.hefery.kafkademo.reflect.Person.setNickName2(java.lang.String)
            public java.lang.String com.hefery.kafkademo.reflect.Person.getNickName2()
            public void com.hefery.kafkademo.reflect.Person.setNickName4(java.lang.String)
            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 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()
         */

        System.out.println("======================================================================================");

        /*
            4. 获取类名信息
            String getName()
         */
        String personClassName = personClass.getName();
        System.out.println(personClassName);  // Person
    }

}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {

    private String name;

    private int age;

    private String idCard;

    // 验证权限修饰符
    public String nickName1;
    protected String nickName2;
    String nickName3;
    private String nickName4;

    public void eat() {
        System.out.println("eat ...");
    }

    public void eat(String food) {
        System.out.println("eat " + food + " ...");
    }

}

案例

配置文件创建类对象

需求:不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法

实现:不改代码,改配置文件

  • 配置文件:/resources/person.properties
    className=com.hefery.kafkademo.reflect.Person
    methodName=eat
    
  • 反射机制

步骤:

  • 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
  • 在程序中加载读取配置文件
  • 使用反射技术来加载类文件进内存
  • 创建对象
  • 执行方法
public class ReflectTest {

    public static void main(String[] args) throws IOException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, NoSuchMethodException {
        // 1. 加载配置文件
        // 1.1 创建 Properties 对象
        Properties properties = new Properties();
        // 1.2 加载配置文件,转换为集合
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream resourceAsStream = classLoader.getResourceAsStream("person.properties");
        properties.load(resourceAsStream);

        // 2. 获取配置文件数据
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");

        // 3. 加载该类进内存
        Class cls = Class.forName(className);

        // 4. 创建对象
        Object instance = cls.newInstance();

        // 5. 获取方法对象
        Method method = cls.getMethod(methodName);

        // 6. 执行方法
        method.invoke(instance);
    }

}

使用反射创建数组

public class ReflectDemo2 {

    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> cls = Class.forName("java.lang.String");
        Object array = Array.newInstance(cls,25);

        Array.set(array, 0, "Hefery0");
        Array.set(array, 1, "Hefery1");
        Array.set(array, 2, "Hefery2");
        Array.set(array, 3, "Hefery3");

        System.out.println(Array.get(array, 3));  // Hefery3
    }

}

标题:Java基础—反射机制
作者:Hefery
地址:http://hefery.icu/articles/2022/03/15/1647282708248.html