反射初体验

本文详细介绍了Java反射机制,包括其工作原理、作用、优缺点以及主要用法。通过反射,可以在运行时动态创建对象、调用方法和访问属性,提供了代码的灵活性。同时,文章提到了反射可能导致的性能问题和封装性的破坏,并给出两种优化方案。最后,列举了反射在实际应用中的场景,如JDBC、动态工厂和配置文件驱动的类方法调用。
摘要由CSDN通过智能技术生成

引入

一般情况下,我们都是通过 new 关键字来实例化对象,这是一种正射

实例化一个 HashMap 集合:

Map<Integer, Integer> map = new HashMap<>();

当需要修改集合类型为 LinkedHashMap 时,需要修改代码:

Map<Integer, Integer> map = new LinkedHashMap<>();

每一次我们改变需求的时候,都需要修改代码,然后对代码进行编译、打包、再到 JVM 上重启项目

缺点:效率低下

优化一:动态传参,根据不同情况选择不同的数据结构

public Map<Integer, Integer> getMap(String param) {
    Map<Integer, Integer> map = null;
    if (param.equals("HashMap")) {
        map = new HashMap<>();
    } else if (param.equals("LinkedHashMap")) {
        map = new LinkedHashMap<>();
    } else if (param.equals("WeakHashMap")) {
        map = new WeakHashMap<>();
    }
    return map;
}

弊端:需要尽量考虑多的情况,当漏了某种情况时,还是需要修改代码

优化二:反射,在程序运行过程中动态获取类信息,调用类的方法,构造类实例

public Map<Integer, Integer> getMap(String className) {
    Class c = Class.forName(className);
    Consructor con = c.getConstructor();
    return (Map<Integer, Integer>) con.newInstance();
}

当需要使用的时候,传入对应结构的 全类名路径 到方法中,返回一个对应实例

总结

  • new:在编译器确定对象类型
  • 反射:在运行期确定具体的数据类型

反射机制

  1. 简述反射机制

反射机制是指在运行过程中,对于任意一个类,能获取并调用该类的任意属性和方法,获取类的构造器来构建对象。这种 动态获取程序信息 以及 动态调用对象的功能 称为反射机制(Reflection),反射是 Java 被视为 动态语言 的关键

  1. 反射机制的作用

在程序运行时,构造任意一个类的对象,判定任意一个对象所属的类,判断任意一个类所具有的成员变量和方法,调用任意一个对象的方法,生成动态代理

  1. 反射的优缺点

优点:动态加载类,动态创建对象和编译,当需求变更时,可以灵活地实例化不同对象

缺点:

  • 性能瓶颈。反射是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们的要求,这类操作总是慢于直接执行相同的操作
  • 破坏类的封装性:通过反射我们可以强制访问 private 修饰的信息

主要用法

  • 获取类的 Class
  • 获取类的实例化对象
  • 获取类的所有信息,包括:变量、方法、构造器、注解、泛型信息
  • 动态代理
  • 获取类的私有信息(存在访问权限的成员,如 protected、private)并修改其作用域

Class

概述

每一个 Java 类都有一个 Class 模板(字节码文件对象),当一个 java 类经 javac 编译后,产生一个 .class 字节码文件,其中包含了类的所有信息,如 属性、构造方法、方法 等

当 .class 被装载进 JVM 执行时,在内存中生成一个 Class 对象,它包含了该类内部的所有信息。通过该对象,我们可以在程序运行时获取类的信息

特点:

  • Class 是一个类,Class 对象只能由系统建立

  • 每个类的 Class 模板是唯一的

  • 每个类的实例都会记得自己是由哪个 Class 模板所生成

Class 是反射的根源,只有先获取 Class 对象,才能对类的信息进行动态获取、加载、运行

获取 Class

  • 类名.class:安全、最高性能
  • 实例.getClass()
  • Class.forName(className):灵活
Class c = Student.class;
Class c = new Student().getClass();
Class c = Class.forName("com.wes.pojo.Student");

基本类型的 Class

基本类型的 Class 通过其包装类来获取

Class c = Integer.TYPE;

获取父类 Class

Class parent = c.getSuperclass();

其他类型的 Class

public static void main(String[] args) {
    Class c1 = Object.class;
    Class c2 = Comparable.class;
    Class c3 = String[].class;
    Class c4 = int[][].class;
    Class c5 = Override.class;
    Class c6 = ElementType.class;
    Class c7 = Integer.class;
    Class c8 = void.class;
    Class c9 = Class.class;
}

常用方法

方法说明
getName()Class 对象所代表的实体的名称
ClassLoader getClassLoader()返回该类的类加载器

获取类名

public static void main(String[] args) throws Exception {
    Class c = Class.forName("com.yue.pojo.Student");	

    String typeName = c.getName();//获取类的全限定名
    String name = c.getSimpleName(); // 获取类名
}

创建对象

获取 Class 后,通过反射创建对象:

  • 使用 Class 对象的 newInstance 方法(调用类的无参构造器)
  • 获取构造器,调用构造器的 newInstance
    public static void main(String[] args) {

        Class<Student> c = Student.class;

        Student student = null;
        try {
            student = c.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }

        // 获取无参构造器
        Constructor<Student> c1 = c.getConstructor();
        // 调用无参构造
        student = c1.newInstance();

        // 获取有参构造器
        Constructor<Student> c2 = c.getConstructor(String.class, int.class);
        // 调用有参构造,传入对应参数类型进行赋值
        student = c2.newInstance("zhangsan", 20);
    }

获取构造器

方法说明
Constuctor[] getConstructors()获取 public 构造器
Constructor getConstructor(Class…<?> paramTypes)根据参数获取 public 构造器
Constructor[] getDeclaredConstructors()获取所有构造器
Constructor getDeclaredConstructor(class…<?> paramTypes)根据参数获取所有构造器
public static void main(String[] args) throws Exception {
    Class<Student> c = Student.class;
    
    // public
    Constructor<?>[] publicC = c.getConstructors();
    
    // 无参构造器
    Constructor<Student> c1 = c.getConstructor();
    
    // 所有构造器
    Constructor<?>[] allC = c.getDeclaredConstructors();
    
    // 指定构造器
    Constructor<Student> c2 = c.getDeclaredConstructor(String.class, int.class);

}

通过 getDeclaredConstructor 可以获取到私有的构造器,但却不能直接进行创建对象

  • 使用 setAccessible 取消访问检查
public static void main(String[] args) throws Exception {
    Class c = Class.forName("com.yue.pojo.Student");
    Constructor con = c.getDeclaredConstructor(String.class); // 获取一个私有的构造器
    c.setAccessible(true); // 取消访问检查
    Object o = c.newInstance("蓝天"); // 创建对象
}

获取变量

方法说明
Field[] getFields()获取 public 变量
Field getField(String name)根据变量名获取 public 变量
Field[] getDeclaredFields()获取所有变量,无法获取继承的变量
Field getDeclaredField(String name)根据变量名获取变量,无法获取继承的变量
public static void main(String[] args) throws Exception {
    Class<Student> c = Student.class;
    
    // 获取所有 pubilc 变量
    Field[] fs1 = c.getFields();
    Field[] fs2 = c.getDeclaredFields();
    
    // 根据变量名
    Field f1 = c.getField("address");
    Field f2 = c.getDeclaredField("address");
}

获取变量,创建对象后对变量进行动态赋值

public static void main(String[] args) throws Exception {
    Class<Student> c = Student.class;
	Constructor<Student> con = c.getConstructor();
    Student student = con.newInstance();
    
    Field field = c.getField("address");
    field.setAccessible(true);
    field.set(student, "北京");
}

获取方法

方法说明
Method[] getMethods()获取 public 方法
Method getMethod(String name, Class…<?> paramTypes)根据名字、参数获取 public 方法
Method[] getDeclaredMethods()获取所有方法,无法获取继承的方法
Method getDeclaredMethod(String name, Class…<?> paramTypes)根据名字、参数获取方法,无法获取继承的方法
public static void main(String[] args) throws Exception {
    Class<Student> c = Student.class;
    
    // 获取所有方法
    Method[] ms1 = c.getMethods();
    Method[] ms2 = c.getDeclaredMethods();
    
    // 获取单个方法
    Method m1 = c.getMethod("method");
    Method m2 = c.getDeclaredMethod("method");
}

利用 Method 中的 invoke 函数调用方法

Object invoke(Object obj, Object... args);
// obj:调用对象
// args:参数
public static void main(String[] args) throws Exception {
    Class<Student> c = Student.class;
    Student student = c.getConstructor().newInstance();	// 创建对象
    
    c.getMethod("method").invoke(student); // 调用方法
}

如果调用的是静态方法,invoke 函数的第一个参数只需要传入 null,因为静态方法不与某个对象有关,只与某个类有关

获取注解

在反射中,Field、Constructor 和 Method 类对象都可以获取标注在它们之上的注解

方法说明
Annotation[] getAnnotations()获取对象上所有注解
Annotation getAnnotation(Class annotaionClass)传入注解类型,获取对象上某个注解
Annotation[] getDeclaredAnnotations()获取对象上所有注解(显示标记的),无法获取继承的注解
Annotation getDeclaredAnnotation(Class annotationClass)传入注解类型,获取对象上某个注解(显示),无法获取继承的注解

只有注解的 @Retension 标注为 RUNTIME 时,才能够通过反射获取到该注解

定义一个抽象类、一个抽象的继承

public abstract class Pineapple {
    void getInfo();
}

public class SmallPineapple extends Pineapple {
    @Transient
    @Override
    public void getInfo() {
        
    }
}
public static void main(String[] args) throws Exception {
    Class<Student> c = Student.class;
    
    // 获取方法 getInfo
    Method method = c.getMethod("getInfo");
    // 获取方法上的注解,这里获取到 @Transient
    Annotation[] annotations = method.getAnnotations();
}

应用场景

  1. 举例说明反射的应用场景
  • 在 JDBC 中,通过反射动态加载了数据库驱动程序
  • Web 服务器利用反射调用 Servlet 的服务方法
  • Spring 利用反射注入属性并调用方法

抽象工厂

传统的工厂模式,如果需要生产新的子类,需要修改工厂类,在工厂类中增加新的分支

public class MapFactory {
    
    public Map<Object, object> produceMap(String name) {
        if ("HashMap".equals(name)) {
            return new HashMap<>();
        } else if ("TreeMap".equals(name)) {
            return new TreeMap<>();
        } // ···
    }
    
}

结合反射,工厂类不需要进行条件的判定去特定返回特定对象。在运行时,通过参数传入不同子类的全限定名获取到不同的 Class,调用 newInstance 方法返回不同的子类

例如,在运行时才确定使用哪一种 Map 结构,我们可以利用反射传入某个具体 Map 的全限定名,实例化一个特定的子类

public class MapFactory {

    public Map<Object, Object> produceMap(String className) {
        Class c = Class.forName(className);
        Map<Object, Object> map = c.newInstance();
        return map;
    }
    
}

className 可以指定为 java.util.HashMap,或者 java.util.TreeMap 等等,根据业务场景来定

JDBC

在导入第三方库时,JVM 不会主动去加载外部导入的类,而是等到真正使用时,才去加载需要的类

正是如此,我们可以在获取数据库连接时传入驱动类的全限定名,交给 JVM 加载该类

public class DBConnectionUtil {
    // 指定数据库的驱动类 
    private static final String DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";
    
    public static Connection getConnection() {
        Connection conn = null;
        // 加载驱动类
        Class.forName(DRIVER_CLASS_NAME);
        // 获取数据库连接对象
        conn = DriverManager.getConnection("jdbc:mysql://···", "root", "root");
        return conn;
    }
}

读取配置文件

通过反射,使用配置文件的内容来运行指定的类中的指定的方法

以后想要访问什么类的什么方法,只需要修改一下配置文件即可,灵活性很高

现在有一个配置文件 class.txt

className=com.yue.pojo.Student
methodName=study
public static void main(String[] args) throws Exception {
    
    // 首先要加载数据
    Properties properties = new Properties();
    FileReader fileReader = new FileReader("L:\\反射\\class.txt");
    properties.load(fileReader);
    fileReader.close();

    String className = properties.getProperty("className");
    String methodName = properties.getProperty("methodName");

    // 通过反射来使用
    Class<?> c = Class.forName(className);
    Constructor<?> constructor = c.getConstructor();
    Object o = constructor.newInstance();

    Method method = c.getMethod(methodName);
    method.invoke(o);

}

添加不同数据

向 ArrayList<Integer> 中添加一个字符串数据:通过反射获取到 List 的 add 方法,调用 invoke 添加数据

public static void main(String[] args) throws Exception{

    ArrayList<Integer> list = new ArrayList<>();
    list.add(100);

    // 通过反射拿到 add 方法,然后添加数据
    Class<? extends ArrayList> c = list.getClass();
    Method add = c.getMethod("add", Object.class);


    add.invoke(list, "hello");
    add.invoke(list, "world");

    System.out.println(list);
}

如何提升反射的性能

Spring 的反射如何实现

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值