Java的反射机制

目录

反射的基本介绍

反射的需求

反射相关的主要类

反射优点和缺点

反射调用优化


首先我们先来看看反射的基本介绍吧

反射的基本介绍

Java的发射机制(Reflection)是指在运行时动态地获取类的信息并操作对象的能力。Java的发射机制允许程序在运行时检查和操作任意一个类、方法、属性等的信息,包括了类名、方法名、属性名、参数列表以及访问修饰符等。

通过Java的发射机制,可以实现一些高级的功能,比如动态生成代理对象、动态生成类、动态配置对象等。同时,Java的一些框架也广泛应用了发射机制,比如Spring框架中大量使用了反射机制来实现依赖注入和AOP等功能。

Java中一些常用的反射API包括Class、Method、Field、Constructor等。其中,Class类是反射机制的核心,通过它可以获取一个类的所有信息。Method和Field则分别表示类中的方法和属性,Constructor则表示类中的构造方法。

Java的发射机制给Java编程带来了更大的灵活性,但同时也需要注意到发射机制的运行效率相对较低,同时也可能破坏类的封装性,因此在使用时需要谨慎考虑。

在了解反射的基本介绍之后,我们在来看看反射的需求,为什么会需要反射

先看一段代码

代码演示:

package com.reflection.question;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * 反射问题的引入
 */
@SuppressWarnings({"all"})
public class ReflectionQuestion {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        //根据配置文件 re.properties 指定信息, 创建Cat对象并调用方法hi

        //传统的方式 new 对象 -》 调用方法
//        Cat cat = new Cat();
//        cat.hi(); ===> cat.cry() 修改源码.

        //我们尝试做一做 -> 明白反射

        //1. 使用Properties 类, 可以读写配置文件    Properties的底层就是HashTable
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\re.properties"));//加载配置文件的键值对到properties对象
        String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat"  根据键获取值
        String methodName = properties.get("method").toString();//"hi"
        System.out.println("classfullpath=" + classfullpath);
        System.out.println("method=" + methodName);

        //2. 创建对象 , 传统的方法,行不通 =》 反射机制
        //new classfullpath();

        //3. 使用反射机制解决
        //(1) 加载类, 返回Class类型的对象cls
        Class cls = Class.forName(classfullpath);
        //(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
        Object o = cls.newInstance();
        System.out.println("o的运行类型=" + o.getClass()); //运行类型
        //(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi"  的方法对象
        //    即:在反射中,可以把方法视为对象(万物皆对象)
        Method method1 = cls.getMethod(methodName);
        //(4) 通过method1 调用方法: 即通过方法对象来实现调用方法
        System.out.println("=============================");
        method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象)

    }
}

反射的需求

Java反射机制的需求主要有以下几个方面:

  1. 动态加载类和调用类方法:通过反射机制,可以在运行时动态的加载一个类,并调用该类的方法。这种方式可以实现更加灵活的代码编写,避免了在编译期就要确定所有的类和方法的限制。

  2. 实现通用框架:通过反射机制,可以实现通用的框架,比如ORM(对象-关系映射)框架、依赖注入框架、AOP(面向切面编程)框架等。这些框架需要在运行时动态的获取类的信息,然后进行相应的操作。

  3. 修改类的属性和方法:通过反射机制,可以在运行时修改类的属性和方法,从而实现更加灵活的代码编写。

  4. 实现序列化和反序列化:Java中的序列化和反序列化需要用到反射机制,通过反射机制可以获取对象的属性和方法,然后将其序列化为字节流或者反序列化为对象。

  5. 动态代理:通过反射机制,可以实现动态代理,即在运行时生成一个代理对象,通过代理对象来调用目标对象的方法,从而实现AOP等功能。

那么我们使用反射机制可以完成

1.在运行时判断任意一个对象所属的类

2.在运行时构造任意一个类的对象

3.在运行时得到任意一个类所具有的成员变量和方法

4.在运行时调用任意一个对象的成员变量和方法

5.生成动态代理

反射相关的主要类

1.java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象

2.java.lang.reflect.Method: 代表类的方法, Method对象表示某个类的方法

3.java.lang.reflect.Field: 代表类的成员变量,Field对象表示某个类的成员变量

4.java.lang.reflect.Constructor: 代表类的构造方法,Constructor对象表示构造器

代码演示:

注意:反射和我们正常的使用方式有些不同,我们正常调用方法时对象名.方法名,但是在反射中是 方法名.对象名这里要注意一下

package com.reflection;

import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;

@SuppressWarnings({"all"})
public class Reflection01 {
    public static void main(String[] args) throws Exception {

        
        //1. 使用Properties 类, 可以读写配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\re.properties"));
        String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat"
        String methodName = properties.get("method").toString();//"hi"


        //2. 使用反射机制解决
        //(1) 加载类, 返回Class类型的对象cls
        Class cls = Class.forName(classfullpath);
        //(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
        Object o = cls.newInstance();
        System.out.println("o的运行类型=" + o.getClass()); //运行类型
        //(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi"  的方法对象
        //    即:在反射中,可以把方法视为对象(万物皆对象)
        Method method1 = cls.getMethod(methodName);
        //(4) 通过method1 调用方法: 即通过方法对象来实现调用方法
        System.out.println("=============================");
        method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象)

        //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()
        

        Constructor constructor2 = cls.getConstructor(String.class); //这里传入的 String.class 就是String类的Class对象
        System.out.println(constructor2);//Cat(String name)
    }
}

反射优点和缺点

优点:

  1. 动态性:Java反射机制可以在运行时动态地获取类的信息和操作对象,使程序更加灵活和易于扩展。

  2. 通用性:可以通过反射机制操作任意类型的对象,无需知道对象的具体类型。

  3. 代码复用性:反射机制可以让代码更加通用和复用,提高开发效率。

  4. AOP支持:反射机制是实现AOP(面向切面编程)的重要手段之一。

缺点:

  1. 性能较低:Java反射机制需要在运行时动态地获取对象信息和方法,需要一定的时间开销,因此性能较低。

  2. 安全问题:反射机制可以操作任意类型的对象,容易造成安全问题,需要谨慎使用,并在必要时进行安全检查。

  3. 可读性下降:由于反射机制支持动态的获取和操作对象,因此代码的可读性会下降一些,需要谨慎使用。

  4. 不利于编译器优化:由于反射机制需要在运行时动态获取对象信息和方法,因此编译器很难对代码进行优化。

代码演示;

在下面的代码中 ,我们分别测试了,使用正常的方式去调用方法,和使用反射的方式去调用方法,并且在执行方法开始之前记录了开始时间,在方法执行结束之后,记录了结束时间,最后两个相减,就可以得到方法的执行时间,我们就可以看出,使用正常的方式去调用方法,和使用反射的方式去调用方法,的区别。

package com.reflection;

import com.Cat;

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

/**
 * 测试反射调用a的性能,和优化方案
 */
public class Reflection02 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        //Field
        //Method
        //Constructor
        m1();
        m2();
        m3();
    }

    //传统方法来调用hi
    public static void m1() {

        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 90; i++) {
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("m1() 耗时=" + (end - start));
    }

    //反射机制调用方法hi
    public static void m2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        Class cls = Class.forName("com.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            hi.invoke(o);//反射调用方法
        }
        long end = System.currentTimeMillis();
        System.out.println("m2() 耗时=" + (end - start));
    }

    //反射调用优化 + 关闭访问检查

    public static void m3() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        Class cls = Class.forName("com.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        hi.setAccessible(true);//在反射调用方法时,取消访问检查
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            hi.invoke(o);//反射调用方法
        }
        long end = System.currentTimeMillis();
        System.out.println("m3() 耗时=" + (end - start));
    }
}

反射调用优化

Java中反射调用通常比直接调用方法缓慢,因为它需要进行额外的动态类型检查和查找。为了优化反射调用,可以采用以下几种方法:

  1. 缓存Method对象:通过缓存Method对象,避免动态查找,提高调用效率。

  2. 使用方法句柄(MethodHandle):方法句柄是一种更高效的调用机制,它可以绕过反射调用的开销。方法句柄比反射调用更快,特别是在热代码路径上。

  3. 使用invokedynamic指令:为了提高反射调用的性能,Java 7引入了invokedynamic指令。它允许动态绑定方法调用,避免了反射调用的开销。

  4. 使用字节码生成库:字节码生成库可以生成字节码,从而避免了反射调用的开销。比如,cglib是一个非常常用的字节码生成库。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值