Java面试题及详细答案120道之(061-080)
《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
文章目录
- 一、本文面试题目录
- 61. 什么是Java中的协变返回类型?
- 62. 解释Java中的`ThreadLocal`
- 63. Java中的`static`关键字有什么作用?
- 64. 什么是Java中的死锁?如何避免?
- 65. 解释Java中的`Comparable`和`Comparator`
- 66. Java中的`enum`枚举类有什么特点?
- 67. 什么是Java中的包装类?自动装箱与拆箱的原理是什么?
- 68. 解释Java中的`transient`关键字
- 69. Java中的`BigDecimal`有什么作用?
- 70. 什么是Java中的注解处理器(Annotation Processor)?
- 71. Java中的`WeakReference`、`SoftReference`、`PhantomReference`有什么区别?
- 72. 解释Java中的`CompletableFuture`
- 73. Java中的`for-each`循环与普通`for`循环有什么区别?
- 74. 什么是Java中的模块化(Module)?
- 75. Java中的`DateTimeFormatter`与`SimpleDateFormat`有什么区别?
- 76. 什么是Java中的方法句柄(MethodHandle)?
- 77. Java中的`Optional`类有什么作用?
- 78. 解释Java中的`StackOverflowError`与`OutOfMemoryError`
- 79. Java中的`Varargs`(可变参数)原理是什么?
- 80. 什么是Java中的`Sealed Classes`(密封类)?
- 二、120道面试题目录列表
一、本文面试题目录
61. 什么是Java中的协变返回类型?
原理:协变返回类型允许子类重写父类方法时,返回父类方法返回类型的子类类型(JDK 5+支持),增强代码灵活性。
代码示例:
class Animal {}
class Dog extends Animal {}class AnimalFactory {// 父类方法返回Animalpublic Animal createAnimal() {return new Animal();}
}class DogFactory extends AnimalFactory {// 子类重写方法返回Dog(Animal的子类)@Overridepublic Dog createAnimal() { // 协变返回类型return new Dog();}
}public class CovariantReturn {public static void main(String[] args) {AnimalFactory factory = new DogFactory();Animal animal = factory.createAnimal();System.out.println(animal.getClass()); // 输出class Dog}
}
62. 解释Java中的ThreadLocal
原理:ThreadLocal
为每个线程提供独立的变量副本,实现线程隔离(线程私有)。常用于存储线程上下文信息(如用户会话)。
注意:使用后需手动调用remove()
,否则可能导致内存泄漏(线程池线程复用,变量未清除)。
代码示例:
public class ThreadLocalDemo {private static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {// 线程1设置变量new Thread(() -> {threadLocal.set("线程1的变量");System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());threadLocal.remove(); // 清除变量}, "线程1").start();// 线程2设置变量new Thread(() -> {threadLocal.set("线程2的变量");System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());threadLocal.remove(); // 清除变量}, "线程2").start();}
}
// 输出:
// 线程1: 线程1的变量
// 线程2: 线程2的变量
63. Java中的static
关键字有什么作用?
原理:static
修饰的成员属于类而非实例,可直接通过类名访问,共享于所有实例。
用法:
- 静态变量:类级别的变量,所有实例共享;
- 静态方法:无
this
指针,只能访问静态成员; - 静态代码块:类加载时执行,用于初始化静态变量;
- 静态内部类:不依赖外部类实例,不能访问外部类非静态成员。
代码示例:
public class StaticDemo {private static int staticVar = 0; // 静态变量private int instanceVar = 0; // 实例变量static {System.out.println("静态代码块执行"); // 类加载时执行}public static void staticMethod() {// System.out.println(instanceVar); // 编译错误:静态方法不能访问非静态成员System.out.println("静态方法:" + staticVar);}public void instanceMethod() {staticVar++; // 实例方法可访问静态变量instanceVar++;}public static void main(String[] args) {StaticDemo.staticMethod(); // 直接通过类名调用静态方法StaticDemo obj1 = new StaticDemo();StaticDemo obj2 = new StaticDemo();obj1.instanceMethod();obj2.instanceMethod();System.out.println("staticVar: " + staticVar); // 输出2(共享)System.out.println("obj1.instanceVar: " + obj1.instanceVar); // 输出1(独立)}
}
64. 什么是Java中的死锁?如何避免?
原理:死锁是两个或多个线程相互等待对方释放资源而陷入无限等待的状态。
必要条件:互斥、持有并等待、不可剥夺、循环等待。
避免方式:按固定顺序获取资源、设置超时时间、使用tryLock()
等。
代码示例(死锁及避免):
public class DeadlockDemo {private static final Object resource1 = new Object();private static final Object resource2 = new Object();// 死锁场景:线程1持有resource1等待resource2,线程2持有resource2等待resource1public static void main(String[] args) {new Thread(() -> {synchronized (resource1) { // 线程1获取resource1System.out.println("线程1持有resource1,等待resource2");try { Thread.sleep(100); } catch (InterruptedException e) {}synchronized (resource2) { // 等待resource2System.out.println("线程1获取resource2");}}}).start();new Thread(() -> {// 避免死锁:按相同顺序获取资源(先resource1再resource2)synchronized (resource1) { // 原代码为synchronized (resource2),导致死锁System.out.println("线程2持有resource1,等待resource2");try { Thread.sleep(100); } catch (InterruptedException e) {}synchronized (resource2) {System.out.println("线程2获取resource2");}}}).start();}
}
65. 解释Java中的Comparable
和Comparator
原理:两者用于对象排序,核心区别:
Comparable
:类自身实现的接口(compareTo(T o)
),定义“自然排序”;Comparator
:外部比较器(compare(T o1, T o2)
),定义“定制排序”,更灵活。
代码示例:
import java.util.Arrays;
import java.util.Comparator;// 实现Comparable,定义自然排序(按年龄)
class Person implements Comparable<Person> {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic int compareTo(Person o) { // 自然排序:按年龄升序return Integer.compare(this.age, o.age);}// getter/toString省略
}public class CompareDemo {public static void main(String[] args) {Person[] people = {new Person("Bob", 25),new Person("Alice", 20)};// 自然排序(使用Comparable)Arrays.sort(people);System.out.println(Arrays.toString(people)); // 输出[Alice(20), Bob(25)]// 定制排序(使用Comparator,按姓名长度)Arrays.sort(people, Comparator.comparingInt(p -> p.getName().length()));System.out.println(Arrays.toString(people)); // 输出[Bob(25)(长度3), Alice(20)(长度5)]}
}
66. Java中的enum
枚举类有什么特点?
原理:枚举是特殊的类,实例个数固定(枚举常量),默认继承Enum
类,不能被继承。常用于定义常量集合(如状态码、类型)。
特性:
- 枚举常量为单例;
- 可定义构造器、方法、字段;
- 支持
switch
语句。
代码示例:
enum Season {SPRING("春天", 1),SUMMER("夏天", 2),AUTUMN("秋天", 3),WINTER("冬天", 4);private String name;private int code;// 私有构造器(枚举构造器必须私有)Season(String name, int code) {this.name = name;this.code = code;}// 自定义方法public String getInfo() {return code + ":" + name;}
}public class EnumDemo {public static void main(String[] args) {Season season = Season.SPRING;System.out.println(season.getInfo()); // 输出1:春天// switch语句switch (season) {case SPRING:System.out.println("春暖花开");break;// 其他case省略}// 遍历枚举常量for (Season s : Season.values()) {System.out.println(s);}}
}
67. 什么是Java中的包装类?自动装箱与拆箱的原理是什么?
原理:包装类是基本类型的对象形式(如Integer
对应int
),用于泛型、集合等场景。
- 自动装箱:基本类型→包装类(如
int
→Integer
,编译期自动调用Integer.valueOf()
); - 自动拆箱:包装类→基本类型(如
Integer
→int
,编译期自动调用intValue()
)。
代码示例:
public class WrapperDemo {public static void main(String[] args) {// 自动装箱:int → IntegerInteger a = 10; // 等价于 Integer a = Integer.valueOf(10);// 自动拆箱:Integer → intint b = a; // 等价于 int b = a.intValue();// 注意:Integer缓存[-128, 127]范围的对象Integer c = 127;Integer d = 127;System.out.println(c == d); // true(缓存命中)Integer e = 128;Integer f = 128;System.out.println(e == f); // false(超出缓存范围,新对象)}
}
68. 解释Java中的transient
关键字
原理:transient
修饰的字段在序列化时被忽略,不写入字节流。用于排除不需要序列化的敏感信息(如密码)。
代码示例:
import java.io.*;class User implements Serializable {private String username;private transient String password; // 密码不序列化public User(String username, String password) {this.username = username;this.password = password;}@Overridepublic String toString() {return "User{username='" + username + "', password='" + password + "'}";}
}public class TransientDemo {public static void main(String[] args) throws IOException, ClassNotFoundException {User user = new User("admin", "123456");// 序列化ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(user);// 反序列化ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));User deserializedUser = (User) ois.readObject();System.out.println(deserializedUser); // 输出User{username='admin', password='null'}(password未序列化)}
}
69. Java中的BigDecimal
有什么作用?
原理:BigDecimal
用于高精度十进制计算,解决float
/double
的浮点精度问题(如0.1 + 0.2 = 0.30000000000000004
)。
注意:
- 避免使用
double
构造器(可能引入精度问题),推荐String
构造器; - 运算需用
add()
、subtract()
等方法,而非+
、-
。
代码示例:
import java.math.BigDecimal;public class BigDecimalDemo {public static void main(String[] args) {// 浮点精度问题System.out.println(0.1 + 0.2); // 输出0.30000000000000004// 使用BigDecimalBigDecimal a = new BigDecimal("0.1"); // 推荐String构造器BigDecimal b = new BigDecimal("0.2");BigDecimal sum = a.add(b); // 加法System.out.println(sum); // 输出0.3BigDecimal product = a.multiply(b); // 乘法System.out.println(product); // 输出0.02}
}
70. 什么是Java中的注解处理器(Annotation Processor)?
原理:注解处理器是编译期工具,用于扫描和处理注解,可生成代码、校验逻辑等(如Lombok通过@Data
生成getter/setter)。需继承AbstractProcessor
,重写process()
方法。
代码示例(简单注解处理器):
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;
import java.util.Set;// 处理@Log注解的处理器(需配置META-INF/services/javax.annotation.processing.Processor)
public class LogProcessor extends AbstractProcessor {@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {// 遍历被@Log注解的元素roundEnv.getElementsAnnotatedWith(Log.class).forEach(element -> {System.out.println("处理被@Log注解的元素:" + element.getSimpleName());// 此处可生成代码(如日志工具类)});return true;}
}
No. | 大剑师精品GIS教程推荐 |
---|---|
0 | 地图渲染基础- 【WebGL 教程】 - 【Canvas 教程】 - 【SVG 教程】 |
1 | Openlayers 【入门教程】 - 【源代码+示例 300+】 |
2 | Leaflet 【入门教程】 - 【源代码+图文示例 150+】 |
3 | MapboxGL 【入门教程】 - 【源代码+图文示例150+】 |
4 | Cesium 【入门教程】 - 【源代码+综合教程 200+】 |
5 | threejs 【中文API】 - 【源代码+图文示例200+】 |
71. Java中的WeakReference
、SoftReference
、PhantomReference
有什么区别?
原理:均为引用类型,与垃圾回收相关,强度从高到低为:强引用 > 软引用 > 弱引用 > 虚引用。
类型 | 特点 | 应用场景 |
---|---|---|
StrongReference | 默认引用类型,只要存在,对象不被回收 | 普通对象引用 |
SoftReference | 内存不足时回收,常用于缓存 | 图片缓存 |
WeakReference | 垃圾回收时立即回收,不依赖内存情况 | 关联临时数据(如WeakHashMap ) |
PhantomReference | 最弱,无法通过引用获取对象,用于跟踪对象回收,必须配合引用队列使用 | 资源释放(如关闭文件描述符) |
代码示例(弱引用):
import java.lang.ref.WeakReference;public class ReferenceDemo {public static void main(String[] args) {Object obj = new Object();WeakReference<Object> weakRef = new WeakReference<>(obj);obj = null; // 断开强引用System.gc(); // 触发GCSystem.out.println(weakRef.get()); // 输出null(对象已被回收)}
}
72. 解释Java中的CompletableFuture
原理:CompletableFuture
是JDK 8+引入的异步编程工具,支持链式异步操作、结果组合等,替代传统Future
的阻塞获取。
常用方法:
supplyAsync()
:异步执行有返回值的任务;thenApply()
:异步处理前一个任务的结果;thenCombine()
:组合两个异步任务的结果。
代码示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;public class CompletableFutureDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {// 异步任务1:计算a + bCompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {System.out.println("任务1执行");return 10 + 20;});// 异步任务2:计算c * dCompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {System.out.println("任务2执行");return 3 * 4;});// 组合结果:(a+b) * (c*d)CompletableFuture<Integer> resultFuture = future1.thenCombine(future2, (r1, r2) -> r1 * r2);System.out.println("最终结果:" + resultFuture.get()); // 输出30 * 12 = 360}
}
73. Java中的for-each
循环与普通for
循环有什么区别?
原理:
for-each
(增强for循环):简化集合/数组遍历,底层依赖迭代器(Iterable
),无法获取索引,不能修改集合结构(如删除元素);- 普通
for
循环:可通过索引访问,支持修改集合(需注意索引偏移)。
代码示例:
import java.util.ArrayList;
import java.util.List;public class ForLoopDemo {public static void main(String[] args) {List<String> list = new ArrayList<>(List.of("a", "b", "c"));// for-each循环for (String str : list) {System.out.println(str);// list.remove(str); // 抛出ConcurrentModificationException}// 普通for循环(可安全删除)for (int i = 0; i < list.size(); i++) {if (list.get(i).equals("b")) {list.remove(i);i--; // 修正索引偏移}}System.out.println(list); // 输出[a, c]}
}
74. 什么是Java中的模块化(Module)?
原理:JDK 9引入模块化,将代码和资源封装为模块,通过module-info.java
声明依赖和导出包,解决“类路径地狱”(类冲突)。
核心关键字:
module
:定义模块;requires
:声明依赖的模块;exports
:导出包,允许其他模块访问;opens
:开放包,允许反射访问。
代码示例(module-info.java
):
// 模块定义:com.example.myapp
module com.example.myapp {requires java.base; // 依赖基础模块(默认隐含)requires com.example.utils; // 依赖自定义工具模块exports com.example.myapp.service; // 导出service包opens com.example.myapp.model to com.example.reflect; // 向反射模块开放model包
}
75. Java中的DateTimeFormatter
与SimpleDateFormat
有什么区别?
原理:均用于日期时间格式化,核心区别:
特性 | SimpleDateFormat | DateTimeFormatter (JDK 8+) |
---|---|---|
线程安全 | 非线程安全(内部有共享变量) | 线程安全(不可变) |
适用类型 | Date | LocalDateTime 等新日期类型 |
灵活性 | 较低 | 较高(支持自定义格式、本地化) |
代码示例:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.text.SimpleDateFormat;
import java.util.Date;public class DateTimeFormat {public static void main(String[] args) {// DateTimeFormatter(线程安全)DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");LocalDateTime now = LocalDateTime.now();String formatted = now.format(formatter);System.out.println(formatted); // 输出2025-07-22 12:34:56// SimpleDateFormat(非线程安全)SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String sdfFormatted = sdf.format(new Date());System.out.println(sdfFormatted); // 输出2025-07-22 12:34:56}
}
76. 什么是Java中的方法句柄(MethodHandle)?
原理:MethodHandle
是JDK 7引入的反射替代方案,用于动态调用方法,性能优于反射(更接近直接调用),支持方法链接、参数适配等。
代码示例:
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;public class MethodHandleDemo {public static String greet(String name) {return "Hello, " + name;}public static void main(String[] args) throws Throwable {// 获取方法句柄:参数类型String,返回类型StringMethodType methodType = MethodType.methodType(String.class, String.class);MethodHandle methodHandle = MethodHandles.lookup().findStatic(MethodHandleDemo.class, "greet", methodType);// 调用方法String result = (String) methodHandle.invoke("Alice");System.out.println(result); // 输出Hello, Alice}
}
77. Java中的Optional
类有什么作用?
原理:Optional
是JDK 8+引入的容器类,用于包装可能为null
的对象,避免NullPointerException
,强制显式处理null
情况。
常用方法:
of()
:创建非空Optional
(null
则抛异常);ofNullable()
:创建可空Optional
;orElse()
:null
时返回默认值;ifPresent()
:值存在时执行操作。
代码示例:
import java.util.Optional;public class OptionalDemo {public static void main(String[] args) {String str = null;// 避免NullPointerExceptionOptional<String> optional = Optional.ofNullable(str);// 存在时打印,否则输出默认值optional.ifPresent(s -> System.out.println(s)); // 不执行String result = optional.orElse("默认值");System.out.println(result); // 输出默认值// 链式调用String upperCase = optional.map(String::toUpperCase).orElse("空字符串");System.out.println(upperCase); // 输出空字符串}
}
78. 解释Java中的StackOverflowError
与OutOfMemoryError
原理:均为Error
子类,属于严重错误,不可恢复。
错误类型 | 原因 | 示例场景 |
---|---|---|
StackOverflowError | 栈内存溢出(方法调用栈深度过大) | 无限递归调用 |
OutOfMemoryError | 堆内存溢出(对象无法分配内存) | 创建大量对象且未回收 |
OutOfMemoryError: Metaspace | 元空间溢出(类信息过多) | 动态生成大量类(如CGLIB) |
代码示例(栈溢出):
public class StackOverflow {public static void recursiveCall() {recursiveCall(); // 无限递归}public static void main(String[] args) {recursiveCall(); // 抛出StackOverflowError}
}
79. Java中的Varargs
(可变参数)原理是什么?
原理:可变参数允许方法接收不定数量的同类型参数,语法为类型... 参数名
,编译后转换为数组。一个方法只能有一个可变参数,且必须位于参数列表末尾。
代码示例:
public class VarargsDemo {// 可变参数方法:计算多个整数的和public static int sum(int... numbers) { // 等价于int[] numbersint total = 0;for (int num : numbers) {total += num;}return total;}public static void main(String[] args) {System.out.println(sum(1, 2, 3)); // 输出6System.out.println(sum(10, 20)); // 输出30System.out.println(sum()); // 输出0(空数组)}
}
80. 什么是Java中的Sealed Classes
(密封类)?
原理:JDK 15引入的密封类,通过sealed
关键字限制子类继承,仅允许指定类继承,增强代码安全性。
关键字:
sealed
:声明密封类;permits
:指定允许继承的子类;final
:子类不可再继承;non-sealed
:子类可被任意继承。
代码示例:
// 密封类:仅允许Cat和Dog继承
public sealed class Animal permits Cat, Dog {public abstract void sound();
}final class Cat extends Animal { // final子类,不可再继承@Overridepublic void sound() {System.out.println("喵喵");}
}non-sealed class Dog extends Animal { // 非密封子类,可被继承@Overridepublic void sound() {System.out.println("汪汪");}
}// class Bird extends Animal {} // 编译错误:Bird不在permits列表中
二、120道面试题目录列表
文章序号 | Java面试题120道 |
---|---|
1 | Java面试题及详细答案120道(01-20) |
2 | Java面试题及详细答案120道(21-40) |
3 | Java面试题及详细答案120道(41-60) |
4 | Java面试题及详细答案120道(61-80) |
5 | Java面试题及详细答案120道(81-100) |
6 | Java面试题及详细答案120道(5101-120) |