Java面试题及详细答案120道之(061-080)

前后端面试题》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。

前后端面试题-专栏总目录

在这里插入图片描述

一、本文面试题目录

61. 什么是Java中的协变返回类型?

原理:协变返回类型允许子类重写父类方法时,返回父类方法返回类型的子类类型(JDK 5+支持),增强代码灵活性。

代码示例

class Animal {}
class Dog extends Animal {}

class AnimalFactory {
    // 父类方法返回Animal
    public Animal createAnimal() {
        return new Animal();
    }
}

class DogFactory extends AnimalFactory {
    // 子类重写方法返回Dog(Animal的子类)
    @Override
    public 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等待resource1
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) { // 线程1获取resource1
                System.out.println("线程1持有resource1,等待resource2");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                synchronized (resource2) { // 等待resource2
                    System.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中的ComparableComparator

原理:两者用于对象排序,核心区别:

  • 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;
    }
    
    @Override
    public 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),用于泛型、集合等场景。

  • 自动装箱:基本类型→包装类(如intInteger,编译期自动调用Integer.valueOf());
  • 自动拆箱:包装类→基本类型(如Integerint,编译期自动调用intValue())。

代码示例

public class WrapperDemo {
    public static void main(String[] args) {
        // 自动装箱:int → Integer
        Integer a = 10; // 等价于 Integer a = Integer.valueOf(10);
        
        // 自动拆箱:Integer → int
        int 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;
    }
    
    @Override
    public 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
        
        // 使用BigDecimal
        BigDecimal a = new BigDecimal("0.1"); // 推荐String构造器
        BigDecimal b = new BigDecimal("0.2");
        BigDecimal sum = a.add(b); // 加法
        System.out.println(sum); // 输出0.3
        
        BigDecimal 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 {
    @Override
    public 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 教程】
1Openlayers 【入门教程】 - 【源代码+示例 300+】
2Leaflet 【入门教程】 - 【源代码+图文示例 150+】
3MapboxGL【入门教程】 - 【源代码+图文示例150+】
4Cesium 【入门教程】 - 【源代码+综合教程 200+】
5threejs【中文API】 - 【源代码+图文示例200+】

71. Java中的WeakReferenceSoftReferencePhantomReference有什么区别?

原理:均为引用类型,与垃圾回收相关,强度从高到低为:强引用 > 软引用 > 弱引用 > 虚引用。

类型特点应用场景
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(); // 触发GC
        
        System.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 + b
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务1执行");
            return 10 + 20;
        });
        
        // 异步任务2:计算c * d
        CompletableFuture<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中的DateTimeFormatterSimpleDateFormat有什么区别?

原理:均用于日期时间格式化,核心区别:

特性SimpleDateFormatDateTimeFormatter(JDK 8+)
线程安全非线程安全(内部有共享变量)线程安全(不可变)
适用类型DateLocalDateTime等新日期类型
灵活性较低较高(支持自定义格式、本地化)

代码示例

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,返回类型String
        MethodType 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():创建非空Optionalnull则抛异常);
  • ofNullable():创建可空Optional
  • orElse()null时返回默认值;
  • ifPresent():值存在时执行操作。

代码示例

import java.util.Optional;

public class OptionalDemo {
    public static void main(String[] args) {
        String str = null;
        
        // 避免NullPointerException
        Optional<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中的StackOverflowErrorOutOfMemoryError

原理:均为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[] numbers
        int total = 0;
        for (int num : numbers) {
            total += num;
        }
        return total;
    }
    
    public static void main(String[] args) {
        System.out.println(sum(1, 2, 3)); // 输出6
        System.out.println(sum(10, 20)); // 输出30
        System.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子类,不可再继承
    @Override
    public void sound() {
        System.out.println("喵喵");
    }
}

non-sealed class Dog extends Animal { // 非密封子类,可被继承
    @Override
    public void sound() {
        System.out.println("汪汪");
    }
}

// class Bird extends Animal {} // 编译错误:Bird不在permits列表中

二、120道面试题目录列表

文章序号Java面试题120道
1Java面试题及详细答案120道(01-20)
2Java面试题及详细答案120道(21-40)
3Java面试题及详细答案120道(41-60)
4Java面试题及详细答案120道(61-80)
5Java面试题及详细答案120道(81-100)
6Java面试题及详细答案120道(5101-120)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

还是大剑师兰特

打赏一杯可口可乐

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值