Java 面试宝典系列之 Java 基础
Posted Spring-_-Bear
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 面试宝典系列之 Java 基础相关的知识,希望对你有一定的参考价值。
文章目录
- 1、为什么 Java 代码可以实现一次编写、到处运行?
- 2、一个 Java 文件里可以有多个类吗(不含内部类)?
- 3、说一说你对 Java 访问权限的了解
- 4、介绍一下 Java 的数据类型
- 5、int 类型的数据范围是多少?
- 6、请介绍全局变量和局部变量的区别
- 7、请介绍一下实例变量的默认值
- 8、为啥要有包装类?
- 9、说一说自动装箱、自动拆箱的应用场景
- 10、 如何对 Integer 和 Double 类型判断相等?
- 11、 int 和 Integer 有什么区别,二者在做 == 运算时会得到什么结果?
- 12、说一说你对面向对象的理解
- 13、面向对象的三大特征是什么?
- 14、 封装的目的是什么,为什么要有封装?
- 15、说一说你对多态的理解
- 16、Java 中的多态是怎么实现的?
- 17、Java 为什么是单继承,为什么不能多继承?
- 18、说一说重写与重载的区别
- 19、构造方法能不能重写?
- 20、介绍一下 Object 类中的方法
- 21、说一说 hashCode() 和 equals() 的关系
- 22、为什么要重写 hashCode() 和 equals()?
- 23、== 和 equals() 有什么区别?
- 24、 String 类有哪些常用方法?
- 25、 String 可以被继承吗?
- 26、说一说 String 和 StringBuffer 有什么区别
- 27、说一说 StringBuffer 和 StringBuilder 有什么区别
- 28、使用字符串时,new 和 "" 推荐使用哪种方式?
- 29、说一说你对字符串拼接的理解
- 30、两个字符串相加的底层是如何实现的?
- 31、String a = "abc"; 说一下这个过程会创建什么,放在哪里?
- 32、new String("abc") 是去了哪里,仅仅是在堆里面吗?
- 33、接口和抽象类有什么区别?
- 34、接口中可以有构造函数吗?
- 35、谈谈你对面向接口编程的理解
- 36、遇到过异常吗,如何处理?
- 37、说一说 Java 的异常机制
- 38、请介绍 Java 的异常接口
- 39、finally 是无条件执行的吗?
- 40、在 finally 中 return 会发生什么?
- 41、说一说你对 static 关键字的理解
- 42、static 修饰的类能不能被继承?
- 43、static 和 final 有什么区别?
- 44、说一说你对泛型的理解
- 45、介绍一下泛型擦除
- 46、List<? super T> 和 List<? extends T> 有什么区别?
- 47、说一说你对 Java 反射机制的理解
- 48、Java 反射在实际项目中有哪些应用场景?
- 49、==说一说 Java 的四种引用方式(强、软、弱、虚)==
1、为什么 Java 代码可以实现一次编写、到处运行?
JVM(Java 虚拟机)是 Java 跨平台的关键。Java 源代码(.java
)经过编译器编译成字节码(.class
),JVM 负责将字节码翻译成特定平台下的机器码并运行,从而实现了平台无关性
2、一个 Java 文件里可以有多个类吗(不含内部类)?
一个 .java
结尾的文件中可以有多个类但有且仅有一个使用 public
修饰符修饰的类,且源文件名必须与 public 修饰的类名相同并以 .java 作为文件后缀名
3、说一说你对 Java 访问权限的了解
修饰符 | 说明 |
---|---|
private | 仅可被本类的成员访问 |
default | 可以被本类的成员、同一个包下的其他类访问 |
protected | 可以被本类的成员、同一个包下的其他类、本类的子类访问 |
public | 访问不受限 |
4、介绍一下 Java 的数据类型
Java 的数据类型分为八大基本数据类型和引用类型
-
八大基本数据类型:整数类型(
byte/short/int/long
)、浮点类型(float/double
)、字符类型(char
)、布尔类型(boolean
,除 boolean 类型外其它类型都可以看作数字类型)类型 字节数 boolean 不确定,因 JVM 具体实现而异 byte 1 char 2(\\u0000 ~ \\uffff) short 2 int 4 float 4 long 8 double 8 -
引用类型:对一个对象的引用,可以将引用类型分为三类,即数组、类和接口
5、int 类型的数据范围是多少?
Java 中的 int 数据类型占用 4 个字节(byte),一个字节占用 8 位(bit),因而 int 的数据范围是 232 - 232 - 1
6、请介绍全局变量和局部变量的区别
-
全局变量(Java 中没有真正意义上的全局变量的概念,代指类的成员变量)
- 定义位置:成员变量是在类的范围里定义的变量
- 默认值:成员变量有默认初始值
- 实例变量:未被
static
修饰的成员变量也叫实例变量,它存储于对象所在的堆内存中,生命周期与对象相同 - 类变量:被
static
修饰的成员变量也叫类变量,它存储于方法区中,生命周期与当前类相同
-
局部变量
-
定义位置:局部变量是在方法或代码块里定义的变量
-
默认值:局部变量没有默认初始值(数组除外)
public static void main(String[] args) int[] arr = new int[3]; // Output: [0, 0, 0] System.out.println(Arrays.toString(arr));
-
作用域:局部变量存储于栈内存中,方法执行结束时变量空间会自动释放
-
7、请介绍一下实例变量的默认值
所谓实例变量也即未被 static 关键字修饰的成员变量(参考 6.1.3),当实例变量未在定义时,或未在构造器中,或未在代码块中赋初值时将拥有默认值。引用类型默认初始化为 null,八大基本数据类型的默认初始值如下表
类型 | 默认值 |
---|---|
boolean | false |
byte | 0 |
char | ‘\\u0000’ |
short | 0 |
int | 0 |
float | 0.0F |
long | 0L |
double | 0.0 |
8、为啥要有包装类?
在万物皆对象的 Java 编程世界里,八大基本数据类型在实际使用过程中存在着一些约束,比如传参、赋值传入的值可能是 null,而基本数据类型不允许为 null,这时就会抛出隐式的异常而导致程序中断运行,因而为每个基本数据类型都引入对应的包装类以更加高效地使用 Java 进行编程
9、说一说自动装箱、自动拆箱的应用场景
-
什么是自动装箱和自动拆箱?自动装、拆箱是
JDK1.5
提供的功能,通过自动装箱、自动拆箱可以大大简化基本类型和包装类对象之间的转换 -
自动装箱与自动拆箱
- 自动装箱:可以把一个基本类型的数据直接赋值给对应的包装类型
- 自动拆箱:可以把一个包装类型的对象直接赋值给对应的基本类型
-
手动装箱与手动拆箱
// 手动装箱 Integer integer = Integer.valueOf(i); // 手动拆箱 int ii = integer.intValue();
10、 如何对 Integer 和 Double 类型判断相等?
-
错误方法:不能使用
==
、转为字符串、包装类的compareTo
方法进行比较,因为这三种比较方式比较前提是二者属于同一数据类型 -
正确方法:将
Integer
、Double
先转为转换为相同的基本数据类型(double
),然后再二者作差进行比较Integer i = 100; Double d = 100.00; System.out.println(i.doubleValue() - d.doubleValue() == 0 ? "equal" : "not equal");
11、 int 和 Integer 有什么区别,二者在做 == 运算时会得到什么结果?
int
是基本数据类型,Integer
是 int 的包装类。二者在做 ==
运算时,Integer 会自动拆箱为 int 类型,然后再进行比较
12、说一说你对面向对象的理解
面向对象是比面向过程编程更为优秀的一种程序设计思想,其基本思想是使用类对客观世界中存在的事物进行模拟,形成一种映射的关系,使得系统设计符合人类的自然思维方式,开发更加便捷,设计更加直观,程序更加健壮
13、面向对象的三大特征是什么?
- 继承:是面向对象实现代码复用的重要手段,当子类继承父类后获得父类的方法和属性
- 封装:将对象的实现细节隐藏,对外暴露接口
- 多态:子类的对象可以赋值给父类的引用,做到运行时动态绑定,意味着执行同一对象的同一方法时,可表现出多种行为特征
14、 封装的目的是什么,为什么要有封装?
封装的目的是隐藏对象内部的实现细节,外部无法直接操作和修改(只能通过提供的接口方法)。使用封装具有以下好处:
- 隐藏了类的实现细节
- 限制对成员变量的非法访问
- 进行数据检查,保证了对象信息的完整性
- 利于修改,提高了代码的可维护性
15、说一说你对多态的理解
子类的对象可以赋值给父类的引用,做到运行时动态绑定,意味着执行同一对象的同一方法时,可表现出多种行为特征。提高了程序的可扩展性,让代码更加简洁优雅
16、Java 中的多态是怎么实现的?
多态的实现依赖于继承。程序设计时,将方法的参数设置为父类型,传参时传入该父类型的子类型即可做到运行时动态绑定,此时执行父类的同一方法时可以表现出多种行为特征
17、Java 为什么是单继承,为什么不能多继承?
Java 业内也称为 “C++--
”,即 Java 语言设计时借鉴了 C++ 的语法,而 C++ 的多继承机制繁琐,容易产生误解,代码可读性不高,Java 语言设计时摒弃了这一实现,只允许单继承,但一个子类有父类,父类仍可有父类,变相地实现了多继承
18、说一说重写与重载的区别
- 重载:重载发生在一个类中,多个方法之间方法名相同、参数列表不同时构成重载(不能根据方法的访问修饰符和返回值判断是否构成重载,因为他们不属于方法签名的一部分)
- 重写:重写发生在父子类或实现接口时,重写要求返回值类型小于等于父类、抛出的异常小于等于父类、访问权限修饰符要大于等于父类即不能缩小父类方法的访问权限
19、构造方法能不能重写?
构造方法可以重载但不能重写。因为构造方法需要和类保持同名,而重写的要求是子类方法要和父类方法保持同名。如果允许重写构造方法的话,那么子类中将会存在与类名不同的构造方法,这与构造方法的要求是矛盾的
20、介绍一下 Object 类中的方法
方法 | 功能 |
---|---|
Class<?> getClass() | 返回该对象的运行时类 |
boolean equals(Object obj) | 判断传入的对象与当前对象引用是否相等 |
int hashCode() | 返回该对象的 hashCode 值。在默认情况下,Object 类的 hashCode() 方法根据该对象的地址来计算 |
String toString() | Object 类的 toString() 方法返回【运行时类名@十六进制 hashCode 值】格式的字符串 |
clone() | 克隆对象 |
finalize() | 主动调用垃圾回收器回收内存,JDK9 及之后版本不推荐使用 |
wait() | 让当前线程进入等待状态。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法 |
notify() | 唤醒在该对象上等待的某个线程 |
notifyAll() | 唤醒在该对象上等待的所有线程 |
21、说一说 hashCode() 和 equals() 的关系
hashCode() 用于获取对象的哈希码(散列码),eauqls() 用于比较两个对象引用是否相等
- 如果两个对象相等,则它们必然有相同的 hashCode
- 虽然两个对象有相同的 hashCode,但它们未必相等
22、为什么要重写 hashCode() 和 equals()?
如果重写 equals() 时不重写 hashCode(),那么导致该类的实例添加到散列集合(Set、Map)中时不同的对象具有不同的 hashCode(对象间两两比较始终不会相等),从而致使通过 equals() 方法比较内容判断对象是否相同时就失去了意义。
重写 equals() 的同时重写 hashCode() 的意义在于当两个对象 equals() 比较相等时,他们就有相同的 hashCode 即可判断元素重复
23、== 和 equals() 有什么区别?
基本类型 | 引用类型 | |
---|---|---|
== | 比较数值是否相等 | 比较引用是否相同 |
未重写 | 重写后 | |
---|---|---|
equals() | 比较引用是否相同 | 比较对象内容是否相同 |
注:重写类的 equals() 方法的同时一般也会重写其 hashCode() 方法,原因分析参考第 22 点
24、 String 类有哪些常用方法?
方法 | 功能 |
---|---|
char charAt(int index) | 返回指定索引处的字符 |
String substring(int beginIndex, int endIndex) | 从字符串中截取 [begin, end) 子串 |
String[] split(String regex) | 以指定的规则将此字符串分割成数组 |
String trim() | 删除字符串前导和后置的空格 |
int indexOf(String str) | 返回子串在此字符串首次出现的索引 |
int lastIndexOf(String str) | 返回子串在此字符串最后出现的索引 |
boolean startsWith(String prefix) | 判断此字符串是否以指定的前缀开头 |
boolean endsWith(String suffix) | 判断此字符串是否以指定的后缀结尾 |
String toUpperCase() | 将此字符串中所有的字符大写 |
String toLowerCase() | 将此字符串中所有的字符小写 |
String replaceFirst(String regex, String replacement) | 用指定字符串替换第一个匹配的子串 |
String replaceAll(String regex, String replacement) | 用指定字符串替换所有的匹配的子串 |
25、 String 可以被继承吗?
String 类不可以被继承因为其是一个不可变类即使用了 fianl
关键字进行修饰。Java9 之前使用 char[]
数组来存储字符,Java9 及之后使用 byte[] 来保存字符。将 String 设计为不可变的类提供了极大的方便:
- 便于存储敏感信息如账号、密码、网络路径
- 不可变的数据可以由多线程共享而不用考虑数据安全问题
- 不变的字符串使得字符串常量池有了意义,节省了堆空间
- 使用不变的 String 字符串来存储对象的 hashCode 值
26、说一说 String 和 StringBuffer 有什么区别
- String 创建后其内容不可变,线程安全且效率高
- StringBuffer 创建后可以通过其 append()、insert()、reverse()、setCharAt()、setLength() 等方法进行修改,线程安全但效率低
27、说一说 StringBuffer 和 StringBuilder 有什么区别
两个类都是 final 类型的类,构造方法和成员方法基本相同,构建的字符串序列可变,不同的是 StringBuffer 较 StringBuilder 线程安全,导致效率较低。单线程情况下通常使用 StringBuilder
28、使用字符串时,new 和 “” 推荐使用哪种方式?
"hello"
的方式直接将栈中的变量引用到常量池的 “hello” 数据空间new String("hello")
将栈中的变量引用到堆中的 String 对象的 value 数组,而 value 最终引用到常量池的 “hello” 数据空间
new String() 比 “” 的方式占用更多的空间,推荐直接使用 “” 方式创建字符串
29、说一说你对字符串拼接的理解
+
:常量字符串直接相加- String 类的 concat(str1,str2) 方法:对两个包含变量的字符串进行拼接
- StringBuilder:包含变量且不要求线程安全
- StringBuffer:包含变量且要求线程安全
30、两个字符串相加的底层是如何实现的?
常量相加看池,变量相加看堆
// 池中只创建了一个字符串常量 "hello123"
String a = "hello" + "123";
// b -> 常量池的 "hello"
String b = "hello";
// c -> 常量池的 "123"
String c = "123";
// d -> 堆中 value,value 指向常量池中的 "hello123"
// b + c 的底层实现:调用 StringBuilder 的 append 方法连接两次,然后再 new String() 返回
String d = b + c;
31、String a = “abc”; 说一下这个过程会创建什么,放在哪里?
JVM 会先检查常量池中是否已经存有 “abc”,若没有则将 “abc” 存入常量池,已存在则直接将其引用赋值给栈变量 a
32、new String(“abc”) 是去了哪里,仅仅是在堆里面吗?
“abc” 字符串存放于常量池中,使用堆中的 String 对象的 private final char value[];
属性引用到 “abc”
33、接口和抽象类有什么区别?
- 接口体现的是一种规范和标准,对外暴露的接口体现了实现者所能提供的服务
- 抽象类体现的是一种模板式设计的理念,定义了多个子类共有的属性和方法
接口 | 抽象类 | |
---|---|---|
类 | 所有内部类 | 所有内部类 |
方法 | 默认方法、静态方法、抽象方法 | 所有类型的方法、抽象方法 |
变量 | 成员变量默认都是 public static final 类型 | 所有类型的变量 |
构造器 | 无 | 有构造器,但不用于创建对象而用于对抽象类的初始化 |
代码块 | 无 | 可以包含 |
34、接口中可以有构造函数吗?
接口定义的是一种规范,因此接口里不能包含构造器和初始化块定义。但接口里可以包含成员变量(默认都是 public static final 类型)、方法(只能是抽象方法、静态方法、默认方法)、内部类(包括内部接口、枚举等)
35、谈谈你对面向接口编程的理解
接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以极好地降低程序各模块之间的耦合,从而提高系统的可扩展性和可维护性。基于这种原则,很多软件架构设计理论都倡导 “面向接口” 编程,而不是面向实现类编程,使用接口编程也能更好地使用多态
36、遇到过异常吗,如何处理?
-
try 捕获异常
-
catch 处理异常
-
finally 无条件回收资源
37、说一说 Java 的异常机制
异常逐层抛出,依次寻找处理者,若没有异常的处理者则由 JVM 打印异常信息后退出系统
- try 块用于包裹业务代码
- catch 块用于捕获并处理某个类型的异常
- finally 块则用于回收资源
38、请介绍 Java 的异常接口
-
Error 是无法处理的错误:一般是与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败
-
Exception 分为运行时异常(Runtime,无须显式声明抛出)和编译时异常(Checked,编译时必须处理)
39、finally 是无条件执行的吗?
正常情况下 finally 块中的语句是无条件执行的。如果在 try 块或 catch 块中使用 System.exit(1);
退出虚拟机,则 finally 块将失去执行的机会
try
throw new RuntimeException("Runtime exception was thrown");
catch (Exception e)
System.out.println(e);
System.exit(1);
finally
// 此时 finally 将不再输出
System.out.println("finally");
40、在 finally 中 return 会发生什么?
一旦在 finally 块中使用了 return、throw 语句,将会导致 try、catch 块中的 return、throw 动作不会执行
public int method()
int i = 1;
try
i++;
String[] names = new String[3];
if (names[i].equals("lcx"))
return i;
catch (NullPointerException e)
return ++i;
finally
// return result: 4
return ++i;
41、说一说你对 static 关键字的理解
在 Java 类里只能包含成员变量、方法、构造器、初始化块、内部类(包括接口、枚举)5 种成员,除构造器外全都可以用 static 关键字进行修饰。以 static 修饰的成员就是类成员,类成员属于整个类而不属于单个对象
类成员不能访问实例成员,因为类成员属于类,作用域比实例成员更大,完全可能出现类成员已经初始化完成,但实例成员还未曾初始化的情况,如果允许类成员访问实例成员将会引起大量错误
42、static 修饰的类能不能被继承?
用 static 修饰的内部类(静态内部类)可以被继承,这个内部类属于外部类本身而不属于某个对象。外部类的上一级程序单元是包,所以不可以用 static 修饰
静态内部类需满足如下规则:
- 静态内部类可以包含静态成员,也可以包含非静态成员
- 静态内部类不能访问外部类的实例成员,只能访问它的静态成员
- 外部类的所有方法、初始化块都能访问其内部定义的静态内部类
- 在外部类的外部,也可以实例化静态内部类,语法如下:
外部类.内部类 变量名 = new 外部类.内部类构造方法();
43、static 和 final 有什么区别?
-
static 关键字可以修饰类的成员变量、成员方法、代码块、内部类,被 static 修饰的成员是类的成员,它属于类而不属于单个对象(类成员属于类,它随类的信息存储在方法区,而并不随对象存储在堆中)
-
final 关键字可以修饰类、变量、方法,被 final 修饰的成员有以下特点:
-
final 修饰的类不可继承、方法不可重写、变量获得初始值后不可修改
-
final 修饰类变量的初始化位置:定义时、静态代码块中
public class Outer private static final int MIN; static MIN = -1;
-
final 修饰成员变量的初始化位置:定义时、代码块中、构造器中
public class Outer private final int a; a = 2; private final int b; public Outer(int b) this.b = b;
-
final 修饰局部变量的初始化位置:定义时、后续代码中
public static void main(String[] args) final int a; a = 2;
-
44、说一说你对泛型的理解
若没有泛型的支持,则所有放进集合的元素编译类型都为 Object 类型,对放进集合的元素不能进行控制、取出也需要进行向下强制转型,容易造成 ClassCastException
Java5 引入泛型后代码更加简洁、程序更加健壮。Java 泛型的设计原则是只要代码在编译时没有出现警告,就不会在运行时产生 ClassCastException,使用泛型使得代码的可读性更高
45、介绍一下泛型擦除
泛型是 Java 1.5
才引进的概念,在这之前是没有泛型这个概念的。为了与之前的代码更好的兼容,字节码在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除(泛型信息只存在于代码编译阶段)
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
// Output: true
System.out.println(l1.getClass() == l2.getClass());
// 当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔掉
List<String> list1 = ...;
// list2 将元素当做 Object 处理
List list2 = list1;
// 把一个不具有泛型信息的对象赋值给另一个具有泛型信息的变量时,发生泛型转换,编译器提示未经检查的转换
List list1 = ...;
// Unchecked assignment: 'java.util.List' to 'java.util.List<java.lang.String>'
List<String> list2 = list1;
46、List<? super T> 和 List<? extends T> 有什么区别?
-
其中的
?
代表通配符,通配符的出现是为了指定泛型中的类型范围。<?>
提供了只读的功能,也就是它删减了增加具体类型元素的能力,只保留与具体类型无关的功能。如下代码:public void generic(Collection<?> collection) // 编译报错 collection.add(1); // 编译通过 collection.isEmpty(); Iterator<?> iterator = collection.iterator();
-
List<? super T>
用于设定类型通配符 ? 的下限,此处 ? 代表一个未知的类型,但它必须是 T 的父类型 -
List<? extends T>
用于设定类型通配符 ? 的上限,此处 ? 代表一个未知的类型,但它必须是 T 的子类型
泛型和数组有所不同:假设 Foo 是 Bar 的一个子类型(子类或者子接口),那么
Foo[]
依然是Bar[]
的子类型,但G<Foo>
不是G<Bar>
的子类型
47、说一说你对 Java 反射机制的理解
反射机制可以在程序运行时:
- 通过反射获得任意一个类的 Class 对象,并通过这个对象查看这个类的信息
- 可以通过反射创建任意一个类的实例,并访问该实例的所有成员
- 可以通过反射机制生成一个类的动态代理类或动态代理对象
48、Java 反射在实际项目中有哪些应用场景?
- JDBC 时使用反射加载数据库的驱动类,获得数据库连接对象
- 框架底层解析注解/XML 时根据类全路径,利用反射获取实例
- 面向切面编程(AOP)的具体实现是在程序运行时利用反射机制来创建目标对象的代理类
49、说一说 Java 的四种引用方式(强、软、弱、虚)
-
强引用:这是 Java 程序中最常见的引用方式,即程序创建一个对象,并把这个对象赋给一个引用变量,程序通过该引用变量来操作实际的对象。当一个对象被一个或一个以上的引用变量所引用时,它处于可达状态,不可能被系统垃圾回收机制回收
Object o = new Object();
-
软引用:当一个对象只有软引用时,它有可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可使用该对象。当系统内存空间不足时,系统可能会回收它。软引用通常用于对内存敏感的程序中
SoftReference<String> studentSoftReference = new SoftReference<>("HelloWorld"); String helloWorld = studentSoftReference.get(); // HelloWorld System.out.println(helloWorld); System.gc(); // Output: HelloWorld(只有当内存不足且垃圾回收机制工作时才回收软引用对象) System.out.println(helloWorld);
-
弱引用:弱引用和软引用很像,但弱引用的引用级别更低。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。当然,并不是说当一个对象只有弱引用时,它就会立即被回收,正如那些失去引用的对象一样,必须等到系统垃圾回收机制运行时才会被回收
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[1]); // Output: [0] System.out.println(Arrays.toString(weakReference.get())); // 垃圾回收机制工作时弱引用的对象一定会被回收 System.gc(); // Output: null System.out.println(Arrays.toString(weakReference.get()));
-
虚引用:虚引用类似于完全没有引用,对对象本身没有太大影响,对象甚至感觉不到虚引用的存在,虚引用主要用于跟踪对象被垃圾回收的状态。虚引用不能单独使用,必须和引用队列联合使用,它的原理是当一个虚引用指向的对象被回收的时候,它会把相关信息添加到跟这个虚引用相关联的这个队列中。再就是虚引用的 get 方法,返回的永远是
null
。用途是可以用来管理堆外内存 Netty NIOpublic class PhantomReference<T> extends Reference<T> public T get() return null; public PhantomReference(T referent, ReferenceQueue<? super T> q) super(referent, q);
/** * @author Spring-_-Bear * @datetime 2022-06-16 06:52 */ public class PhantomReferenceTest public static void main(String[] args) // 引用队列,跟虚引用相关联 ReferenceQueue<Person> referenceQueue = new ReferenceQueue<>(); // 虚引用 PhantomReference<Person> personPhantomReference = new PhantomReference<>(new Person(), referenceQueue); List<Object> list = new LinkedList<>(); // 开启一个线程,不断地占用内存 new Thread(() -> while (true) try // 不断的给 list 添加数据,使内存被占光,导致虚引用被回收 list.add(new byte[1024 * 1024]); Thread.sleep(1000); catch (Exception e) e.printStackTrace(); // Output: null System.out.println(personPhantomReference.get()); ).start(); // 开启另一个线程,不断的从 referenceQueue 引用队列中取数据 new Thread(() -> [Interview]Java 面试宝典系列之 Java 多线程
[Interview]Java 面试宝典系列之 Java 集合类
[Interview]Java 面试宝典系列之 Spring
[Interview]Java 面试宝典系列之 Java 虚拟机(JVM)