你真的了解反射吗?

·  阅读 12000
你真的了解反射吗?

1. 啥是反射

1.初识反射

刚开始学反射的时候,我是一脸懵逼的,这玩意真的是“抽象的妈妈给抽象开门-抽象到家了。”

为什么创建对象要先获取 Class 对象?这不多此一举吗?我直接 new 一下不是更简单吗?

什么是程序运行时获取类的属性和方法?平时都是程序编译出错了再修改代码,我为什么要考虑程序运行时的状态?

我平时开发也用不到,学这玩意有啥用?

后来学了注解、spring、SpringMVC 等技术之后,发现反射无处不在。

2.JVM 加载类

我们写的 java 程序要放到 JVM 中运行,所以要学习反射,首先需要了解 JVM 加载类的过程。

1.我们写的 .java 文件叫做源代码。

2.我们在一个类中写了一个 main 方法,然后点击 IDEA 的 run 按钮,JVM 运行时会触发 jdk 的 javac 指令将源代码编译成 .class 文件,这个文件又叫做字节码文件。

3.JVM 的类加载器(你可以理解成一个工具)通过一个类的全限定名来获取该类的二进制字节流,然后将该 class 文件加载到 JVM 的方法区中。

4.类加载器加载一个 .class 文件到方法区的同时会在堆中生成一个唯一的 Class 对象,这个 Class 包含这个类的成员变量、构造方法以及成员方法。

5.这个 Class 对象会创建与该类对应的对象实例。

所以表面上你 new 了一个对象,实际上当 JVM 运行程序的时候,真正帮你创建对象的是该类的 Class 对象。

也就是说反射其实就是 JVM 在运行程序的时候将你创建的所有类都封装成唯一一个 Class 对象。这个 Class 对象包含属性、构造方法和成员方法。你拿到了 Class 对象,也就能获取这三个东西。

你拿到了反射之后(Class)的属性,就能获取对象的属性名、属性类别、属性值,也能给属性设置值。

你拿到了反射之后(Class)的构造方法,就能创建对象。

你拿到了反射之后(Class)的成员方法,就能执行该方法。

3.反射的概念

JAVA 反射机制是在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制。

知道了 JVM 加载类的过程,相信你应该更加深入的了解了反射的概念。

反射:JVM 运行程序 --> .java 文件 --> .class 文件 --> Class 对象 --> 创建对象实例并操作该实例的属性和方法

接下来我就讲一下反射中的相关类以及常用方法。

2. Class 对象

获取 Class 对象

先建一个 User 类:

public class User {
    private  String name = "知否君";
    public String sex = "男";

    public User() {
    }

    public User(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public void eat(){
        System.out.println("人要吃饭!");
    }

    private void run(){
        System.out.println("人要跑步!");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}
复制代码

获取 Class 对象的三种方式:

1. Class.forName("全类名")

全类名:包名+类名

Class userClass = Class.forName("com.xxl.model.User");
复制代码

2. 类名.class

Class userClass = User.class;
复制代码

3. 对象.getClass()

User user = new User();
Class userClass = user.getClass();
复制代码

尽管有三种方式获取 Class 对象,但是我们一般采用上述第一种方式。

拿到 Class 对象之后,我们就可以操作与它相关的方法了。

3. 获取类名

1.获取完整类名:包名+类名

getName()

Class userClass = Class.forName("com.xxl.model.User");
String name = userClass.getName();
System.out.println(name);
复制代码

打印结果:

2.获取简单类名:不包括包名

getSimpleName()

Class userClass = Class.forName("com.xxl.model.User");
String simpleName = userClass.getSimpleName();
System.out.println(simpleName);
复制代码

打印结果:

4. 属性

4.1 获取属性

1.获取所有公有属性:public 修饰

getFields()

Class userClass = Class.forName("com.xxl.model.User");
Field[] fields = userClass.getFields();
for (Field field : fields) {
    System.out.println(field);
}
复制代码

打印结果:

2.获取单个公有属性

getField("属性名")

Class userClass = Class.forName("com.xxl.model.User");
Field field = userClass.getField("sex");
System.out.println(field);
复制代码

打印结果:

3.获取所有属性:公有+私有

getDeclaredFields()

Class userClass = Class.forName("com.xxl.model.User");
Field[] fields = userClass.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field);
}
复制代码

打印结果:

4.获取单个属性:公有或者私有

getDeclaredField("属性名")

Class userClass = Class.forName("com.xxl.model.User");
Field nameField = userClass.getDeclaredField("name");
Field sexField = userClass.getDeclaredField("sex");
System.out.println(nameField);
System.out.println(sexField);
复制代码

打印结果:

4.2 操作属性

1.获取属性名称

getName()

Class userClass = Class.forName("com.xxl.model.User");
Field nameField = userClass.getDeclaredField("name");
System.out.println(nameField.getName());
复制代码

打印结果:

2.获取属性类型

getType()

Class userClass = Class.forName("com.xxl.model.User");
Field nameField = userClass.getDeclaredField("name");
System.out.println(nameField.getType());
复制代码

打印结果:

3.获取属性值

get(object)

Class userClass = Class.forName("com.xxl.model.User");
Field nameField = userClass.getDeclaredField("sex");
User user = new User();
System.out.println(nameField.get(user));
复制代码

打印结果:

注: 通过反射不能直接获取私有属性的值,但是可以通过修改访问入口来获取私有属性的值。

设置允许访问私有属性:

field.setAccessible(true);
复制代码

例如:

Class userClass = Class.forName("com.xxl.model.User");
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true);
User user = new User();
System.out.println(nameField.get(user));
复制代码

打印方法:

4.设置属性值

set(object,"属性值")

Class userClass = Class.forName("com.xxl.model.User");
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true);
User user = new User();
nameField.set(user,"张无忌");
System.out.println(nameField.get(user));
复制代码

打印结果:

5. 构造方法

1.获取所有公有构造方法

getConstructors()

Class userClass = Class.forName("com.xxl.model.User");
Constructor[] constructors = userClass.getConstructors();
for (Constructor constructor : constructors) {
    System.out.println(constructor);
}
复制代码

打印结果:

2.获取与参数类型匹配的构造方法

getConstructor(参数类型)

Class userClass = Class.forName("com.xxl.model.User");
Constructor constructor = userClass.getConstructor(String.class, String.class);
System.out.println(constructor);
复制代码

打印结果:

6. 成员方法

6.1获取成员方法

1.获取所有公共方法

getMethods()

Class userClass = Class.forName("com.xxl.model.User");
Method[] methods = userClass.getMethods();
for (Method method : methods) {
    System.out.println(method);
}
复制代码

打印结果:

我们发现,打印结果除了自定义的公共方法,还有继承自 Object 类的公共方法。

2.获取某个公共方法

getMethod("方法名", 参数类型)

Class userClass = Class.forName("com.xxl.model.User");
Method method = userClass.getMethod("setName", String.class);
System.out.println(method);
复制代码

打印结果:

3.获取所有方法:公有+私有

getDeclaredMethods()

Class userClass = Class.forName("com.xxl.model.User");
Method[] declaredMethods = userClass.getDeclaredMethods();
for (Method method : declaredMethods) {
    System.out.println(method);
}
复制代码

打印结果:

4.获取某个方法:公有或者私有

getDeclaredMethod("方法名", 参数类型)

Class userClass = Class.forName("com.xxl.model.User");
Method method = userClass.getDeclaredMethod("run");
System.out.println(method);
复制代码

打印结果:

6.2 执行成员方法

invoke(object,"方法参数")

Class userClass = Class.forName("com.xxl.model.User");
Method method = userClass.getDeclaredMethod("eat");
User user = new User();
method.invoke(user);
复制代码

打印结果:

注: 通过反射不能直接执行私有成员方法,但是可以设置允许访问。

设置允许执行私有方法:

method.setAccessible(true);
复制代码

7. 注解

1.判断类上或者方法上时候包含某个注解

isAnnotationPresent(注解名.class)
复制代码

例如:

Class userClass = Class.forName("com.xxl.model.User");
if(userClass.isAnnotationPresent(Component.class)){
    Component annotation = (Component)userClass.getAnnotation(Component.class);
    String value = annotation.value();
    System.out.println(value);
};
复制代码

2.获取注解

getAnnotation(注解名.class)
复制代码

例如:

Class userClass = Class.forName("com.xxl.model.User");
// 获取类上的注解
Annotation annotation1 = userClass.getAnnotation(Component.class);
Method method = userClass.getMethod("eat");
// 获取方法上的某个注解
Annotation annotation2 = userClass.getAnnotation(Component.class);
复制代码

8. 创建类的实例

1.通过 Class 实例化对象

Class.newInstance()

Class userClass = Class.forName("com.xxl.model.User");
User user = (User)userClass.newInstance();
System.out.println("姓名:"+user.getName()+" 性别:"+user.getSex());
复制代码

打印结果:

2.通过构造方法实例化对象

constructor.newInstance(参数值)

Class userClass = Class.forName("com.xxl.model.User");
Constructor constructor = userClass.getConstructor(String.class, String.class);
User user = (User)constructor.newInstance\("李诗情", "女"\);
System.out.println("姓名:"+user.getName()+" 性别:"+user.getSex());
复制代码

打印结果:

9. 反射案例

有一天技术总监对张三说:"张三,听说你最近学反射了呀。那你设计一个对象的工厂类给我看看。"

张三心想:"哟,快过年了,领导这是要给我涨工资啊。这次我一定好好表现一次。"

5分钟过后,张三提交了代码:

public class ObjectFactory {
    
    public static User getUser() {
        
        User user = null;
        try {
            Class userClass = Class.forName("com.xxl.model.User");
            user = (User) userClass.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return user;
    }

    public static UserService getUserService() {
        UserService userService = null;
        try {
            Class userClass = Class.forName("com.xxl.service.impl.UserServiceImpl");
            userService = (UserService) userClass.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return userService;
    }
}

复制代码

技术总监瞄了一眼代码,对张三说:"你这个工厂类存在两个问题。"

1.代码存在大量冗余。如果有一万个类,你是不是要写一万个静态方法?

2.代码耦合度太高。如果这些类存放的包路径发生改变,你再用 forName()获取 Class 对象是不是就会有问题?你还要一个个手动改代码,然后再编译、打包、部署。。你不觉得麻烦吗?

“发散你的思维想一下,能不能只设计一个静态类,通过传参的方式用反射创建对象,传递的参数要降低和工厂类的耦合度。顺便提醒你一下,可以参考一下 JDBC 获取数据库连接参数的方式。”

张三一听:"不愧是总监啊,醍醐灌顶啊!等我 10 分钟。"

10 分钟后,张三再次提交了代码:

object.properties

user=com.xxl.model.User
userService=com.xxl.service.impl.UserServiceImpl
复制代码

ObjectFactory

public class ObjectFactory {

    private static Properties objectProperty = new Properties();

    // 静态方法在类初始化时执行,且只执行一次
    static{
        try {
            InputStream inputStream = ObjectFactory.class.getResourceAsStream("/object.properties");
            objectProperty.load(inputStream);
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static Object getObject(String key){
        Object object = null;
        try {
            Class objectClass = Class.forName(objectProperty.getProperty(key));
            object = objectClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return object;
    }
}
复制代码

测试方法:

@Test
  void testObject() {
      User user = (User)ObjectFactory.getObject("user");
      UserService userService = (UserService)ObjectFactory.getObject("userService");
      System.out.println(user);
      System.out.println(userService);
  }
复制代码

执行结果:

总监看后连连点头,笑着对张三说:“用 properties 文件存放类的全限定名降低了代码的耦合度,通过传参的方式使用反射创建对象又降低了代码的冗余性,这次改的可以。"

"好啦,今晚项目要上线,先吃饭去吧,一会还要改 bug。”

张三:"..........好的总监。"

10. 反射的作用

我们或多或少都听说过设计框架的时候会用到反射,例如 Spring 的 IOC 就用到了工厂模式和反射来创建对象,BeanUtils 的底层也是使用反射来拷贝属性。所以反射无处不在。

尽管我们日常开发几乎用不到反射,但是我们必须要搞懂反射的原理,因为它能帮我们理解框架设计的原理。

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改