java基础100道面试题
一、Java基础概念
1. Java的三大特性是什么?解释其含义。
Java的三大特性是封装、继承和多态:
-  
封装(Encapsulation):将数据(属性)和操作数据的方法绑定在一起,形成一个类。通过访问修饰符(如
private)限制对某些成员的直接访问,增强安全性。 -  
继承(Inheritance):子类可以继承父类的属性和方法,从而实现代码复用和扩展性。
 -  
多态(Polymorphism):同一操作作用于不同的对象时,可以有不同的解释,产生不同的执行结果。主要通过方法重载和方法重写实现。
 
2. Java中 == 和 equals() 的区别?
 
-  
==:用于比较两个变量或对象的引用是否相同。对于基本数据类型,比较的是值;对于引用数据类型,比较的是内存地址。 -  
equals():默认情况下与==相同(比较地址),但可以通过重写来比较对象的内容。例如,String类重写了equals()方法,用于比较字符串内容是否相等。 
3. String、StringBuilder 和 StringBuffer 的区别?
| 特性/类名 | 是否可变 | 线程安全 | 性能 | 
|---|---|---|---|
| String | 不可变 | 是 | 最低 | 
| StringBuilder | 可变 | 否 | 较高 | 
| StringBuffer | 可变 | 是 | 中等 | 
-  
String:不可变对象,每次修改都会生成新的对象,适合少量字符串操作。
 -  
StringBuilder:可变对象,线程不安全,适合单线程环境下的高效字符串操作。
 -  
StringBuffer:可变对象,线程安全,适合多线程环境下的字符串操作。
 
4. Java中的基本数据类型有哪些?对应的包装类是什么?
| 基本数据类型 | 包装类 | 
|---|---|
byte | Byte | 
short | Short | 
int | Integer | 
long | Long | 
float | Float | 
double | Double | 
char | Character | 
boolean | Boolean | 
5. 自动装箱(Autoboxing)和拆箱(Unboxing)是什么?
-  
自动装箱(Autoboxing):将基本数据类型自动转换为其对应的包装类。例如:
int i = 10; Integer obj = i; -  
拆箱(Unboxing):将包装类自动转换为对应的基本数据类型。例如:
Integer obj = new Integer(10); int i = obj; 
6. final关键字的作用(修饰类、方法、变量)?
-  
修饰类:被
final修饰的类不能被继承。 -  
修饰方法:被
final修饰的方法不能被子类重写。 -  
修饰变量:被
final修饰的变量是一个常量,只能赋值一次,且不能修改。 
7. static关键字的作用?
-  
静态变量:属于类,所有对象共享同一个静态变量。
 -  
静态方法:可以直接通过类名调用,不需要创建对象。
 -  
静态代码块:在类加载时执行,用于初始化静态资源。
 
8. Java是否支持多继承?如何实现类似多继承的功能?
-  
Java不支持类的多继承,即一个类只能继承一个父类。
 -  
实现类似多继承的功能:通过接口(Interface)实现。一个类可以实现多个接口,从而达到类似多继承的效果。
 
9. 接口(Interface)和抽象类(Abstract Class)的区别?
| 特性 | 接口(Interface) | 抽象类(Abstract Class) | 
|---|---|---|
| 成员变量 | 默认为public static final | 可以有各种修饰符的变量 | 
| 方法 | 默认为public abstract | 可以有具体实现的方法 | 
| 继承关系 | 类可以实现多个接口 | 类只能继承一个抽象类 | 
| 构造方法 | 没有构造方法 | 可以有构造方法 | 
| 使用场景 | 定义规范 | 提供部分实现和模板 | 
10. 什么是不可变对象(Immutable Object)?举例说明。
-  
不可变对象:一旦创建后,其状态(属性值)不能被修改的对象。
 -  
特点:
-  
所有字段必须是
final且不可变。 -  
不提供任何修改对象状态的方法。
 -  
如果需要“修改”,则返回一个新的对象。
 
 -  
 -  
示例:
-  
Java中的
String、Integer等包装类都是不可变对象。 -  
示例代码:
String str = "hello"; str.concat(" world"); // 不会改变str,而是返回一个新的字符串对象 System.out.println(str); // 输出 "hello" 
 -  
 
二、面向对象编程
1. 重载(Overload)和重写(Override)的区别?
| 特性 | 重载(Overload) | 重写(Override) | 
|---|---|---|
| 定义位置 | 同一个类中 | 子类中 | 
| 方法签名 | 参数列表不同 | 参数列表必须相同 | 
| 返回值类型 | 可以不同 | 必须相同或兼容(协变返回类型) | 
| 访问修饰符 | 不影响 | 子类不能更严格 | 
| 示例 | void show(int a) 和 void show(String a) | 父类方法被子类重新实现 | 
2. 构造方法能否被重写?
-  
不能。构造方法没有返回值,也不能使用
@Override注解,因此无法被重写。 -  
可以重载:同一个类中可以定义多个构造方法,只要参数列表不同即可。
 
3. 如何实现对象克隆?深拷贝和浅拷贝的区别?
-  
浅拷贝:只复制对象本身,引用类型的成员变量仍然指向原来的内存地址。
 -  
深拷隆:不仅复制对象本身,还递归地复制引用类型的成员变量,确保新对象与原对象完全独立。
 
实现方式:
-  
实现
Cloneable接口,并重写clone()方法。 -  
示例代码:
 
class Person implements Cloneable {
    private String name;
    private int age;
    public Object clone() throws CloneNotSupportedException {
        return super.clone(); // 浅拷贝
    }
}
// 深拷贝示例
class Student implements Cloneable {
    private String name;
    private Address address; // 引用类型
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student cloned = (Student) super.clone(); // 浅拷贝
        cloned.address = (Address) this.address.clone(); // 手动深拷贝
        return cloned;
    }
} 
4. 类的初始化顺序(静态块、实例块、构造方法)?
类的初始化顺序如下:
-  
加载类时:执行静态变量初始化和静态代码块(按代码出现顺序)。
 -  
创建对象时:
-  
先执行实例变量初始化。
 -  
再执行实例代码块(按代码出现顺序)。
 -  
最后执行构造方法。
 
 -  
 
示例代码:
class Example {
    static {
        System.out.println("静态代码块");
    }
    {
        System.out.println("实例代码块");
    }
    Example() {
        System.out.println("构造方法");
    }
    public static void main(String[] args) {
        new Example();
    }
} 
输出:
静态代码块 实例代码块 构造方法
5. 什么是多态?如何通过代码体现?
-  
多态:同一操作作用于不同的对象时,产生不同的执行结果。
 -  
实现方式:通过继承和方法重写实现。
 -  
代码示例:
 
class Animal {
    void sound() {
        System.out.println("动物发出声音");
    }
}
class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("汪汪");
    }
}
class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("喵喵");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal a1 = new Dog(); // 多态
        a1.sound(); // 输出 "汪汪"
        Animal a2 = new Cat(); // 多态
        a2.sound(); // 输出 "喵喵"
    }
} 
6. 解释Java中的访问修饰符(public、protected、default、private)。
| 修饰符 | 同一包内 | 子类(不同包) | 不同包且非子类 | 
|---|---|---|---|
public | √ | √ | √ | 
protected | √ | √ | × | 
default | √ | × | × | 
private | × | × | × | 
7. 如何防止类被继承?
-  
使用
final关键字修饰类,例如: 
public final class MyClass {
    // 类不能被继承
} 
8. 内部类有哪些类型?各自的特点是什么?
-  
成员内部类:定义在类内部,作为外部类的成员,可以访问外部类的所有成员。
 -  
静态内部类:使用
static修饰,属于外部类本身,不依赖于外部类实例。 -  
局部内部类:定义在方法或代码块中,只能在定义它的方法或代码块中使用。
 -  
匿名内部类:没有名字的内部类,通常用于简化代码,常用于事件监听器等场景。
 
9. 匿名内部类能否访问外部类的非final变量?
 
-  
不能直接访问外部类的非
final局部变量。 -  
原因:匿名内部类的对象可能比外部方法的生命周期更长,为了保证安全性,JVM要求匿名内部类只能访问
final或effectively final(从Java 8开始支持)的局部变量。 
10. 什么是单例模式?写出线程安全的实现方式。
-  
单例模式:确保一个类只有一个实例,并提供全局访问点。
 
线程安全的实现方式:
-  
饿汉式(线程安全,但可能会浪费资源):
 
public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
} 
-  
懒汉式 + 双重检查锁定(线程安全且延迟加载):
 
public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
} 
-  
静态内部类(推荐,线程安全且延迟加载):
 
public class Singleton {
    private Singleton() {}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
} 
三、集合框架
1. ArrayList 和 LinkedList 的区别及适用场景?
| 特性 | ArrayList | LinkedList | 
|---|---|---|
| 底层实现 | 基于动态数组 | 基于双向链表 | 
| 随机访问效率 | 高(通过索引直接访问) | 低(需要遍历节点) | 
| 插入/删除效率 | 低(可能涉及大量元素移动) | 高(只需修改指针) | 
| 内存占用 | 较小 | 较大(每个节点包含额外的指针) | 
| 适用场景 | 需要频繁随机访问的场景 | 需要频繁插入和删除操作的场景 | 
2. HashMap 的工作原理?如何解决哈希冲突?
-  
工作原理:
-  
HashMap使用哈希表存储键值对。 -  
根据键的
hashCode()计算哈希值,确定存储位置。 -  
如果发生哈希冲突(多个键映射到同一个桶),则使用链表或红黑树存储这些键值对。
 
 -  
 -  
解决哈希冲突:
-  
默认情况下,使用链表解决冲突。
 -  
当链表长度超过阈值(默认为8)且当前桶数量大于64时,链表会转换为红黑树以提高查找效率。
 
 -  
 
3. HashMap 和 Hashtable 的区别?
| 特性 | HashMap | Hashtable | 
|---|---|---|
| 线程安全 | 不是线程安全的 | 是线程安全的 | 
| 允许 null 键值 | 允许一个 null 键和多个 null 值 | 不允许 null 键和 null 值 | 
| 性能 | 更高(非同步) | 较低(同步) | 
| 继承关系 | 实现 Map 接口 | 继承 Dictionary 类 | 
4. ConcurrentHashMap 如何保证线程安全?
-  
分段锁机制(Java 7):将整个哈希表分为多个段(Segment),每个段独立加锁,从而减少锁竞争。
 -  
CAS + synchronized(Java 8):使用 CAS 操作和 synchronized 锁来保证线程安全,避免了分段锁的设计,提升了性能。
 
5. HashSet 的底层实现是什么?
-  
HashSet底层基于HashMap实现。 -  
每个元素作为
HashMap的键,而值是一个固定的对象(PRESENT)。 -  
因此,
HashSet的特性与HashMap的键一致:无序、不允许重复。 
6. Comparable 和 Comparator 的区别?
| 特性 | Comparable | Comparator | 
|---|---|---|
| 定义方式 | 在类中实现 Comparable 接口 | 作为独立的比较器类实现 | 
| 自然排序 | 提供自然排序规则 | 可以提供多种自定义排序规则 | 
| 灵活性 | 单一排序规则 | 多种排序规则 | 
| 使用场景 | 对象本身具有明确的排序规则时 | 需要多种排序规则时 | 
7. Iterator 和 ListIterator 的区别?
| 特性 | Iterator | ListIterator | 
|---|---|---|
| 遍历方向 | 只能单向遍历(向前) | 可以双向遍历(向前和向后) | 
| 功能 | 只能删除元素 | 支持添加、删除和替换元素 | 
| 适用范围 | 所有集合 | 仅适用于 List 集合 | 
8. fail-fast 和 fail-safe 机制的区别?
| 特性 | fail-fast | fail-safe | 
|---|---|---|
| 定义 | 快速失败机制,当检测到集合被修改时抛出异常 | 安全失败机制,允许在并发修改时正常运行 | 
| 实现方式 | 使用 modCount 检测修改次数 | 使用副本机制(如 CopyOnWriteArrayList) | 
| 适用场景 | 单线程环境 | 多线程环境 | 
9. 如何实现一个不可变的集合?
-  
使用
Collections.unmodifiableXXX()方法包装集合: 
List<String> list = new ArrayList<>();
list.add("A");
List<String> unmodifiableList = Collections.unmodifiableList(list); 
-  
自定义实现:确保集合内部状态不可修改,并且不暴露任何可修改的方法。
 
10. ArrayList 的默认初始容量是多少?扩容机制是什么?
-  
默认初始容量:10。
 -  
扩容机制:
-  
当容量不足时,扩容为原容量的 1.5 倍(
newCapacity = oldCapacity + (oldCapacity >> 1))。 -  
创建一个新的数组,并将原有元素复制到新数组中。
 
 -  
 
四、异常处理
1. Java 异常分类(Error 和 Exception 的区别)?
-  
Error:
-  
表示 JVM 系统级错误或资源耗尽等严重问题,程序无法处理。
 -  
示例:
OutOfMemoryError、StackOverflowError。 
 -  
 -  
Exception:
-  
表示程序运行时发生的异常情况,可以通过代码捕获和处理。
 -  
分为 受检异常(Checked Exception) 和 非受检异常(Unchecked Exception):
-  
受检异常:必须在代码中显式处理(如
IOException)。 -  
非受检异常:无需强制处理(如
RuntimeException及其子类)。 
 -  
 
 -  
 
2. try-catch-finally 中,finally 是否一定会执行?
-  
通常情况下会执行:
-  
finally块无论是否发生异常都会执行。 
 -  
 -  
例外情况:
-  
程序提前终止(如调用
System.exit())。 -  
finally块中发生死循环或抛出其他异常。 
 -  
 
3. throw 和 throws 的区别?
| 特性 | throw | throws | 
|---|---|---|
| 用途 | 用于抛出具体的异常对象 | 用于声明方法可能抛出的异常类型 | 
| 使用位置 | 方法体内 | 方法签名后 | 
| 示例 | throw new NullPointerException(); | public void method() throws IOException {} | 
4. 什么是自定义异常?如何实现?
-  
自定义异常:根据业务需求定义的异常类,继承自
Exception或RuntimeException。 -  
实现方式:
 
// 自定义受检异常
class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}
// 自定义非受检异常
class MyRuntimeException extends RuntimeException {
    public MyRuntimeException(String message) {
        super(message);
    }
} 
5. try-with-resources 的作用及使用场景?
-  
作用:自动关闭实现了
AutoCloseable接口的对象(如文件流、数据库连接等),避免手动调用close()方法。 -  
使用场景:
 
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    String line = br.readLine();
} catch (IOException e) {
    e.printStackTrace();
} 
6. OutOfMemoryError 和 StackOverflowError 的区别?
| 特性 | OutOfMemoryError | StackOverflowError | 
|---|---|---|
| 原因 | 内存不足 | 栈溢出(递归过深或栈帧过大) | 
| 触发场景 | 创建大对象、加载大量数据等 | 无限递归调用 | 
7. 列举常见的 RuntimeException?
-  
NullPointerException -  
ArrayIndexOutOfBoundsException -  
ClassCastException -  
IllegalArgumentException -  
ArithmeticException -  
NumberFormatException -  
IllegalStateException 
8. 为什么推荐捕获具体异常而非 Exception?
-  
捕获具体异常可以更精确地处理不同类型的异常,避免掩盖潜在问题。
 -  
示例:
 
try {
    // 可能抛出多种异常的代码
} catch (FileNotFoundException e) {
    // 处理文件未找到的情况
} catch (IOException e) {
    // 处理其他 I/O 错误
} 
9. final、finally 和 finalize() 的区别?
| 特性 | final | finally | finalize() | 
|---|---|---|---|
| 作用 | 修饰变量、方法或类,表示不可修改 | 异常处理机制,确保代码块执行 | 对象销毁前的清理操作 | 
| 使用场景 | 定义常量、防止覆盖等 | 确保资源释放 | 已被废弃,不推荐使用 | 
10. 异常处理对性能的影响有哪些?
-  
影响性能的原因:
-  
异常处理机制涉及栈的展开和恢复,开销较大。
 -  
频繁抛出异常会显著降低程序性能。
 
 -  
 -  
优化建议:
-  
避免在正常逻辑中频繁使用异常。
 -  
使用条件判断代替异常捕获来处理常见错误。
 
 -  
 
五、多线程与并发
1. 线程的创建方式有哪些?
-  
继承
Thread类:class MyThread extends Thread { public void run() { System.out.println("Thread running"); } } new MyThread().start(); -  
实现
Runnable接口:class MyRunnable implements Runnable { public void run() { System.out.println("Runnable running"); } } new Thread(new MyRunnable()).start(); -  
实现
Callable接口(支持返回值和抛出异常):Callable<Integer> task = () -> { return 42; }; FutureTask<Integer> futureTask = new FutureTask<>(task); new Thread(futureTask).start(); 
2. Runnable 和 Callable 的区别?
| 特性 | Runnable | Callable | 
|---|---|---|
| 返回值 | 无返回值 | 支持返回值 | 
| 异常处理 | 不能抛出受检异常 | 可以抛出受检异常 | 
| 使用场景 | 适合简单任务 | 适合需要返回结果的任务 | 
3. 线程的生命周期有哪些状态?
-  
新建(New):线程对象被创建,但尚未启动。
 -  
可运行(Runnable):线程已启动,等待 CPU 调度。
 -  
阻塞(Blocked):线程因竞争锁而被阻塞。
 -  
等待(Waiting/ Timed Waiting):线程调用
wait()或sleep()等方法进入等待状态。 -  
终止(Terminated):线程执行完毕或异常退出。
 
4. sleep() 和 wait() 的区别?
| 特性 | sleep() | wait() | 
|---|---|---|
| 是否释放锁 | 不释放 | 释放 | 
| 调用位置 | 可在任何地方调用 | 必须在同步代码块中调用 | 
| 用途 | 暂停线程一段时间 | 让线程等待其他线程的通知 | 
5. synchronized 关键字的用法(修饰方法、代码块)?
-  
修饰方法:
-  
对实例方法加锁:锁为当前对象(
this)。 -  
对静态方法加锁:锁为类的
Class对象。 
 -  
 -  
修饰代码块:
synchronized (lockObject) { // 同步代码块 } 
6. volatile 关键字的作用?
-  
作用:
-  
保证变量的可见性(线程间共享变量的修改能及时更新到主内存)。
 -  
禁止指令重排序。
 
 -  
 -  
局限性:
-  
不保证原子性,适用于单个变量的读写操作。
 
 -  
 
7. 什么是线程安全?如何保证线程安全?
-  
线程安全:多个线程访问共享资源时,程序的行为符合预期。
 -  
实现方式:
-  
使用同步机制(如
synchronized、ReentrantLock)。 -  
使用不可变对象。
 -  
使用线程本地存储(
ThreadLocal)。 -  
使用并发集合(如
ConcurrentHashMap)。 
 -  
 
8. ReentrantLock 和 synchronized 的区别?
| 特性 | ReentrantLock | synchronized | 
|---|---|---|
| 功能扩展 | 支持公平锁、中断等待等 | 功能有限 | 
| 性能 | 更灵活,可能更高 | 简单高效 | 
| 使用复杂度 | 需要显式加锁和解锁 | 自动管理锁 | 
9. 线程池的核心参数有哪些?工作原理是什么?
-  
核心参数:
-  
corePoolSize:核心线程数。 -  
maximumPoolSize:最大线程数。 -  
keepAliveTime:空闲线程存活时间。 -  
workQueue:任务队列。 -  
RejectedExecutionHandler:拒绝策略。 
 -  
 -  
工作原理:
-  
当提交任务时,如果线程数小于核心线程数,则创建新线程执行任务。
 -  
如果线程数达到核心线程数且队列未满,则将任务放入队列。
 -  
如果队列已满且线程数小于最大线程数,则创建新线程。
 -  
如果无法处理任务,则触发拒绝策略。
 
 -  
 
10. ThreadLocal 的作用及可能的内存泄漏问题?
-  
作用:为每个线程提供独立的变量副本,避免线程间数据共享。
 -  
内存泄漏问题:
-  
ThreadLocal使用弱引用存储键值对,但值是强引用。 -  
如果不手动清理
ThreadLocal,可能导致线程池中的线程持有无效的引用,造成内存泄漏。 
 -  
 
11. 什么是死锁?如何避免死锁?
-  
死锁:两个或多个线程互相等待对方释放资源,导致所有线程都无法继续执行。
 -  
避免死锁:
-  
按固定顺序加锁。
 -  
使用超时机制。
 -  
减少锁的粒度。
 
 -  
 
12. ConcurrentHashMap 如何实现高并发?
-  
分段锁机制(Java 7):将哈希表分为多个段,每个段独立加锁。
 -  
CAS + synchronized(Java 8):使用 CAS 操作和细粒度锁,减少锁竞争。
 
13. AtomicInteger 的实现原理?
-  
基于 CAS(Compare-And-Swap)操作实现线程安全的原子操作。
 -  
内部维护一个
volatile变量,确保可见性。 
14. CountDownLatch 和 CyclicBarrier 的区别?
| 特性 | CountDownLatch | CyclicBarrier | 
|---|---|---|
| 重用性 | 不可重用 | 可重用 | 
| 用途 | 等待多个线程完成 | 等待多个线程到达屏障点 | 
15. 如何实现生产者-消费者模式?
-  
基于阻塞队列:
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);  class Producer implements Runnable { public void run() { try { while (true) { queue.put(produce()); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }  private Integer produce() { return 1; } }  class Consumer implements Runnable { public void run() { try { while (true) { consume(queue.take()); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }  private void consume(Integer item) { System.out.println("Consumed: " + item); } } 
六、IO与NIO
1. Java IO流的分类(字节流、字符流)?
-  
字节流:
-  
基本单位是字节(
byte),适用于处理二进制数据。 -  
输入流:
InputStream,输出流:OutputStream。 
 -  
 -  
字符流:
-  
基本单位是字符(
char),适用于处理文本数据。 -  
输入流:
Reader,输出流:Writer。 
 -  
 
2. BIO、NIO 和 AIO 的区别?
| 特性 | BIO | NIO | AIO | 
|---|---|---|---|
| 模型 | 阻塞式 I/O | 非阻塞式 I/O | 异步 I/O | 
| 多路复用 | 每个连接一个线程 | 单线程管理多个连接 | 系统负责调度 | 
| 适用场景 | 小规模连接 | 大规模连接 | 高并发场景 | 
3. FileInputStream 和 FileReader 的区别?
| 特性 | FileInputStream | FileReader | 
|---|---|---|
| 数据类型 | 字节流 | 字符流 | 
| 用途 | 读取二进制文件 | 读取文本文件 | 
| 编码问题 | 不涉及编码 | 可能涉及编码转换 | 
4. 什么是 NIO 中的 Channel、Buffer 和 Selector?
-  
Channel:
-  
类似于传统的流,但支持双向读写操作。
 -  
常见实现:
FileChannel、SocketChannel。 
 -  
 -  
Buffer:
-  
数据容器,用于存储和操作数据。
 -  
常见类型:
ByteBuffer、CharBuffer、IntBuffer。 
 -  
 -  
Selector:
-  
用于监听多个通道的事件(如连接请求、数据到达等),实现多路复用。
 
 -  
 
5. 序列化和反序列化的作用?如何实现?
-  
作用:
-  
序列化:将对象转换为字节流,便于存储或传输。
 -  
反序列化:将字节流还原为对象。
 
 -  
 -  
实现方式:
-  
实现
Serializable接口:class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; // Getters and setters } // 序列化 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) { oos.writeObject(new Person("Alice", 25)); } // 反序列化 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) { Person person = (Person) ois.readObject(); } 
 -  
 
6. transient 关键字的作用?
-  
作用:标记某个字段不参与序列化过程。
 -  
示例:
class Person implements Serializable { private String name; private transient String password; // 不会被序列化 } 
7. 什么是零拷贝(Zero Copy)?
-  
零拷贝:减少数据在内核态和用户态之间的拷贝次数,提高 I/O 性能。
 -  
实现机制:
-  
使用
sendfile()或mmap()等系统调用。 -  
数据直接从磁盘缓冲区发送到网络接口,无需经过用户态。
 
 -  
 
8. 如何读取大文件(如 10GB)而不内存溢出?
-  
方法:
-  
使用缓冲流(
BufferedReader或BufferedInputStream)分块读取。 -  
使用 NIO 的
MappedByteBuffer映射文件到内存。 
 -  
 -  
示例代码:
// 使用 BufferedReader 分块读取 try (BufferedReader br = new BufferedReader(new FileReader("large_file.txt"))) { String line; while ((line = br.readLine()) != null) { processLine(line); // 处理每一行 } }  // 使用 MappedByteBuffer try (FileChannel channel = FileChannel.open(Paths.get("large_file.bin"), StandardOpenOption.READ)) { MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); byte[] data = new byte[buffer.limit()]; buffer.get(data); processData(data); // 处理数据 } 
七、JVM与内存管理
1. JVM 内存区域划分(堆、栈、方法区等)?
-  
程序计数器:记录当前线程执行的字节码指令地址。
 -  
虚拟机栈:存储局部变量表、操作数栈、动态链接等信息,每个线程独占。
 -  
本地方法栈:为 JNI(Native 方法)服务。
 -  
堆:存放对象实例和数组,所有线程共享。
 -  
方法区:存储类信息、常量池、静态变量等,也称元空间(Metaspace)。
 -  
直接内存:非 JVM 内部内存,用于 NIO 的
ByteBuffer等。 
2. 对象创建的过程是什么?
-  
类加载检查:检查对象对应的
.class文件是否已加载。 -  
分配内存:在堆中分配内存(可能涉及垃圾回收或线程同步)。
 -  
初始化零值:将对象字段初始化为默认值。
 -  
执行构造方法:调用
<init>方法完成对象初始化。 
3. 垃圾回收算法有哪些?解释标记-清除、复制、标记-整理。
-  
标记-清除:
-  
标记所有存活对象,然后清除未被标记的对象。
 -  
缺点:会产生内存碎片。
 
 -  
 -  
复制:
-  
将存活对象复制到另一个区域,清理原区域。
 -  
缺点:需要两倍内存空间。
 
 -  
 -  
标记-整理:
-  
标记存活对象,并将它们移动到一端,清理尾部空间。
 -  
优点:避免内存碎片。
 
 -  
 
4. 什么是 GC Roots?哪些对象可以作为 GC Roots?
-  
GC Roots:垃圾回收的起点,从这些对象开始遍历引用链。
 -  
常见的 GC Roots:
-  
虚拟机栈中的局部变量。
 -  
方法区中的类静态属性。
 -  
方法区中的常量。
 -  
本地方法栈中的 JNI 引用。
 
 -  
 
5. 强引用、软引用、弱引用、虚引用的区别?
| 引用类型 | 特性 | 
|---|---|
| 强引用 | 普通引用,对象不会被回收。 | 
| 软引用 | 内存不足时会被回收,适用于缓存场景。 | 
| 弱引用 | GC 时一定会被回收,适用于临时对象。 | 
| 虚引用 | 不会阻止对象被回收,仅用于跟踪对象的回收状态。 | 
6. 如何判断对象是否可被回收?
-  
如果对象无法通过任何引用链到达 GC Roots,则认为该对象可被回收。
 -  
判断过程分为两步:
-  
引用可达性分析:检查是否有引用指向该对象。
 -  
finalize() 方法:如果对象实现了
finalize()方法且未被调用,会在回收前尝试复活。 
 -  
 
7. 常见的垃圾收集器有哪些(如 CMS、G1)?
| 收集器名称 | 特点 | 
|---|---|
| Serial | 单线程,适合单核 CPU 和小内存环境。 | 
| ParNew | Serial 的多线程版本。 | 
| Parallel Scavenge | 注重吞吐量,适合科学计算等场景。 | 
| CMS | 低延迟,适合交互式应用,但会产生内存碎片。 | 
| G1 | 分代收集,注重平衡吞吐量和延迟,适合大内存环境。 | 
| ZGC | 高并发、低延迟,适合超大内存环境。 | 
| Shenandoah | 类似 ZGC,提供更低的暂停时间。 | 
8. 什么是类加载机制?类加载过程有哪些阶段?
-  
类加载机制:将
.class文件加载到 JVM 中并生成对应的Class对象。 -  
类加载过程:
-  
加载:从文件系统或网络中读取字节码。
 -  
验证:确保字节码格式正确且符合 JVM 规范。
 -  
准备:为类的静态变量分配内存并设置默认值。
 -  
解析:将符号引用替换为直接引用。
 -  
初始化:执行静态代码块和静态变量初始化。
 
 -  
 
9. 双亲委派模型是什么?如何打破它?
-  
双亲委派模型:
-  
加载类时优先由父类加载器加载,只有父类加载器无法加载时才由子类加载器加载。
 -  
目的是保证类的唯一性和安全性。
 
 -  
 -  
打破方式:
-  
使用自定义类加载器覆盖
loadClass()方法,改变加载顺序。 
 -  
 
10. OutOfMemoryError 的可能原因及解决方案?
| 错误类型 | 原因 | 解决方案 | 
|---|---|---|
| Java Heap Space | 堆内存不足 | 增加 -Xmx 参数 | 
| PermGen/Metaspace | 方法区或元空间不足 | 增加 -XX:MaxMetaspaceSize 参数 | 
| Direct Memory | 直接内存不足 | 增加 -XX:MaxDirectMemorySize 参数 | 
| StackOverflowError | 栈深度过大 | 增加 -Xss 参数 | 
11. JVM 调优的常用参数有哪些?
-  
堆内存设置:
-  
-Xms:初始堆大小。 -  
-Xmx:最大堆大小。 
 -  
 -  
新生代设置:
-  
-Xmn:新生代大小。 
 -  
 -  
垃圾收集器选择:
-  
-XX:+UseG1GC:启用 G1 收集器。 -  
-XX:+UseConcMarkSweepGC:启用 CMS 收集器。 
 -  
 -  
其他参数:
-  
-XX:MaxMetaspaceSize:元空间最大大小。 -  
-XX:+PrintGCDetails:打印垃圾回收详细信息。 
 -  
 
八、JDBC与数据库
1. JDBC 操作数据库的基本步骤?
-  
加载驱动:使用
Class.forName()加载数据库驱动。 -  
建立连接:通过
DriverManager.getConnection()获取数据库连接。 -  
创建语句对象:使用
Connection创建Statement或PreparedStatement。 -  
执行 SQL:调用
executeQuery()或executeUpdate()执行查询或更新操作。 -  
处理结果集:遍历
ResultSet获取查询结果。 -  
关闭资源:关闭
ResultSet、Statement和Connection。 
示例代码:
try {
    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT * FROM users");
    while (rs.next()) {
        System.out.println(rs.getString("name"));
    }
    rs.close();
    stmt.close();
    conn.close();
} catch (Exception e) {
    e.printStackTrace();
} 
2. Statement 和 PreparedStatement 的区别?
| 特性 | Statement | PreparedStatement | 
|---|---|---|
| SQL 注入防护 | 容易受到 SQL 注入攻击 | 自动防止 SQL 注入 | 
| 性能 | 每次执行都需要编译 SQL | 预编译 SQL,性能更高 | 
| 参数化支持 | 不支持参数化 | 支持参数化查询 | 
| 适用场景 | 简单的静态 SQL | 动态 SQL 或频繁执行的 SQL | 
3. 什么是数据库连接池?为什么使用它?
-  
数据库连接池:预先创建一组数据库连接并管理它们的生命周期,避免频繁创建和销毁连接。
 -  
优点:
-  
提高性能:减少连接创建和关闭的开销。
 -  
控制资源:限制最大连接数,防止资源耗尽。
 -  
简化管理:统一管理和复用连接。
 
 -  
 
4. 事务的 ACID 特性是什么?
-  
Atomicity(原子性):事务中的所有操作要么全部成功,要么全部失败。
 -  
Consistency(一致性):事务执行前后,数据库状态保持一致。
 -  
Isolation(隔离性):多个事务并发执行时,彼此隔离,互不干扰。
 -  
Durability(持久性):事务提交后,其结果永久保存。
 
5. 什么是脏读、不可重复读、幻读?
| 问题 | 描述 | 
|---|---|
| 脏读 | 一个事务读取了另一个未提交事务的数据。 | 
| 不可重复读 | 同一事务中多次读取同一数据,结果不一致。 | 
| 幻读 | 同一事务中多次查询同一条件,结果集发生变化。 | 
6. 如何通过 JDBC 实现事务管理?
-  
步骤:
-  
禁用自动提交:
conn.setAutoCommit(false); -  
执行一系列 SQL 操作。
 -  
如果所有操作成功,调用
conn.commit();提交事务。 -  
如果发生异常,调用
conn.rollback();回滚事务。 -  
最后恢复自动提交:
conn.setAutoCommit(true); 
 -  
 
示例代码:
try {
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
    conn.setAutoCommit(false);
    String sql1 = "UPDATE accounts SET balance = balance - 100 WHERE id = 1";
    String sql2 = "UPDATE accounts SET balance = balance + 100 WHERE id = 2";
    Statement stmt = conn.createStatement();
    stmt.executeUpdate(sql1);
    stmt.executeUpdate(sql2);
    conn.commit();
    System.out.println("Transaction committed successfully.");
} catch (SQLException e) {
    e.printStackTrace();
    try {
        conn.rollback();
        System.out.println("Transaction rolled back.");
    } catch (SQLException ex) {
        ex.printStackTrace();
    }
} finally {
    try {
        conn.setAutoCommit(true);
        conn.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
} 
九、Java 8+ 新特性
1. Lambda 表达式的语法及使用场景?
-  
语法:
(参数列表) -> { 函数体 } -  
示例:
// 无参数,无返回值 () -> System.out.println("Hello");  // 单个参数,无返回值 (x) -> System.out.println(x);  // 多个参数,有返回值 (x, y) -> { return x + y; }; -  
使用场景:
-  
简化函数式接口的实现。
 -  
结合 Stream API 进行集合操作。
 -  
实现事件监听器等回调机制。
 
 -  
 
2. 函数式接口(Functional Interface)是什么?
-  
定义:只包含一个抽象方法的接口。
 -  
注解:可以用
@FunctionalInterface标记。 -  
示例:
@FunctionalInterface interface MyFunctionalInterface { void doSomething(); }  MyFunctionalInterface func = () -> System.out.println("Doing something"); func.doSomething(); 
3. Stream API 的核心操作有哪些?
-  
创建 Stream:
-  
集合类的
stream()方法。 -  
数组通过
Arrays.stream()。 
 -  
 -  
中间操作(惰性求值):
-  
filter:过滤元素。 -  
map:转换元素。 -  
sorted:排序。 -  
distinct:去重。 
 -  
 -  
终端操作(触发执行):
-  
forEach:遍历。 -  
collect:收集结果。 -  
reduce:聚合操作。 -  
count:统计数量。 
 -  
 
示例代码:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
     .filter(name -> name.startsWith("A"))
     .map(String::toUpperCase)
     .forEach(System.out::println); 
4. Optional 类的作用及使用方法?
-  
作用:避免空指针异常,提供更安全的空值处理方式。
 -  
常用方法:
-  
of(T value):创建非空的Optional。 -  
ofNullable(T value):创建可能为空的Optional。 -  
isPresent():判断是否有值。 -  
orElse(T other):如果没有值,返回默认值。 -  
ifPresent(Consumer<? super T> consumer):如果有值,执行指定操作。 
 -  
 
示例代码:
Optional<String> optional = Optional.ofNullable("Hello");
optional.ifPresent(System.out::println);
String result = optional.orElse("Default Value");
System.out.println(result); 
5. 接口的默认方法和静态方法是什么?
-  
默认方法:
-  
使用
default关键字定义,默认方法可以有实现。 -  
解决接口扩展问题,避免破坏现有实现类。
 
 -  
 -  
静态方法:
-  
使用
static关键字定义,可以直接通过接口调用。 
 -  
 
示例代码:
interface MyInterface {
    default void defaultMethod() {
        System.out.println("Default method");
    }
    static void staticMethod() {
        System.out.println("Static method");
    }
}
MyInterface.staticMethod(); // 调用静态方法
MyInterface obj = new MyInterface() {};
obj.defaultMethod(); // 调用默认方法 
6. 方法引用(Method Reference)的四种形式?
-  
对象::实例方法:
user::getName
 -  
类::静态方法:
String::valueOf
 -  
类::实例方法:
String::toLowerCase
 -  
构造器引用:
MyClass::new
 
示例代码:
List<String> names = Arrays.asList("Alice", "Bob");
names.stream().map(String::toUpperCase).forEach(System.out::println); 
7. 什么是 CompletableFuture?如何实现异步编程?
-  
CompletableFuture:用于异步编程,支持链式调用和组合操作。
 -  
核心方法:
-  
supplyAsync:异步执行任务并返回结果。 -  
thenApply:对结果进行转换。 -  
thenAccept:消费结果。 -  
exceptionally:处理异常。 -  
join:阻塞等待结果。 
 -  
 
示例代码:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 42; // 模拟耗时任务
});
future.thenAccept(result -> {
    System.out.println("Result: " + result);
}).exceptionally(ex -> {
    System.err.println("Error: " + ex.getMessage());
    return null;
});
// 阻塞等待结果
int result = future.join();
System.out.println("Final Result: " + result); 
十、设计模式
1. 列举常见的设计模式(至少5种)。
-  
创建型模式:
-  
单例模式(Singleton):确保一个类只有一个实例。
 -  
工厂模式(Factory):提供创建对象的接口,延迟实例化。
 -  
抽象工厂模式(Abstract Factory):提供一组相关或依赖对象的创建接口。
 
 -  
 -  
结构型模式:
-  
适配器模式(Adapter):将一个类的接口转换成客户端期望的另一个接口。
 -  
代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。
 
 -  
 -  
行为型模式:
-  
观察者模式(Observer):定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知。
 
 -  
 
2. 手写单例模式的双重检查锁(Double-Check Locking)实现。
public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {
        // 私有构造函数,防止外部实例化
    }
    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new Singleton(); // 创建实例
                }
            }
        }
        return instance;
    }
} 
-  
volatile 关键字:保证
instance的可见性,防止指令重排序。 
3. 工厂模式的作用及实现方式?
-  
作用:
-  
将对象的创建过程封装起来,降低耦合度。
 -  
提供统一的接口,隐藏具体的实现细节。
 
 -  
 -  
实现方式:
-  
简单工厂模式:
public class CarFactory { public static Car createCar(String type) { if ("BMW".equals(type)) { return new BMW(); } else if ("Mercedes".equals(type)) { return new Mercedes(); } return null; } } -  
工厂方法模式:
public interface CarFactory { Car createCar(); }  public class BMWFactory implements CarFactory { @Override public Car createCar() { return new BMW(); } } 
 -  
 
4. 观察者模式如何实现?
-  
定义:定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知。
 -  
实现方式:
-  
示例代码:
import java.util.ArrayList; import java.util.List;  // 观察者接口 interface Observer { void update(String message); }  // 被观察者 class Subject { private List<Observer> observers = new ArrayList<>();  public void addObserver(Observer observer) { observers.add(observer); }  public void removeObserver(Observer observer) { observers.remove(observer); }  public void notifyObservers(String message) { for (Observer observer : observers) { observer.update(message); } } }  // 具体观察者 class ConcreteObserver implements Observer { @Override public void update(String message) { System.out.println("Received: " + message); } }  // 使用示例 public class Main { public static void main(String[] args) { Subject subject = new Subject(); Observer observer = new ConcreteObserver();  subject.addObserver(observer); subject.notifyObservers("Hello Observers!"); } } 
 -  
 
5. 适配器模式和代理模式的区别?
| 特性 | 适配器模式 | 代理模式 | 
|---|---|---|
| 目的 | 将一个类的接口转换成客户端期望的接口 | 为其他对象提供代理,控制访问 | 
| 适用场景 | 不兼容的接口之间进行适配 | 增强功能或延迟加载 | 
| 实现方式 | 包装目标对象,暴露适配后的接口 | 实现与目标对象相同的接口 | 
示例对比:
-  
适配器模式:
class Adapter implements Target { private Adaptee adaptee;  public Adapter(Adaptee adaptee) { this.adaptee = adaptee; }  @Override public void request() { adaptee.specificRequest(); } } -  
代理模式:
class Proxy implements Subject { private RealSubject realSubject;  @Override public void request() { if (realSubject == null) { realSubject = new RealSubject(); } realSubject.request(); } } 
十一、综合问题
1. String s = new String("abc"); 创建了几个对象?
 
-  
答案:创建了 两个对象。
 -  
解释:
-  
字面量
"abc"会先在字符串常量池中查找,如果不存在则创建一个对象。 -  
new String("abc")明确通过new关键字创建了一个新的String对象。 
 -  
 
2. Integer a = 127; Integer b = 127;,a == b 的结果?为什么?
 
-  
结果:
true。 -  
原因:
-  
Java 中
-128 ~ 127范围内的Integer对象会被缓存(IntegerCache)。 -  
因此,
a和b引用的是同一个缓存对象,==比较的是引用地址,结果为true。 
 -  
 -  
注意:超出范围时(如
Integer a = 128; Integer b = 128;),a == b结果为false,因为会创建不同的对象。 
3. 如何实现 LRU 缓存?
-  
思路:
-  
使用
LinkedHashMap实现,重写removeEldestEntry()方法。 -  
当缓存达到容量限制时,移除最久未使用的元素。
 
 -  
 -  
示例代码:
import java.util.LinkedHashMap; import java.util.Map;  public class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int capacity;  public LRUCache(int capacity) { super(capacity, 0.75f, true); // accessOrder=true 表示按访问顺序排序 this.capacity = capacity; }  @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > capacity; // 超过容量时移除最老的元素 } } 
4. String 为什么是不可变的?
 
-  
原因:
-  
String的值存储在final char[] value中,无法修改。 -  
不可变性保证了线程安全。
 -  
提高性能:字符串常量池可以复用相同的实例。
 -  
安全性:防止敏感信息(如密码)被篡改。
 
 -  
 
5. 什么是反射?如何通过反射创建对象?
-  
反射:在运行时动态获取类的信息并操作类的行为。
 -  
创建对象:
Class<?> clazz = Class.forName("com.example.MyClass"); Object obj = clazz.getDeclaredConstructor().newInstance(); 
6. 泛型擦除是什么?如何绕过泛型擦除?
-  
泛型擦除:编译时,泛型类型信息会被擦除,替换为原始类型(如
List<String>变为List)。 -  
绕过方法:
-  
使用
Class对象保存类型信息。 -  
示例:
public class GenericType<T> { private Class<T> type;  public GenericType(Class<T> type) { this.type = type; }  public boolean isTypeOf(Object obj) { return type.isInstance(obj); } }  GenericType<String> stringType = new GenericType<>(String.class); System.out.println(stringType.isTypeOf("Hello")); // true 
 -  
 
7. 如何实现深拷贝(Deep Copy)?
-  
方式:
-  
手动复制:递归复制对象及其所有子对象。
 -  
序列化:将对象序列化为字节流后再反序列化。
 
 -  
 -  
示例代码:
import java.io.*;  public class DeepCopy implements Serializable { private static <T extends Serializable> T deepCopy(T obj) throws IOException, ClassNotFoundException { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) { oos.writeObject(obj);  try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis)) { return (T) ois.readObject(); } } } } 
8. 如何用 Java 实现一个简单的阻塞队列?
-  
思路:
-  
使用
synchronized或Lock控制并发。 -  
使用
wait()和notify()实现生产者和消费者之间的通信。 
 -  
 -  
示例代码:
import java.util.LinkedList; import java.util.Queue;  public class BlockingQueue<T> { private final Queue<T> queue = new LinkedList<>(); private final int capacity;  public BlockingQueue(int capacity) { this.capacity = capacity; }  public synchronized void put(T item) throws InterruptedException { while (queue.size() >= capacity) { wait(); // 队列满时等待 } queue.add(item); notifyAll(); // 唤醒消费者 }  public synchronized T take() throws InterruptedException { while (queue.isEmpty()) { wait(); // 队列空时等待 } T item = queue.poll(); notifyAll(); // 唤醒生产者 return item; } } 
