[Java] Java 反射机制

1 概述:反射机制

1.1 原理

  • 反射机制是指程序在运行时可以动态地获取类的信息,并且可以调用类的方法、访问类的属性等。
  • 反射的概念是由 Smith 在 1982 年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。
  • 通俗地讲,一提到反射,我们就可以想到镜子。镜子可以明明白白地照出我是谁,还可以照出别人是谁。反映到程序中,反射就是用来让开发者知道这个类中有什么成员,以及别的类中有什么成员。
  • 在Java中,反射机制被广泛应用于框架、工具和其他一些需要动态加载和使用类的场景中。
    • 反射是一项高级开发人员应该掌握的“底层黑科技”,其实反射并不是 Java 独有的,许多编程语言都提供了反射功能。
    • 反射机制是Java语言的一个特性,它是通过反射API实现的。
    • 在Java中,每个类都有一个Class对象,它保存了该类的信息,包括类的名称、成员变量、方法和构造方法等。
    • 反射机制就是通过这个Class对象来获取类的信息,并且可以动态地调用类的方法、访问类的属性等。

1.2 为什么会有反射机制

有的同学可能会疑惑,Java 已经有了封装为什么还要有反射呢?反射看起来像是破坏了封装性
甚至让私有变量都可以被外部访问到,使得类变得不那么安全了。我们来看一下 Oracle 官方文档中对反射的描述:

Uses of Reflection
Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.
Extensibility FeaturesAn application may make use of external, user-defined classes by creating instances of extensibility objects using their fully-qualified names.
Class Browsers and Visual Development EnvironmentsA class browser needs to be able to enumerate the members of classes. Visual development environments can benefit from making use of type information available in reflection to aid the developer in writing correct code.
Debuggers and Test ToolsDebuggers need to be able to examine private members on classes. Test harnesses can make use of reflection to systematically call a discoverable set APIs defined on a class, to insure a high level of code coverage in a test suite.

从 Oracle 官方文档中可以看出,反射主要应用在以下几方面:

  • 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能。
  • 反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码。
  • 测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率。

也就是说,Oracle 希望开发者将反射作为一个工具,用来帮助程序员实现本不可能实现的功能(perform operations which would otherwise be impossible)。
正如《人月神话》一书中所言:软件工程没有银弹
很多程序架构,尤其是三方框架,无法保证自己的封装是完美的。如果没有反射,对于外部类的私有成员,我们将一筹莫展,所以我们有了反射这一后门,为程序设计提供了更大的灵活性。工具本身并没有错,关键在于如何正确地使用。

1.3 作用

  • 1、反编译:.class–>.java
  • 2、通过反射机制访问/操纵java对象的属性、方法、构造方法等;
1)在运行时判断任意一个对象所属的类。
2)在运行时判断任意一个类所具有的成员变量和方法。
3)在运行时任意调用/操纵一个对象的方法
4)在运行时构造任意一个类的对象

1.4 应用场景

哪里用到反射机制?

  • 开发通用框架:为了保持通用性,通过配置文件来加载不同的对象,调用不同的方法。
    • Spring等很多框架都用到反射机制,注入属性,调用方法。
  • JDBC中,利用反射动态加载了数据库驱动程序。
  • 动态代理:在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,而动态代理的底层技术就是反射。
  • Eclispe等开发工具利用反射动态刨析对象的类型与结构,动态提示对象的属性和方法。
  • Web服务器中利用反射调用了Sevlet的服务方法。
  • 注解:注解本身只是起到一个标记符的作用,它需要利用发射机制,根据标记符去执行特定的行为。

1.5 优缺点

  • 优点:可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。

  • 缺点:

  • 破坏封装:由于反射允许访问私有字段和私有方法,所以可能会破坏封装而导致安全问题。
  • 性能开销:由于反射涉及到动态解析,因此无法执行 Java 虚拟机优化,再加上反射的写法的确要复杂得多,所以性能要比“正射”差很多,在一些性能敏感的程序中应该避免使用反射。

对性能有影响,这类操作总是慢于直接执行java代码。

1.6 核心API

反射机制的核心API

  • java.lang.Class
    • 静态方法
      • forName(className)
    • 实例方法
      • newInstance()
  • java.lang.reflect.Constructor : 用于描述获取到的单个成员构造器信息
  • java.lang.reflect.Field : 用于描述获取到的单个成员属性信息
    • getDeclaredFields
    • getFields
    • getDeclaredField
    • getField
    • set(object, fieldValue)
    • get(object)/getBoolean/Long/Short/Char/Byte/Double/Int(object)
    • getName()
    • getType()
    • getGenericType()
    • getModifiers()
    • getAnnotatedType
  • java.lang.reflect.Method : 用于描述获取到的单个成员方法信息
    • Method getMethod(String name,Class<?>... parameterTypes)
      • 获取该Class对象表示类中名字为name、且参数为parameterTypes的指定公共成员方法
    • Method[] getMethods()
      • 用于获取该Class对象表示类中所有公共成员方法
    • Object invoke(Object obj, Object... args)
      • 使用对象obj来调用此Method对象所表示的成员方法,实参传递args
    • int getModifiers()
      • 获取方法的访问修饰符
    • Class<?> getReturnType()
      • 获取方法的返回值类型
    • String getName()
      • 获取方法的名称
    • Class<?>[] getParameterTypes()
      • 获取方法所有参数的类型
    • Class<?>[] getExceptionTypes()
      • 获取方法的异常信息
  • java.lang.reflect.Modifier
  • java.lang.reflect.Parameter

2 案例实践

  • 假定我们有这么一个被试验的类:
package myjava.lang.reflect;  
  
public class Employee {  
    private String name;  
    private Integer salary;  
    
    public static String PREFIX = "EMP";
  
    public Employee() {  
  
    }  
  
    public Employee(String name, Integer salary) {  
        this.name = name;  
        this.salary = salary;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    public Integer getSalary() {  
        return salary;  
    }  
  
    public void setSalary(Integer salary) {  
        this.salary = salary;  
    }  
  
    @Override  
    public String toString() {  
        return "Employee{" +  
                "name='" + name + '\'' +  
                ", salary=" + salary +  
                '}';  
    }  
}

2.1 获取【Class/实例对象】

import org.junit.Test;  
  
/**  
 * @description 类和对象的反射测试  
 * @reference-doc  
 *  [1] [Java反射设置类静态属性值 java实现反射的三种方式 - 51cto](https://blog.51cto.com/u_16213610/7297643)  
 */public class ClassAndInstanceReflectTest {  
    /** 获取 class **/  
    @Test  
    public void getClassTest() throws ClassNotFoundException {  
        //方式1 Class.forName  
        Class c1 = Class.forName("myjava.lang.reflect.Employee");  
        
        //方式2 java中每个类型都有 class 属性  
        Class c2 = Employee.class;  
        
        //方式3 java语言中任何一个java对象都有getClass 方法  
        Employee e = new Employee();  
        Class c3 = e.getClass(); //c3是运行时类 (e的运行时类是Employee)  
    }  
  
    /** 创建对象 **/  
    @Test  
    public void createObjectTest() throws ClassNotFoundException, InstantiationException, IllegalAccessException {  
        Class c =Class.forName("myjava.lang.reflect.Employee");  
  
        //创建此Class 对象所表示的类的一个新实例  
        Object o = c.newInstance(); //调用了Employee的无参数构造方法.  
  
        System.out.println(o.toString());//Employee{name='null', salary=null}  
    }  
}

2.2 获取/操纵【属性】

package myjava.lang.reflect;  
  
import org.junit.Test;  
  
import java.lang.reflect.Field;  
import java.lang.reflect.Modifier;  
  
/** 属性的反射测试 **/  
public class FieldReflectTest {  
    /**  
     * 获取所有属性  
     * @throws ClassNotFoundException  
     * @reference-doc  
     *  [1] {@lik myjava.lang.reflect.Employee }     */    @Test  
    public void getAllFieldsTest() throws ClassNotFoundException {  
        /** 获取整个类 **/  
        Class c = Class.forName("myjava.lang.reflect.Employee");  
        //获取所有的属性  
        //1) getFields : 只获取 public 的属性字段: PREFIX  
        //Field[] fs = c.getFields();        //2) getDeclaredFields : 获取指定类中所有声明属性的字段数组(包括public、private和protected,但不包括父类的字段): name salary PREFIX  
        Field[] fs = c.getDeclaredFields();  
  
        //定义可变长的字符串,用来存储属性  
        StringBuffer sb = new StringBuffer();  
        //通过追加的方法,将每个属性拼接到此字符串中  
        //最外边的public定义  
        String modifier = Modifier.toString(c.getModifiers());//Modifier.toString(17) = "public final"  
        sb.append(modifier + " class " + c.getSimpleName() +"{\n");  
        //里边的每一个属性  
        for(Field field:fs){  
            sb.append("\t");//空格  
            sb.append(Modifier.toString(field.getModifiers())+" ");//获得属性的修饰符,例如 "public static final" 等等  
            sb.append(field.getType().getSimpleName() + " ");//属性的类型的名字 例如 "int"            sb.append(field.getName()+";\n");//属性的名字 + 回车 例如 "SIZE"        }  
  
        sb.append("}");  
  
        System.out.println(sb);  
        /**  
         public class Employee{            private String name;            private Integer salary;            public static String PREFIX;         }         */    }  
  
    /** 获取指定的属性 **/  
    @Test  
    public void getTargetFieldTest() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {  
        Employee employee = new Employee("jane", 8970);  
  
        /** 获取整个类 **/  
        Class c = Class.forName("myjava.lang.reflect.Employee");  
        //获取所有的属性  
        Field field1 = c.getField("PREFIX");//PREFIX √(获取成功) , name X(获取失败) , getName X(获取失败)  
        //1) getField("fieldName") : 只获取 public 的属性字段: PREFIX  
        System.out.println("PREFIX : " + field1.get(employee));//"EMP"  
  
        //2) getDeclaredFields : 获取指定类中指定声明属性的字段(支持public、private和protected,但不支持父类的字段): name salary PREFIX  
        Field field2 = c.getDeclaredField("name");  
        field2.setAccessible(true);//如不将private属性设置为true,在获取属性值(get)时将报 IllegalAccessException        field2.get(employee); //从对象中获取目标字段的值  
        System.out.println("name : " + field1.get(employee));//"name"  
        field2.set(employee, "jack");//设置目标字段的值  
        System.out.println(String.format("%s(type:%s) = %s" , field2.getName(), field2.getType().toString(), field2.get(employee)));//"name(type:class java.lang.String) = jack"  
    }  
}

2.3 获取/操纵【方法】

package myjava.lang.reflect;  
  
import org.junit.Test;  
  
import java.lang.reflect.Constructor;  
import java.lang.reflect.InvocationTargetException;  
import java.lang.reflect.Method;  
import java.lang.reflect.Parameter;  
  
public class MethodReflectTest {  
    @Test  
    public void aMethodTest() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {  
        Employee employee = new Employee("jane", 83500);  
  
        Class clazz = employee.getClass();  
        // 根据Class对象获取对应的有参构造方法  
        Constructor constructor = clazz.getConstructor(String.class, Integer.class);  
  
        // 使用有参构造方法构造对象,并记录  
        Object employee2 = constructor.newInstance("xiaoping.deng", 30000);  
  
        Method method = clazz.getMethod("getName");  
        //clazz.getMethods() / clazz.getDeclaredMethods()  
        //clazz.getDeclaredMethod(methodName) / clazz.getEnclosingMethod()  
        Object value = method.invoke(employee2);//Object invoke(Object object, Object... args)  
        System.out.println("value : " + value);//xiaoping.deng  
    }  
  
    @Test  
    public void aMethodTest2() throws NoSuchMethodException {  
        Employee employee = new Employee("jane", 83500);  
  
        Class clazz = employee.getClass();  
  
        //Method [] methods = clazz.getMethods();  
        Method method = clazz.getDeclaredMethod("setName", String.class);//查找 方法名为 setName,且入参类型为 String 的方法,查找失败时会报 NoSuchMethodException  
        Class<?> [] parameterTypes = method.getParameterTypes();;  
        Parameter [] parameters = method.getParameters();  
        for(int i=0, size = method.getParameterCount();i<size; i++){  
            Class<?> parameterType = parameterTypes[i];  
            Parameter parameter = parameters[i];  
            //method=setName | parameterType : java.lang.String | parameter # name=name ,type=class java.lang.String, index : 0  
            System.out.println(String.format("method=%s | parameterType : %s | parameter # name=%s ,type=%s, index : %s"  
                , method.getName(), parameterType.getCanonicalName(), parameter.getName(), parameter.getType().toString(), i)  
            );  
        }  
  
        Class<?> returnType = method.getReturnType();  
        System.out.println("returnType : " + returnType.getCanonicalName());//returnType : void  
    }  
}

X 参考文献

posted @ 2024-01-03 13:00  千千寰宇  阅读(54)  评论(0编辑  收藏  举报