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

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

在上篇文章《一篇文章全面了解Java反射机制》中我们学习了Java反射机制的基本使用,留心的朋友可能已经注意到了,在文中提到了三种获取Class对象的方法。

如果面试中涉及到Java反射,那么遇到该面试题的概率将大大增加。

以下三种获取Class对象的方式有什么不同?

1、new Object().getClass 2、Object.class 3、 Class.forName("java.util.String")

本篇文章就通过实例带大家来了解一下这三种获取Class对象的区别。示例基于JDK8。

实例演示场景一

为了更好的演示,我们先创建一个对象Person,对象内部定义了一些静态的方法。















public class Person {
  static {    System.out.println("Person:静态代码块");  }
  {    System.out.println("Person:动态代码块");  }
  public Person(){    System.out.println("Person:构造方法");  }}

在Person对象中分别定义了:静态代码块、动态代码块、构造方法。

针对上面的实例,我们构建了三个单元测试的场景,对应代码如下:


















public class GetClassTest {
  @Test  public void test1(){    Class<?> clz = Person.class;  }
  @Test  public void test2() throws ClassNotFoundException {    Class<?> clz = Class.forName("com.choupangxia.reflect.Person");  }
  @Test  public void test3() {    Class<?> clz = new Person().getClass();  }}

分别执行三个单元测试发现,第一个单元测试没打印任何内容;第二个单元测试打印了“静态方法”中的内容;第三个单元测试打印出了全部内容:

Person:静态代码块
Person:动态代码块
Person:构造方法

也就是说通过Person.class的方法获取对象的Class对象,根本不会调用对象中任何的代码块或代码。而Class.forName()会调用静态代码块的内容。

而第三种方式打印所有内容的原因很显然,就因为要先实例化对象。

实例演示场景二

下面再组合一下这三种方式,看看一些其他的效果。

首先,依次调用三个获取Class对象的方法:









@Testpublic void test4() throws ClassNotFoundException {  Class<?> clz = Person.class;  System.out.println("---------------");  clz = Class.forName("com.choupangxia.reflect.Person");  System.out.println("---------------");  clz = new Person().getClass();}

test4打印日志如下:

---------------
Person:静态代码块
---------------
Person:动态代码块
Person:构造方法

通过日志说明,Class.forName()方法执行过静态代码块之后,new Person().getClass()就不再会执行同样的静态代码块了。这也证明静态代码块只会被初始化一次。

再调整组合第二种场景:







@Testpublic void test5() throws ClassNotFoundException {  Class<?> clz = new Person().getClass();  System.out.println("---------------");  clz = Class.forName("com.choupangxia.reflect.Person");}

test5打印日志如下:

Person:静态代码块
Person:动态代码块
Person:构造方法
---------------

同样只打印一次静态代码块的操作。再次证明类中的静态代码块只会被初始化一次。

实例演示场景三

这里,我们比较一下三种形式获得的Class对象是否是相同的。测试代码如下:










@Testpublic void test6() throws ClassNotFoundException {  Class<?> clz1 = Person.class;  Class<?> clz2 = Class.forName("com.choupangxia.reflect.Person");  Class<?> clz3 = new Person().getClass();
  System.out.println(clz1 == clz2);  System.out.println(clz2 == clz3);}

注意,上面我们使用的是等号,也就是说比较的是引用。猜猜打印的结果?

true
true

三种形式获得的Class对象是同一个对象。这是为什么呢?

这要涉及到类的加载过程,我们知道类加载过程分:加载阶段、连接阶段和初始化阶段。

类的加载阶段是将class文件中的二进制数据读取到内存中,然后将该字节流所代表的静态存储结构转化为方法区中运行时的数据结构,并且在堆内存中生成一个该类的java.lang.class对象,作为方法区数据结构的入口。

类加载阶段的最终产物是堆内存中的class对象,对于同一个Classloader对象,不管某类被加载多少次,对应堆内存中的class对象始终只有一个。

也就是说无论通过哪种形式来获取Class对象,获得的都是堆内存中对应的Class对象。

回顾三种形式

(1)类名.class:JVM将使用类装载器,将类装入内存(前提是:类还没有装入内存),不做类的初始化工作,返回Class的对象。

(2)Class.forName("类名字符串"):装入类,并做类的静态初始化,返回Class的对象。

(3)实例对象.getClass():对类进行静态初始化、非静态初始化;返回引用运行时真正所指的对象(子对象的引用会赋给父对象的引用变量中)所属的类的Class的对象。


目录
相关文章
|
11天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
20天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
12天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
38 4
|
24天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
43 17
|
17天前
|
存储 缓存 网络协议
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点,GET、POST的区别,Cookie与Session
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点、状态码、报文格式,GET、POST的区别,DNS的解析过程、数字证书、Cookie与Session,对称加密和非对称加密
|
19天前
|
Java 编译器 Maven
Java“class file contains wrong class”解决
当Java程序运行时出现“class file contains wrong class”错误,通常是因为类文件与预期的类名不匹配。解决方法包括:1. 确保类名和文件名一致;2. 清理并重新编译项目;3. 检查包声明是否正确。
|
17天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
23天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
25天前
|
Java
通过Java代码解释成员变量(实例变量)和局部变量的区别
本文通过一个Java示例,详细解释了成员变量(实例变量)和局部变量的区别。成员变量属于类的一部分,每个对象有独立的副本;局部变量则在方法或代码块内部声明,作用范围仅限于此。示例代码展示了如何在类中声明和使用这两种变量。
|
1月前
|
存储 Java 数据管理
Java零基础-Java对象详解
【10月更文挑战第7天】Java零基础教学篇,手把手实践教学!
26 6