乐趣区

面试说说Java反射中获取Class对象三种方式的区别


我们都知道 Java 的反射机制中有三种获取类对象的方式,那么这几种方式的区别到底是什么呢?
1、new Object().getClass 2、Object.class 3、Class.forName(“java.util.String”)


测试场景一

因为类对象与类的加载息息相关,所以为了展示区别,我们先加入动态的和静态的代码块。

        public class CalBean {
            static {System.out.println("静态的代码块。");
            }
            {System.out.println("动态的代码块。");
            }
            public CalBean() {System.out.println("构造方法");
            }
            private Integer num1;
            private Integer num2;
            public Integer getNum1() {return num1;}
            public void setNum1(Integer num1) {this.num1 = num1;}
            public Integer getNum2() {return num2;}
            public void setNum2(Integer num2) {this.num2 = num2;}
        }

创建一个测试单元:

    @SuppressWarnings("unused")
    @org.junit.Test
    public void test() {
         Class<CalBean> cla1 = CalBean.class;
         System.out.println("===================================");
         try {Class<?> cla2 =Class.forName("com.zking.entity.CalBean");
         } catch (ClassNotFoundException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();}
         System.out.println("===================================");
         Class<? extends CalBean> cla3 = new CalBean().getClass();
    }

控制台结果:



结果可以看到,类名.class 的方式并未执行该类的代码块或者是代码。
而 forName 的方式执行了该类的静态代码块。
实例对象.getClass() 的方式打印所有内容的原因显而易见,因为要先实例化。

测试场景二

我们将.class 方式与实例对象.getClass() 组合测试:

        @SuppressWarnings("unused")
        @org.junit.Test
        public void test() {
             Class<CalBean> cla1 = CalBean.class;
             System.out.println("===================================");
             Class<? extends CalBean> cla3 = new CalBean().getClass();
        }

控制台结果:



可以看到.class 方式仍然没有输出,实例对象.getClass() 方式将所有的内容打印。

测试场景三

这里,我们测试一下三种方式获取到的类对象是否相等:

    @SuppressWarnings("unused")
    @org.junit.Test
    public void test() {
        try {
             Class<CalBean> cla1 = CalBean.class;
             System.out.println("===================================");
             Class<?> cla2 =Class.forName("com.zking.entity.CalBean");
             System.out.println("===================================");
             Class<? extends CalBean> cla3 = new CalBean().getClass();
             
             System.out.println(cla1 == cla2);
             System.out.println(cla2 == cla3);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();}
    }

等于号判断引用地址:


三种方式获取到的是同一个对象,为什么呢?
这就涉及到类的加载过程,我们知道类加载过程分:加载阶段、连接阶段和初始化阶段。
类的加载阶段是将 class 文件中的二进制数据读取到内存中,然后将该字节流所代表的静态存储结构转化为方法区中运行时的数据结构,并且在堆内存中生成一个该类的 java.lang.class 对象,作为方法区数据结构的入口。
类加载阶段的最终产物是堆内存中的 class 对象,对于同一个 Classloader 对象,不管某个类被加载多少次,对应堆内存中的 class 对象始终只有一个。
也就是说无论通过哪种形式来获取 Class 对象,获得的都是堆内存中对应的 Class 对象。


总结三种方式

  1. 类名.class:JVM 将使用类装载器,将类装入内存 (前提是: 类还没有装入内存),不做类的初始化工作,返回 Class 的对象。
  2. Class.forName(“ 类名字符串 ”):装入类,并做类的静态初始化,返回 Class 的对象。
  3. 实例对象.getClass():对类进行静态初始化、非静态初始化;返回引用运行时真正所指的对象所属的类的 Class 的对象。
退出移动版