ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

反射初体验

2021-09-08 13:01:24  阅读:112  来源: 互联网

标签:反射 初体验 String 获取 Class public Student class


引入

一般情况下,我们都是通过 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:在编译器确定对象类型
  • 反射:在运行期确定具体的数据类型

反射机制

Reflection:反射是 Java 被视为 动态语言 的关键

反射机制是指在运行过程中,对于任意一个类,能获取类的属性和方法,并能调用其任一可调用属性。这种动态获取信息,并动态调用方法的功能机制称为反射机制

反射机制允许我们获取一个类的属性和方法信息,然后通过获得到的信息来创建对象,调用对象的方法

主要用法:

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

优缺点

优点:实现动态创建对象和编译,当需求变更时,可以灵活地实例化不同对象,体现出很大的灵活性

缺点:

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

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();
}

应用场景

  • Spring 实例化对象
  • 反射 + 抽象工厂模式
  • JDBC

抽象工厂

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

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);
}

标签:反射,初体验,String,获取,Class,public,Student,class
来源: https://blog.csdn.net/qinuna/article/details/120177646

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有