我们都知道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的对象。