1. Java 的三大特性是什么?请分别说明。
答案:
1. 封装(Encapsulation)
定义:隐藏对象内部实现细节,通过
private限制访问,提供getter/setter等公共方法交互作用:降低耦合、提高安全性、便于维护
示例:实体类的属性封装
2. 继承(Inheritance)
定义:子类通过
extends继承父类非私有属性和方法,实现代码复用特点:Java 只支持单继承(避免菱形依赖),可通过接口间接实现多继承
作用:代码复用、扩展功能
3. 多态(Polymorphism)
定义:同一行为的不同表现形式
核心:方法重写(子类覆写父类方法)和向上转型(父类引用指向子类对象)
示例:
List list = new ArrayList<>(),调用list.add()时实际执行 ArrayList 的实现作用:提高代码灵活性和可扩展性
2. String、StringBuffer、StringBuilder 的区别是什么?
答案:
特性 | String | StringBuffer | StringBuilder |
|---|---|---|---|
可变性 | 不可变(final 修饰字符数组) | 可变(数组扩容) | 可变(数组扩容) |
线程安全 | 安全(无修改操作) | 安全(方法加 | 不安全(无锁) |
效率 | 最低(修改会创建新对象) | 中等 | 最高 |
适用场景 | 少量字符串操作 | 多线程环境字符串拼接 | 单线程环境字符串拼接 |
核心区别:
String:不可变对象,每次修改都会创建新对象,适合常量定义
StringBuffer:线程安全的可变字符串,适合多线程环境
StringBuilder:非线程安全的可变字符串,效率最高,适合单线程环境
3. JVM 内存模型包含哪些主要分区?各有什么作用?
答案:
JVM 内存模型(JMM)定义了线程如何通过内存交互,解决多线程可见性、原子性、有序性问题。
核心分区:
程序计数器(Program Counter)
线程私有,记录当前线程执行的字节码行号
无 OOM 可能
虚拟机栈(JVM Stack)
线程私有,存储方法调用的栈帧(局部变量、操作数栈等)
栈深度溢出抛
StackOverflowError,扩容失败抛OutOfMemoryError
本地方法栈(Native Method Stack)
线程私有,为 Native 方法(如
System.currentTimeMillis())提供内存支持可能抛
StackOverflowError/OOM
堆(Heap)
线程共享,存储对象实例和数组
GC 主要区域,分年轻代、老年代
OOM 高频发生区
方法区(Method Area)
线程共享,存储类信息、常量、静态变量等
JDK 8 后用元空间(Metaspace)实现,占用本地内存,默认无上限
4. 什么是 GC(垃圾回收)?常见 GC 算法和收集器有哪些?
答案:
GC 定义
自动回收堆中"不可达对象"(无引用指向的对象)的内存,避免内存泄漏,无需手动释放。
核心算法:
标记-清除(Mark-Sweep)
先标记垃圾,再清除
缺点:效率低、会产生内存碎片
复制算法(Copying)
将内存分为两块,存活对象复制到另一块,清除原块
优点:无碎片、效率高
适用:年轻代默认使用
标记-整理(Mark-Compact)
标记后将存活对象向一端移动,再清除剩余区域
适用:老年代默认使用
常见收集器:
年轻代:
SerialGC:串行,单线程
Parallel Scavenge:并行,注重吞吐量
老年代:
Serial Old:串行
Parallel Old:并行
CMS:并发,注重响应时间
G1:区域分代,兼顾吞吐量和响应时间
5. Java 中实现多线程的三种方式及区别是什么?
答案:
方式 1:继承 Thread 类
class MyThread extends Thread { @Override public void run() { // 线程执行代码 } } // 启动:new MyThread().start();缺点:单继承限制,无法继承其他类
方式 2:实现 Runnable 接口
class MyRunnable implements Runnable { @Override public void run() { // 线程执行代码 } } // 启动:new Thread(new MyRunnable()).start();优点:无继承限制,可实现多个接口
缺点:无法直接获取返回值
方式 3:实现 Callable 接口
class MyCallable implements Callable<String> { @Override public String call() throws Exception { // 线程执行代码,可返回值 return "result"; } } // 启动:FutureTask<String> task = new FutureTask<>(new MyCallable()); // new Thread(task).start();优点:支持返回值和异常处理,适合异步任务
缺点:代码稍复杂
6. 什么是线程安全?如何保证线程安全?
答案:
线程安全定义
多线程并发访问共享资源时,不会出现数据错乱、死锁等问题(比如多线程抢票,不会出现超卖、重复售票)。
实现方式:
锁机制
synchronized:关键字,可修饰方法/代码块,底层是监视器锁ReentrantLock:类锁,支持公平锁/非公平锁、可中断
无锁机制
volatile:修饰变量,保证可见性和有序性,不保证原子性原子类(
AtomicInteger):基于 CAS 实现原子操作
线程封闭
局部变量:线程私有,无共享
ThreadLocal:每个线程独立存储副本,避免共享
并发容器
ConcurrentHashMap:分段锁/CAS 实现线程安全CopyOnWriteArrayList:写时复制,读无锁
7. HashMap 和 ConcurrentHashMap 的区别是什么?(JDK 8+)
答案:
特性 | HashMap | ConcurrentHashMap |
|---|---|---|
线程安全 | 不安全 | 安全(CAS + synchronized) |
null 键/值 | 允许 | 不允许 null 键,允许 null 值 |
迭代器 | Fail-Fast | Weakly Consistent |
锁粒度 | 无锁 | 桶级别锁(JDK 8+) |
性能 | 单线程性能高 | 并发性能高 |
ConcurrentHashMap JDK 8+ 改进:
JDK 7:分段锁(Segment),默认 16 个段
JDK 8+:CAS + synchronized,锁粒度更细(桶级别)
结构:数组 + 链表 + 红黑树,链表长度 > 8 转为红黑树
8. == 和 equals 的区别是什么?
答案:
== 运算符
基本数据类型:比较值是否相同
引用数据类型:比较引用地址是否相同
equals 方法
默认实现:与
==相同,比较引用地址重写后:比较对象内容(如 String、Integer 等类重写了 equals)
示例:
String x = "string"; String y = "string"; String z = new String("string"); System.out.println(x == y); // true(同一引用) System.out.println(x == z); // false(不同引用) System.out.println(x.equals(y)); // true(内容相同) System.out.println(x.equals(z)); // true(内容相同)注意事项:
null值不能调用equals()方法,否则会抛出空指针异常重写
equals()必须重写hashCode()
9. int 和 Integer 的区别是什么?
答案:
特性 | int | Integer |
|---|---|---|
类型 | 基本数据类型 | 包装类(引用类型) |
存储位置 | 栈内存 | 堆内存 |
默认值 | 0 | null |
是否可为 null | 否 | 是 |
泛型支持 | 不支持 | 支持 |
方法 | 无 | 提供丰富方法(如 parseInt、toString) |
装箱与拆箱:
装箱:
Integer a = 10;(自动将 int 转换为 Integer)拆箱:
int b = a;(自动将 Integer 转换为 int)
缓存机制:
Integer 缓存了 -128 到 127 的值,在这个范围内使用==比较返回 true。
10. ArrayList 和 LinkedList 的区别是什么?
答案:
特性 | ArrayList | LinkedList |
|---|---|---|
底层实现 | 动态数组 | 双向链表 |
随机访问 | O(1),效率高 | O(n),效率低 |
插入/删除 | 需要移动元素,效率较低 | 只需修改指针,效率高 |
内存占用 | 初始容量 10,扩容 1.5 倍 | 每个元素存储前驱后继指针 |
线程安全 | 不安全 | 不安全 |
适用场景:
ArrayList:适合增删操作较少、查询操作较多的场景
LinkedList:适合查询操作较少、增删操作较多的场景
扩容机制:
ArrayList:初始容量为 10,扩容为原来的 1.5 倍
LinkedList:无需扩容,动态添加节点