(目录)


反射

一个需求 引出反射

需求如下:

根据配置文件re.properties中的指定信息,创建Cat对象并调用方法hi

在配置文件中代码:classfullpath=com.panyujie.reflection.Catmethod=hi

这样的需求在学习框架时非常多,通过外部文件配置,在不修改源码情况下控制程序 符合设计模式的ocp原则开闭原则不修改源码,扩容功能


代码如下:

  1. Cat 类:
public class Cat {
    private String name;
    public int age;
    public String sex;
 
    //无参构造器
    public Cat() {}
    private Cat(String name){
        this.name = name;
    }
    
    public Cat(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
 
    public void hi() {
        System.out.println("hi:" + name);
    }
    
    public void eat(String food) {
        System.out.println(name+": 吃"+food);
    }
    public int sum(int x, int y) {
        return x+y;
    }
    public void dosome(int x,float y, char z, boolean b, String... strs){
        System.out.println(name+": "+x+" "+y+" "+z+" "+b);
        System.out.println(name+": "+strs);
    }
    private void WC() {
        System.out.println(name + ": 上厕所..");
    }
 
    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}
  1. 实现需求

// 反射问题的引入
//根据 re.properties 配置文件中的信息,创建Cat对象并调用hi方法
public class ReflectionQuestion {

    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        //传统方式:new 对象 --》 调用方法 Cat().hi();

        // 1. 传统方法: 使用Properties类读写配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src/main/resources/re.properties"));
        String classfullpath = properties.get("classfullpath").toString(); //com.panyujie.reflection.Cat
        String methodName = properties.get("method").toString(); //hi
        System.out.println("classfullpath=" + classfullpath);
        System.out.println("methodName=" + methodName);

        // 2. 创建对象
        // 怎么创建对象???? 你拿到了类的全路径 你怎么 new?
        // 只能使用 反射机制
        // 2.1 加载类,返回Class类型的对象
        Class<?> cls = Class.forName(classfullpath);
        // 2.2 通过 cls对象 得到加载的类 com.panyujie.reflection.Cat 的对象
        Object o = cls.newInstance();
        // 2.3 通过 cls对象 得到加载的类 com.panyujie.reflection.Cat 的 methodName 方法对象 hi
        // 即:在反射机制中,可以把方法视为对象(万物皆对象)
        Method method1 = cls.getMethod(methodName);
        // 2.4通过 method1 调用方法:即-》通过方法对象实现调用方法
        // 反射机制:方法.invoke(对象)
        method1.invoke(o);
    }

}

效果: 调用成功,为null是因为我没赋值

image-20230305155622988

此时我们发现,我们只需要将re.properties中的 method=hi 改成 method=eat,就会调用eat(),不需要修改源码,反射机制非常强大!


反射机制

Java Reflection

  1. 反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息比如:成员变量,构造器,成员方法等),并能操作对象的属性及方法。反射在设计模式和框架的底层都会用到
  2. 加载完类之后,在堆内存中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以形象称之为:反射

反射机制 原理图

image-20230305155631610


反射机制可以完成的功能

  1. 在运行时,判断任意一个对象所属的类
  2. 在运行时,创建任意一个类的对象
  3. 在运行时,得到任意一个类所具有的成员变量和方法
  4. 在运行时,调用任意一个对象的成员变量和方法
  5. 生成 动态代理

反射相关的主要类

  • java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造方法
  • java.lang.reflect.Array:创建和访问动态数组

  1. 测试Field和Constructor:
        //java.lang.reflect.Field: 代表类的成员变量, Field对象表示某个类的成员变量
        //得到name字段
        //getField不能得到私有的属性
        Field nameField = cls.getField("age"); //
        System.out.println(nameField.get(o)); // 传统写法 对象.成员变量 , 反射 :  成员变量对象.get(对象)

        //java.lang.reflect.Constructor: 代表类的构造方法, Constructor对象表示构造器
        Constructor constructor = cls.getConstructor(); //()中可以指定构造器参数类型, 返回无参构造器
        System.out.println(constructor);//Cat()
public class Cat {

    private String name = "招财猫";
    public int age = 10; //public的

    public Cat() {} //无参构造器

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

    public void hi() { //常用方法
        //System.out.println("hi " + name);
    }
    public void cry() { //常用方法
        System.out.println(name + " 喵喵叫..");
    }

}

测试Field和Constructor得到的结果: image-20230305155641460


  1. 获得有参数的构造器
//传入 String.class 就是String类的Class对象
Constructor constructor2 = cls.getConstructor(String.class); 
System.out.println(constructor2);//Cat(String name)

image-20230305155648871


反射优点 和 缺点

  • 优点:可以动态的创建和使用对象(就是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
  • 缺点:使用反射基本是解释执行对执行速度有影响

反射调用 性能测试 及 关闭访问检测

测试代码:

普通调用、反射调用 5千万次 看耗时


/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Submerge
 * Date: 2022-10-23
 * Time: 18:10
 * 测试反射调用的性能
 */
public class Reflection2 {

    public static void main(String[] args) throws Exception {
        m1();
        m2();
    }

    // 普通调用方法
    public static void m1(){
        Cat cat = new Cat();
        long l = System.currentTimeMillis();
        for(int i=0;i<50000000;i++){
            cat.hi();
        }
        long r = System.currentTimeMillis();
        System.out.println("普通调用耗时: " + (r-l));
    }

    // 反射调用方法
    public static void m2() throws Exception {

        Class<?> cls = Class.forName("com.panyujie.reflection.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        long l = System.currentTimeMillis();
        for(int i=0;i<50000000;i++){
            hi.invoke(o);
        }
        long r = System.currentTimeMillis();
        System.out.println("反射调用耗时: " + (r-l));
    }

}

测试结果:反射对速度影响还是挺到的

image-20230305155801849


关闭访问检查,提升的也不是很高哈,还有风险

    //反射调用优化:关闭访问检测
    public static void m3() throws Exception {
        Class<?> cls = Class.forName("com.panyujie.reflection.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        //在反射调用方法时,取消访问检查
        hi.setAccessible(true);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 50000000; i++) {
            hi.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("关闭访问检测反射耗时:" + (end - start));
    }

image-20230305155816213



@