Java--反射机制原理几种Class获取方式及应用场景

Posted 吾日三省贾斯汀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java--反射机制原理几种Class获取方式及应用场景相关的知识,希望对你有一定的参考价值。

目录

📢学习背景

学习Java的小伙伴,可能听过Java反射机制,但是熟悉又有点陌生,本文主要是通过思考面试中经常被问到的几个Java反射机制的问题,再通过理论知识结合代码实例应用场景进行讲解,加深自己对Java反射机制的认知和理解,也希望能帮助到有需要的小伙伴~

🎹一、Java反射机制是什么?

🎸1.1 反射原理

(1)Java反射机制(Java Reflection)是Java语言中一种动态(运行时)访问、检测 & 修改它本身的能力,主要作用是动态(运行时)获取类的完整结构信息 & 调用对象的方法~
更简单点的说就是Java程序在运行时(动态)通过创建一个类的反射对象,再对类进行相关操作,比如:

  • 获取该对象的成员变量 & 赋值
  • 调用该对象的方法(含构造方法,有参/无参)
  • 判断该对象所属的类

PS:不过说实话,直接看比较官方的定义还是有点难理解,再来更加通俗点的说吧~

(2)一般情况下,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过new实例化创建对象,然后使用这个对象对类进行操作,这个就属于正射~

(3)而反射则是一开始并不知道要初始化的是什么类,无法使用new来实例化创建对象,主要是通过JDK提供的反射API来实现,在运行时才知道要操作的是什么类,并且可以获取到类的完整构造以及调用对应的方法,这就是反射~

📣1.2 反射例子

代码如下:

package com.justin.java.lang;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * @program: Jdk1.8 Test
 * @description: 正射、反射简单调用示例
 * @author: JustinQin
 * @create: 2021/8/22 13:23
 * @version: v1.0.0
 **/
public class Student 
    private int id;

    public void setId(int id) 
        this.id = id;
    
    public int getId() 
        return id;
    

    public static void main(String[] args) throws Exception
        //一、正射调用过程
        Student student = new Student();
        student.setId(1);
        System.out.println("正射调用过程Student id:" + student.getId());

        //二、反射调用过程
        Class clz = Class.forName("com.justin.java.lang.Student");
        Constructor studentConstructor = clz.getConstructor();
        Object studentObj = studentConstructor.newInstance();
        
        Method setIdMethod = clz.getMethod("setId", int.class);
        setIdMethod.invoke(studentObj, 2);
        Method getIdMethod = clz.getMethod("getId");
        System.out.println("正射调用过程Student id:" + getIdMethod.invoke(studentObj));
    


输出结果:

正射调用过程Student id:1
反射调用过程Student id:2

上述例子反射的调用过程,可以看到获取一个类的反射对象,主要过程为:

  • 获取类的Class实例对象
  • 根据Class实例对象获取Constructor对象
  • 再根据Constructor对象的newInstance方法获取到类的反射对象

获取到类的反射对象后,就可以对类进行操作了~ 例如,上述示例中对类的方法进行调用过程为:

  • 根据Class实例对象获取到类的Method对象
  • 再根据Method对象的invoke方法调用到具体类的方法

前面一点也提到了获取到类的Class实例对象,上面示例反向调用过程中我们是通过Class.forName("类的全局定名")这种方式来获取到类的Class实例对象,除了这种,常用的还有其他两种,往下讲解~

🎵二、Java反射机制中获取Class的三种方式及区别?

📀2.1 Class的几种获取方式

(1)获取类的java.lang.Class实例对象,常见的三种方式分别为:

  • 通过MyClass.class获取,这里的MyClass指具体类~~
  • 通过Class.forName("类的全局定名")获取,全局定名为包名+类名
  • 通过new MyClass().getClass()获取,这里的MyClass指具体类~

(2)通过MyClass.class获取,JVM会使用ClassLoader类加载器将类加载到内存中,但并不会做任何类的初始化工作,返回java.lang.Class对象

(3)通过Class.forName("类的全局定名")获取,同样,类会被JVM加载到内存中,并且会进行类的静态初始化工作,返回java.lang.Class对象

(4)通过new MyClass().getClass()获取,这种方式使用了new进行实例化操作,因此静态初始化和非静态初始化工作都会进行getClass方法属于顶级Object类中的方法,任何子类对象都可以调用,哪个子类调用,就返回那个子类的java.lang.Class对象

PS: 这3种方式,最终在JVM堆区对应类的java.lang.Class对象都属于同一个,也就是内存地址相同,进行==双等号比较结果为true,原因是JVM类加载过程中使用的是同一个ClassLoader类加载器加载某个类,不论加载多少次,生成到堆区的java.lang.Class对象始终只有一个,除非自定义类加载器,破坏JVM的双亲委派机制,使得同一个类被不同类加载器加载,JVM才会把它当做两个不同的java.lang.Class对象

🔊2.2 代码演示几种方式的区别

创建一个实体类,分别在实体类中创建类的静态代码块动态代码块有参构造方法无参构造方法,方便测试几种方式的区别及内存地址是否相同~

(1)实体类:

public class MyClass 
    private static final String staticStr = "Hi";
    private static int staticInt = 2021;
    private String id;

    static 
        System.out.println("静态代码块:staticStr=" + staticStr + ",staticInt=" + staticInt);
    

    
        System.out.println("动态代码块~");
    

    public MyClass() 
        System.out.println("无参构造方法~");
    

    public MyClass(String id) 
        System.out.println("有参构造方法~");
        this.id = id;
    

    public String getId() 
        return id;
    

    public void setId(String id) 
        this.id = id;
    

    @Override
    public String toString() 
        return "MyClass" +
                "id='" + id + '\\'' +
                '';
    

(2)单元测试类:
通过@Test注解对三种方式分别进行单元测试,再对这三种方式的组合进行单元测试~

package com.justin.java.lang;

import org.junit.Test;

/**
 * @program: Jdk1.8Test
 * @description: Java反射机制中获取类的Class实例对象的常见三种方式及区别对比
 * @author: JustinQin
 * @create: 2021/8/22 15:04
 * @version: v1.0.0
 **/
public class MyClassTest 

    @Test
    public void test1() 
        System.out.println("一、MyClass.class方式=========");
        Class<?> class1 = MyClass.class;
    

    @Test
    public void test2() throws ClassNotFoundException 
        System.out.println("二、Class.forName方式=========");
        Class class2 = Class.forName("com.justin.java.lang.MyClass");
    


    @Test
    public void test3() 
        System.out.println("三、new MyClass().getClass方式=========");
        Class class3 = new MyClass().getClass();
    

    @Test
    public void test12() throws ClassNotFoundException 
        System.out.println("一、MyClass.class方式=========");
        Class<?> class1 = MyClass.class;
        System.out.println("二、Class.forName方式=========");
        Class class2 = Class.forName("com.justin.java.lang.MyClass");
    

    @Test
    public void test13() 
        System.out.println("一、MyClass.class方式=========");
        Class<?> class1 = MyClass.class;
        System.out.println("三、new MyClass().getClass方式=========");
        Class class3 = new MyClass().getClass();
    

    @Test
    public void test23() throws ClassNotFoundException 
        System.out.println("二、Class.forName方式=========");
        Class class2 = Class.forName("com.justin.java.lang.MyClass");
        System.out.println("三、new MyClass().getClass方式=========");
        Class class3 = new MyClass().getClass();
    

    @Test
    public void test() throws ClassNotFoundException 
        System.out.println("四、三种方式内存地址比较=========");
        Class<?> class1 = MyClass.class;
        Class class2 = Class.forName("com.justin.java.lang.MyClass");
        Class class3 = new MyClass().getClass();
        System.out.println("比较结果=========");
        System.out.println("MyClass.class和Class.forName内存地址比较是否相同:" + (class1 == class2));
        System.out.println("MyClass.class和new MyClass().getClass内存地址比较是否相同:" + (class1 == class3));
        System.out.println("Class.forName和new MyClass().getClass内存地址比较是否相同:" + (class2 == class3));
    


逐个执行单元,得出测试结果为:

* test1()方法
一、MyClass.class方式=========

*  test2()方法
二、Class.forName方式=========
静态代码块:staticStr=Hi,staticInt=2021

*  test3()方法
三、new MyClass().getClass方式=========
静态代码块:staticStr=Hi,staticInt=2021
动态代码块~
无参构造方法~

*  test12()方法
一、MyClass.class方式=========
二、Class.forName方式=========
静态代码块:staticStr=Hi,staticInt=2021

*  test13()方法
一、MyClass.class方式=========
三、new MyClass().getClass方式=========
静态代码块:staticStr=Hi,staticInt=2021
动态代码块~
无参构造方法~

*  test23()方法
二、Class.forName方式=========
静态代码块:staticStr=Hi,staticInt=2021
三、new MyClass().getClass方式=========
动态代码块~
无参构造方法~

*  test()方法
四、三种方式内存地址比较=========
静态代码块:staticStr=Hi,staticInt=2021
动态代码块~
无参构造方法~
比较结果=========
MyClass.classClass.forName内存地址比较是否相同:true
MyClass.classnew MyClass().getClass内存地址比较是否相同:true
Class.forName和new MyClass().getClass内存地址比较是否相同:true

通过test1test2test3的测试结果验证了2.1 三种方式及区别中黄色标记部分的区别说明,即:

  • MyClass.class不会做任何类的初始化工作
  • Class.forName会进行类的静态初始化工作
  • new MyClass().getClass静态初始化和非静态初始化工作都会进行
  • 使用这三种方式任意一种最终在JVM加载到内存中都会是内存地址相同

test23组合得到的测试结果,说明静态代码块只会被加载一次~

讲了这么多,除了知道基本原理和基本使用之外,更重要的还是要知道它的一些比较实际的应用场景,往下介绍~

💥三、Java反射机制的应用场景有哪些?

🎶3.1 应用场景

  • 工厂模式中的简单工厂模式优化
  • 代理模式中的动态代理方式实现
  • Java JDBC数据库操作

🎧3.2 简单工厂模式优化

📢3.2.1 什么是简单工厂模式?

Java中主要有23种设计模式,其中工厂模式就是其中一种,而简单工厂模式,顾名思义,也是属于工厂模式中的一种,只不过比较简单。简单工厂模式也可以叫做静态方法模式(因为工厂类一般都是在内部定义了一个静态方法)。
从现实生活角度来理解的话,工厂是专门负责生产产品的,同样在设计模式中,简单工厂模式我们可以理解为专门负责生产对象的一个类,称为“工厂类”。

🎹3.2.2 简单工厂模式有什么用?

简单工厂模式通过创建一个对应的工厂类,将类实例化的操作使用对象的操作进行分开,让使用者不用知道具体参数就可以实例化出所需要的具体产品类,从而避免了在客户端代码中显式指定,实现了解耦。即使用者可直接消费产品而不需要知道其生产的细节~

🎸3.2.3 如何实现简单工程模式?

实现简单工程模式的核心是创建一个工厂类,并且在内部定义了一个静态方法,传入不同的参数标识通过switch进行分组,通过new实例化创建不同的子类对象返回~

实现例子:

步骤1:创建抽象产品类

public interface Product 
    public abstract void show();

步骤2:创建具体产品类:

public class ProductA implements Product 
    @Override
    public void show() 
        System.out.println("生产了产品A");
    

public class ProductB implements Product 
    @Override
    public void show() 
        System.out.println("生产了产品B");
    


public class ProductC implements Product 
    @Override
    public void show() 
        System.out.println("生产了产品C");
    

步骤3:创建简单工厂类

public class SimpleFactory 
    /**
     * 实现简单工厂模式
     * @param pName 产品标识
     * @return 返回具体的产品
     */
    public static Product createProduct(String pName)
        switch (pName)
            case "A":
                return new ProductA();
            case "B":
                return new ProductB();
            case "C":
                return new ProductC();
            default:
                return null;
        
    

步骤4:调用简单工厂类

public class SimpleFactoryTest 
    public static void main(String[] args) 
        try 
            SimpleFactory.createProduct("A").show();
         catch (NullPointerException e) 
            System.out.println("没有A这款产品,无法生产~");
        
        try 
            SimpleFactory.createProduct("B").show();
         catch (NullPointerException e) 
            System.out.println("没有B这款产品,无法生产~");
        
        try 
            SimpleFactory.createProduct("C").show();
         catch (NullPointerException e) 
            System.out.println("没有C这款产品,无法生产~");
        
        try 
            SimpleFactory.createProduct("D").show();
         catch (NullPointerException e) 
            System.out.println("没有D这款产品,无法生产~");
        
    

📣3.2.4 简单工厂模式优化

(1)简单工厂模式弊端

  • 操作成本高:每增加一个接口的子类,必须修改工厂类的逻辑
  • 系统复杂性提高:每增加一个接口的子类,都必须向工厂类添加逻辑

这两点弊端从前面的例子SimpleFactory工厂类的实现,可以看出简单工厂模式中对工厂类SimpleFactory的维护成本有点大,因为实际中可能会很频繁的去更新具体产品类,每一次变更都需要去修改工厂类,此时就可以利用Java反射机制对简单工厂模式进行优化~

(2)简单工厂模式的优化思路
采用Java反射机制,通过传入子类全局定名(包名+类名) 动态的创建不同的子类对象实例,从而使得在不增加产品接口子类和修改工厂类的逻辑的情况下还能实现了工厂类对子类实例对象的统一创建~

(3)简单工厂模式的优化步骤
步骤1:创建工厂类
采用Java反射机制对工厂类进行优化,主要是将className子类全局定名(包名+类名)作为入参,通过Class.forName方式获取类的java.lang.Class实例对象,再通过Class实例对象的getInstance方法获取到具体子类的实例对象~

public class Factory 
    public static Product getInstance(String className) 
        Product realProduct = null;
        try 
            Class pClass = Class.forName(className);
            realProduct = (Product) pClass.newInstance();
         catch (Exception e) 
            e.printStackTrace();
        
        return realProduct;
    

步骤2:调用工厂类

public class FactoryTest 
    public static void main(String[] args) 
        try 
            Product productA = Factory.getInstance("com.justin.java.lang.ProductA");
            productA.show();
         catch (NullPointerException e) 
            System.out.println("没有A这款产品,无法生产~"以上是关于Java--反射机制原理几种Class获取方式及应用场景的主要内容,如果未能解决你的问题,请参考以下文章

java利器------反射机制

强哥说Java--反射

请问java中的反射机制与用法

Java讲课笔记36:初探反射机制

第三课:JAVA反射机制

java 27 - 2 反射之 反射的概述以及获取Class文件对象的方式