Java 知识总结与面试题
Java 是一门面向对象的编程语言,以其"一次编写,到处运行"的跨平台特性长期占据企业级开发主流地位。本文涵盖从基础到进阶的核心知识点。
一、Java 基础
1. 基本数据类型
| 类型 | 大小 | 默认值 | 范围 | 示例 |
|---|---|---|---|---|
byte |
1 字节 | 0 | -128 ~ 127 | byte a = 10; |
short |
2 字节 | 0 | -32768 ~ 32767 | short b = 100; |
int |
4 字节 | 0 | -2^31 ~ 2^31-1 | int c = 1000; |
long |
8 字节 | 0L | -2^63 ~ 2^63-1 | long d = 1000L; |
float |
4 字节 | 0.0f | 约 ±3.4E-38 ~ ±3.4E+38 | float e = 3.14f; |
double |
8 字节 | 0.0d | 约 ±1.7E-308 ~ ±1.7E+308 | double f = 3.14; |
char |
2 字节 | '\u0000' | 0 ~ 65535 | char g = 'A'; |
boolean |
未明确定义 | false | true / false | boolean h = true; |
注意:
long赋值结尾要加L,float加fchar在 Java 中是 2 字节(Unicode),不是 1 字节
2. 访问修饰符
| 修饰符 | 同类 | 同包 | 子类(不同包) | 所有类 |
|---|---|---|---|---|
private |
✅ | ❌ | ❌ | ❌ |
default(不写) |
✅ | ✅ | ❌ | ❌ |
protected |
✅ | ✅ | ✅ | ❌ |
public |
✅ | ✅ | ✅ | ✅ |
3. OOP 三大特性
封装
- 将数据(属性)和操作数据的方法绑定在一起
- 对外隐藏内部实现,通过 getter/setter 暴露访问
- 用
private+public方法实现
public class User { private String name; // 隐藏内部数据 public String getName() { return name; } public void setName(String name) { if (name != null && name.length() > 0) { this.name = name; } }}继承
- 用
extends关键字实现,Java 只支持单继承 - 子类继承父类的非 private 成员
- 通过
super调用父类构造器和方法
public class Animal { protected String name; public void eat() { System.out.println("Animal is eating"); }}public class Dog extends Animal { public void bark() { System.out.println("Dog is barking"); } @Override public void eat() { super.eat(); // 调用父类方法 System.out.println("Dog is eating"); }}多态
- 同一个方法在不同对象上有不同的行为
- 三要素:继承、方法重写、父类引用指向子类对象
Animal animal = new Dog(); // 父类引用指向子类对象animal.eat(); // 调用的是 Dog 的 eat() 方法// animal.bark(); // ❌ 编译错误,父类引用看不到子类特有方法4. 接口与抽象类
| 对比项 | 抽象类 | 接口 |
|---|---|---|
| 关键字 | abstract class |
interface |
| 构造方法 | 可以有 | 不能有 |
| 成员变量 | 任何类型 | public static final(常量) |
| 方法实现 | 可以有抽象方法和普通方法 | Java 8+ 可以有 default/static 方法 |
| 继承方式 | extends(单继承) |
implements(多实现) |
| 语义 | "是什么"(is-a) | "能做什么"(has-a/can-do) |
// 抽象类:定义一种类型和公共行为public abstract class Shape { protected String color; public abstract double area(); // 抽象方法,子类必须实现 public void setColor(String color) { this.color = color; }}// 接口:定义能力public interface Drawable { void draw(); // 默认 public abstract default void print() { // Java 8+ default 方法 System.out.println("Printing..."); }}选择原则:
- 有关系(is-a)、有共享状态 → 抽象类
- 没关系(can-do)、只定义行为 → 接口
- Java 8 后接口可以有 default 方法,但建议接口仍以"行为契约"为主
5. 重载 vs 重写
| 对比 | 重载(Overload) | 重写(Override) |
|---|---|---|
| 定义 | 同类中方法名相同,参数不同 | 子类重新实现父类方法 |
| 参数 | 必须不同(个数/类型/顺序) | 必须相同 |
| 返回类型 | 可以不同 | 必须相同或子类(协变返回) |
| 访问权限 | 无限制 | 不能比父类更严格 |
| 异常 | 无限制 | 不能抛出比父类更宽泛的异常 |
二、集合框架
1. 集合体系结构
Collection(接口)├── List(有序、可重复)│ ├── ArrayList(数组实现,查询快)│ └── LinkedList(链表实现,增删快)├── Set(无序、不可重复)│ ├── HashSet(哈希表,最快)│ ├── TreeSet(红黑树,有序)│ └── LinkedHashSet(链表+哈希,保持插入顺序)└── Queue(队列) ├── LinkedList └── PriorityQueue(优先队列)Map(接口,键值对)├── HashMap(哈希表,最快)├── TreeMap(红黑树,键有序)├── LinkedHashMap(保持插入顺序)└── ConcurrentHashMap(线程安全)2. ArrayList vs LinkedList
| 对比项 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组 | 双向链表 |
随机访问(get(index)) |
O(1) | O(n) |
| 尾部插入 | O(1) | O(1) |
| 中间插入/删除 | O(n) | O(n)(但找到位置后操作快) |
| 内存占用 | 少(只存数据) | 多(存前后指针) |
| 适用场景 | 查询多、尾部追加 | 频繁头尾操作、队列/栈场景 |
// 常用 APIList<String> list = new ArrayList<>();list.add("A"); // 添加list.get(0); // 取list.remove(0); // 删list.contains("A"); // 是否包含list.size(); // 大小3. HashMap 原理
| 特性 | 说明 |
|---|---|
| 底层结构 | 数组 + 链表 + 红黑树(JDK 8+) |
| 默认容量 | 16 |
| 负载因子 | 0.75(超过 75% 容量时扩容) |
| 扩容倍数 | 2 倍(位移操作优化性能) |
| key 为 null | 允许,放在 table[0] |
| 线程安全 | ❌ 非线程安全,需用 ConcurrentHashMap |
JDK 8+ 优化:
- 链表长度 ≥ 8 时转为红黑树(TREEIFY_THRESHOLD = 8)
- 红黑树节点 ≤ 6 时转回链表(UNTREEIFY_THRESHOLD = 6)
- 引入红黑树是为了解决哈希碰撞严重时链表查询 O(n) 的性能问题
Map<String, Integer> map = new HashMap<>();map.put("key", 1);map.get("key"); // 1map.getOrDefault("key", 0); // 1map.containsKey("key"); // truemap.forEach((k, v) -> System.out.println(k + "=" + v));4. 线程安全集合
| 集合 | 说明 |
|---|---|
ConcurrentHashMap |
分段锁/CAS,高并发首选 |
CopyOnWriteArrayList |
读多写少的场景,写时复制 |
BlockingQueue |
阻塞队列,生产者-消费者模式 |
Collections.synchronizedList() |
同步包装器,性能较低 |
// 推荐用法Map<String, Object> concurrentMap = new ConcurrentHashMap<>();Queue<String> queue = new LinkedBlockingQueue<>();5. Collections 工具类
List<Integer> list = Arrays.asList(3, 1, 4, 1, 5);Collections.sort(list); // 排序Collections.reverse(list); // 反转Collections.shuffle(list); // 打乱Collections.max(list); // 最大值Collections.min(list); // 最小值Collections.binarySearch(list, 3); // 二分查找Collections.unmodifiableList(list); // 返回不可修改的视图三、异常处理
1. 异常体系
Throwable├── Error(不可恢复)│ ├── OutOfMemoryError│ ├── StackOverflowError│ └── NoClassDefFoundError└── Exception(可处理) ├── RuntimeException(非受检异常) │ ├── NullPointerException │ ├── ArrayIndexOutOfBoundsException │ ├── IllegalArgumentException │ └── ArithmeticException └── 受检异常(Checked Exception) ├── IOException ├── SQLException └── FileNotFoundException2. try-catch-finally
try { // 可能抛出异常的代码 FileInputStream fis = new FileInputStream("test.txt"); int data = fis.read();} catch (FileNotFoundException e) { // 处理特定异常 System.err.println("文件未找到: " + e.getMessage());} catch (IOException e) { // 处理更通用的异常 System.err.println("IO 异常: " + e.getMessage());} finally { // 无论是否异常都执行(释放资源) if (fis != null) { try { fis.close(); } catch (IOException e) { /* ignore */ } }}3. try-with-resources(JDK 7+)
自动关闭实现了 AutoCloseable 的资源:
try (FileInputStream fis = new FileInputStream("test.txt"); BufferedReader br = new BufferedReader(new InputStreamReader(fis))) { String line = br.readLine(); System.out.println(line);} catch (IOException e) { System.err.println("IO 异常: " + e.getMessage());}// 无需在 finally 中手动 close,资源自动释放4. 自定义异常
public class BusinessException extends RuntimeException { private Integer code; public BusinessException(Integer code, String message) { super(message); this.code = code; } public Integer getCode() { return code; }}// 使用throw new BusinessException(40001, "用户名已存在");5. 异常处理最佳实践
- 不要捕获
Exception或Throwable:捕获具体的异常类型 - 不要吞没异常:至少打印日志
- 不要用异常控制业务流程:if-else 能解决的问题不要用 try-catch
- 尽早抛出,延迟捕获:底层抛异常,上层统一处理
- 在合适层级处理:Web 项目用
@ControllerAdvice(Spring Boot)统一处理
四、泛型与注解
1. 泛型基础
// 泛型类public class Box<T> { private T content; public void set(T content) { this.content = content; } public T get() { return content; }}// 泛型方法public static <T> T getMiddle(T... args) { return args[args.length / 2];}// 使用Box<String> stringBox = new Box<>();stringBox.set("Hello");String str = stringBox.get(); // 无需强转2. 泛型擦除
Java 的泛型是编译期的,运行时会被擦除为原始类型:
// 编译时List<String> list1 = new ArrayList<>();List<Integer> list2 = new ArrayList<>();// 运行时(擦除后)// list1 和 list2 都是 ArrayList,类型信息丢失System.out.println(list1.getClass() == list2.getClass()); // true影响:不能 new T()、不能用 instanceof T、不能创建泛型数组。
3. 通配符
// ? extends T — 上界通配符(读数据时用)List<? extends Number> list = new ArrayList<Integer>();Number n = list.get(0); // 可以读,最大到 Number// list.add(1); // ❌ 不能写,因为具体类型未知// ? super T — 下界通配符(写数据时用)List<? super Integer> list2 = new ArrayList<Number>();list2.add(1); // 可以写 Integer// Integer i = list2.get(0); // ❌ 不能读,因为可能是 Object// ? — 无界通配符List<?> list3 = new ArrayList<String>();// list3.add("test"); // ❌ 不能写(null 除外)Object obj = list3.get(0); // 只能读为 ObjectPECS 原则:Producer Extends, Consumer Super(生产者用 extends,消费者用 super)。
4. 常用注解
// 编译时检查@Override // 重写父类方法@SuppressWarnings // 抑制警告@Deprecated // 标记已弃用// Spring 常用@Component // 通用组件@Autowired // 依赖注入@Service // 业务层@Repository // 持久层@Controller // 控制层@Configuration // 配置类@Value // 注入属性值@Transactional // 事务管理// Lombok@Data // 生成 getter/setter/toString/equals/hashCode@Slf4j // 生成 log 对象@Builder // 建造者模式@NoArgsConstructor // 无参构造@AllArgsConstructor// 全参构造五、I/O 与 NIO
1. 字节流与字符流
| 流类型 | 抽象基类 | 常用实现 | 适用场景 |
|---|---|---|---|
| 字节输入流 | InputStream |
FileInputStream |
二进制文件(图片、音频) |
| 字节输出流 | OutputStream |
FileOutputStream |
写入二进制文件 |
| 字符输入流 | Reader |
FileReader、BufferedReader |
文本文件 |
| 字符输出流 | Writer |
FileWriter、BufferedWriter |
写入文本文件 |
// 文本文件读取(推荐)try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); }}// 二进制文件复制try (FileInputStream in = new FileInputStream("source.jpg"); FileOutputStream out = new FileOutputStream("dest.jpg")) { byte[] buffer = new byte[8192]; int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); }}2. BIO / NIO / AIO 对比
| 模型 | 全称 | 特点 | 适用场景 |
|---|---|---|---|
| BIO | Blocking I/O | 同步阻塞,一个连接一个线程 | 连接数少、固定架构 |
| NIO | Non-blocking I/O | 同步非阻塞,多路复用(Selector) | 高并发、短连接 |
| AIO | Asynchronous I/O | 异步非阻塞,回调机制 | 文件 I/O 密集型 |
// NIO 核心组件// Channel — 通道(FileChannel、SocketChannel)// Buffer — 缓冲区(ByteBuffer、CharBuffer)// Selector — 多路复用器// NIO 快速读取文件示例try (RandomAccessFile file = new RandomAccessFile("test.txt", "r"); FileChannel channel = file.getChannel()) { ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = channel.read(buffer); while (bytesRead != -1) { buffer.flip(); // 切换为读模式 while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } buffer.clear(); // 清空,准备下次写入 bytesRead = channel.read(buffer); }}六、多线程与并发
1. 线程创建方式
// 方式 1:继承 Threadclass MyThread extends Thread { @Override public void run() { System.out.println("Thread running"); }}new MyThread().start();// 方式 2:实现 Runnable(推荐)class MyRunnable implements Runnable { @Override public void run() { System.out.println("Runnable running"); }}new Thread(new MyRunnable()).start();// 方式 3:实现 Callable(有返回值)class MyCallable implements Callable<String> { @Override public String call() { return "Callable result"; }}FutureTask<String> task = new FutureTask<>(new MyCallable());new Thread(task).start();String result = task.get(); // 获取返回值// 方式 4:Lambda 简化new Thread(() -> System.out.println("Lambda thread")).start();2. synchronized 与 Lock
// synchronized — 隐式锁public class Counter { private int count = 0; // 同步方法 public synchronized void increment() { count++; } // 同步代码块 public void method() { synchronized (this) { // 临界区 } }}// Lock — 显式锁(更灵活)public class CounterWithLock { private int count = 0; private final Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); // 必须在 finally 中释放 } }}| 对比 | synchronized | Lock |
|---|---|---|
| 用法 | 关键字,自动加解锁 | API,手动加解锁 |
| 锁释放 | 执行完自动释放 | 必须在 finally 中手动释放 |
| 可中断 | ❌ | ✅ lockInterruptibly() |
| 超时 | ❌ | ✅ tryLock(timeout, unit) |
| 公平锁 | ❌ | ✅ ReentrantLock(true) |
| 条件变量 | wait/notify |
Condition.await/signal |
3. 线程池
// 推荐:通过 ThreadPoolExecutor 创建ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // corePoolSize — 核心线程数 5, // maximumPoolSize — 最大线程数 60L, // keepAliveTime — 空闲线程存活时间 TimeUnit.SECONDS, // 时间单位 new LinkedBlockingQueue<>(100), // 工作队列 Executors.defaultThreadFactory(), // 线程工厂 new ThreadPoolExecutor.AbortPolicy() // 拒绝策略);// 用法executor.execute(() -> System.out.println("Task executed"));executor.shutdown(); // 关闭线程池// 不建议用 Executors 创建(阿里巴巴 Java 规范禁止)// Executors.newFixedThreadPool(10); // ❌ 队列无限大,可能 OOM// Executors.newCachedThreadPool(); // ❌ 线程无限多// Executors.newSingleThreadExecutor(); // ❌ 同上拒绝策略:
| 策略 | 行为 |
|---|---|
AbortPolicy(默认) |
抛出 RejectedExecutionException |
CallerRunsPolicy |
提交任务的线程自己执行 |
DiscardPolicy |
丢弃,不抛异常 |
DiscardOldestPolicy |
丢弃最老的任务,再尝试提交 |
4. volatile 关键字
public class Flag { private volatile boolean running = true; public void stop() { running = false; // volatile 保证修改对其他线程立即可见 } public void run() { while (running) { // 不加 volatile,此循环可能永远不会停止(指令重排) } }}volatile 保证:
- 可见性:一个线程修改,其他线程立即看到
- 有序性:禁止指令重排
- 不保证原子性:
count++这种操作仍需要同步
5. ThreadLocal
每个线程有自己的独立副本,互不干扰:
// 常用于存储当前用户信息public class UserContext { private static final ThreadLocal<User> currentUser = new ThreadLocal<>(); public static void set(User user) { currentUser.set(user); } public static User get() { return currentUser.get(); } public static void remove() { currentUser.remove(); // 必须清理,否则内存泄漏 }}注意:使用完必须调用
remove()清理,否则在线程池场景下会造成内存泄漏或数据错乱。
七、Java 8+ 新特性
1. Lambda 表达式
// 传统写法button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("Clicked"); }});// Lambda 写法button.addActionListener(e -> System.out.println("Clicked"));// 多参数Comparator<String> comparator = (a, b) -> a.length() - b.length();2. Stream API
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");// 链式操作List<String> result = names.stream() .filter(name -> name.length() > 3) // 过滤 .map(String::toUpperCase) // 映射 .sorted() // 排序 .collect(Collectors.toList()); // 收集// 常用操作names.stream() .filter(n -> n.startsWith("A")) .forEach(System.out::println); long count = names.stream() .filter(n -> n.length() > 3) .count(); boolean anyMatch = names.stream() .anyMatch(n -> n.equals("Bob")); // 分组Map<Integer, List<String>> grouped = names.stream() .collect(Collectors.groupingBy(String::length));3. Optional
// 传统写法 — 大量 null 检查public String getCity(User user) { if (user != null) { Address address = user.getAddress(); if (address != null) { return address.getCity(); } } return "Unknown";}// Optional 链式写法public String getCity(User user) { return Optional.ofNullable(user) .map(User::getAddress) .map(Address::getCity) .orElse("Unknown");}4. 日期时间 API
// 新的日期 API(线程安全,不可变)LocalDate today = LocalDate.now(); // 2024-01-15LocalTime now = LocalTime.now(); // 14:30:00LocalDateTime dateTime = LocalDateTime.now(); // 2024-01-15T14:30// 格式化DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String formatted = dateTime.format(formatter); // 2024-01-15 14:30:00LocalDateTime parsed = LocalDateTime.parse("2024-01-15 14:30:00", formatter);// 时间计算LocalDateTime tomorrow = dateTime.plusDays(1);LocalDateTime nextWeek = dateTime.plusWeeks(1);long hoursBetween = ChronoUnit.HOURS.between(dateTime, tomorrow);// 时间差Duration duration = Duration.between(LocalTime.now(), LocalTime.of(18, 0));Period period = Period.between(LocalDate.of(2024, 1, 1), LocalDate.now());八、JVM 基础
1. 内存区域划分
| 区域 | 线程共享 | 存放内容 | 异常 |
|---|---|---|---|
| 堆(Heap) | ✅ | 对象实例、数组 | OutOfMemoryError |
| 方法区(Metaspace) | ✅ | 类信息、常量、静态变量 | OutOfMemoryError |
| 虚拟机栈(Stack) | ❌ | 局部变量表、操作数栈 | StackOverflowError |
| 本地方法栈 | ❌ | native 方法 | StackOverflowError |
| 程序计数器 | ❌ | 当前线程执行的字节码行号 | 无 |
JDK 8+ 变化:
- 方法区从"堆内永久代" → "元空间(Metaspace)"
- 元空间使用本地内存,不再受 JVM 堆大小限制
2. GC 算法
| 算法 | 原理 | 适用区域 | 特点 |
|---|---|---|---|
| 标记-清除 | 标记存活对象 → 清除未标记的 | 老年代 | 有碎片 |
| 标记-复制 | 将存活对象复制到另一半区域 | 新生代 | 无碎片,但浪费空间 |
| 标记-整理 | 标记存活 → 向一端移动 → 清理边界外 | 老年代 | 无碎片,但效率低 |
| 分代收集 | 新生代复制、老年代标记-整理 | 整个堆 | 综合最优 |
垃圾回收器对比:
| 回收器 | 适用代 | 算法 | 特点 |
|---|---|---|---|
| Serial | 新生代 | 复制 | 单线程,暂停所有用户线程 |
| ParNew | 新生代 | 复制 | Serial 的多线程版本 |
| Parallel Scavenge | 新生代 | 复制 | 关注吞吐量 |
| CMS | 老年代 | 标记-清除 | 最短停顿时间 |
| G1(JDK 9+默认) | 全堆 | 分区 | 可预测停顿时间 |
3. 类加载机制
加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载 ↑ 连接阶段(双亲委派模型发生在加载阶段)双亲委派模型:
自定义类加载器 ↓ 委托应用程序类加载器(Application ClassLoader) ↓ 委托扩展类加载器(Extension ClassLoader) ↓ 委托启动类加载器(Bootstrap ClassLoader)作用:保证核心类库的安全性(比如自己写的 java.lang.String 不会被加载)。
九、常用框架
1. Spring Boot 核心
| 概念 | 说明 |
|---|---|
| IoC(控制反转) | 对象创建权交给容器,由 Spring 管理 Bean 生命周期 |
| DI(依赖注入) | 容器自动注入依赖,不需要自己 new |
| AOP(面向切面编程) | 将日志、事务、权限等横切关注点与业务逻辑分离 |
| Bean | Spring 管理的对象 |
| 自动配置 | @EnableAutoConfiguration 自动装配 |
@SpringBootApplication // 组合注解:@SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScanpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}// 分层结构@RestController // 控制层@RequestMapping("/api")public class UserController { @Autowired // 依赖注入 private UserService userService; @GetMapping("/users/{id}") public Result<User> getUser(@PathVariable Long id) { return Result.success(userService.getById(id)); }}2. MyBatis
// Mapper 接口@Mapperpublic interface UserMapper { @Select("SELECT * FROM user WHERE id = #{id}") User selectById(Long id); @Insert("INSERT INTO user(username, password) VALUES(#{username}, #{password})") @Options(useGeneratedKeys = true, keyProperty = "id") int insert(User user); @Update("UPDATE user SET username = #{username} WHERE id = #{id}") int update(User user); @Delete("DELETE FROM user WHERE id = #{id}") int deleteById(Long id);}3. MyBatis-Plus(增强版)
// 继承 BaseMapper,内置 CRUD 方法@Mapperpublic interface UserMapper extends BaseMapper<User> { // 无需写任何 SQL,自动拥有增删改查}// 使用@Servicepublic class UserService { @Autowired private UserMapper userMapper; public User getById(Long id) { return userMapper.selectById(id); } public Page<User> page(int page, int pageSize) { return userMapper.selectPage(new Page<>(page, pageSize), null); }}4. 事务管理
@Servicepublic class OrderService { @Autowired private OrderMapper orderMapper; @Transactional(rollbackFor = Exception.class) public void createOrder(Order order) { orderMapper.insert(order); // 如果这里抛出异常,上面的 insert 会自动回滚 inventoryService.deductStock(order.getProductId(), order.getQuantity()); }}十、常见面试题
Java 基础
Q1:== 和 equals() 的区别?
==:比较引用(内存地址)或基本数据类型的值equals():默认比较引用,但可以重写为比较内容(String、Integer等已经重写)
String a = new String("hello");String b = new String("hello");a == b; // false,不同对象a.equals(b); // true,内容相同Q2:String、StringBuilder、StringBuffer 的区别?
String:不可变,每次修改创建新对象StringBuilder:可变,线程不安全,性能最好StringBuffer:可变,线程安全(方法有 synchronized),性能稍低
Q3:final 关键字的作用?
final class:不能被继承final method:不能被重写final variable:值不可变(基本类型值不变,引用类型指向不变)
集合
Q4:HashMap 和 Hashtable 的区别?
| 对比 | HashMap | Hashtable |
|---|---|---|
| 线程安全 | ❌ | ✅(方法有 synchronized) |
| 允许 null key | ✅ | ❌ |
| 性能 | 高 | 低(同步开销) |
| 迭代器 | fail-fast | fail-fast |
Q5:ArrayList 扩容机制?
- 默认容量 10
- 每次扩容为原来的 1.5 倍(
oldCapacity + (oldCapacity >> 1)) - 通过
Arrays.copyOf()将原数组复制到新数组
Q6:HashSet 怎么保证元素不重复?
- 底层是
HashMap,元素作为HashMap的 key - 先算
hashCode()定位桶,再用equals()判断是否已存在
多线程
Q7:sleep() 和 wait() 的区别?
| 对比 | sleep() | wait() |
|---|---|---|
| 所属类 | Thread 的静态方法 | Object 的方法 |
| 是否释放锁 | ❌ 不释放 | ✅ 释放 |
| 唤醒方式 | 时间到自动唤醒 | notify()/notifyAll() |
| 使用前提 | 不需要 | 必须在同步块中 |
Q8:线程池的核心参数有哪些?
corePoolSize:核心线程数maximumPoolSize:最大线程数keepAliveTime:空闲线程存活时间workQueue:任务队列threadFactory:线程工厂handler:拒绝策略
Q9:synchronized 的锁升级过程? 无锁 → 偏向锁 → 轻量级锁 → 重量级锁
- 偏向锁:只有一个线程访问,减少 CAS 开销
- 轻量级锁:少量线程竞争,自旋等待
- 重量级锁:大量线程竞争,操作系统互斥量
JVM
Q10:Java 内存泄漏的场景?
- 静态集合类持有对象引用未释放
- 未关闭的资源(数据库连接、文件流)
- ThreadLocal 使用后未 remove()
- 内部类持有外部类的隐式引用
- 缓存中的对象一直不被淘汰
Q11:OOM 的常见原因和处理?
- 堆溢出:对象太多,增加
-Xmx - 栈溢出:递归太深,检查代码或增加
-Xss - 元空间溢出:类加载太多,增加
-XX:MaxMetaspaceSize - 直接内存溢出:NIO 使用不当
框架
Q12:Spring Boot 自动配置的原理?
@EnableAutoConfiguration导入AutoConfigurationImportSelector- 读取
META-INF/spring.factories中的配置类 - 通过
@ConditionalOnClass、@ConditionalOnProperty等条件注解按需加载
Q13:@Autowired 和 @Resource 的区别?
| 对比 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring 注解 | JSR-250 标准 |
| 装配方式 | 按 type | 按 name |
| 配合 | 可配合 @Qualifier | 指定 name 属性 |
Q14:什么是循环依赖?Spring 怎么解决?
- A 依赖 B,B 依赖 A,形成循环
- Spring 通过三级缓存解决:
- 一级缓存:完整对象
- 二级缓存:早期对象(未完全初始化)
- 三级缓存:对象工厂
Q15:MyBatis 中 #{} 和 ${} 的区别?
#{}:预编译(参数化查询),防 SQL 注入${}:字符串拼接,有 SQL 注入风险,仅用于动态表名/列名
进阶
Q16:什么是不可变类?如何创建?
- 创建后状态不可变(如
String、Integer) - 规则:class 用 final、所有字段 final、不提供 setter、构造器深度拷贝可变对象
Q17:序列化时 serialVersionUID 的作用?
- 版本标识,确保反序列化时类版本一致
- 不指定时 JVM 自动生成,但类变更后 UID 会变,导致反序列化失败
Q18:动态代理的原理?
- JDK 动态代理:基于接口,
Proxy.newProxyInstance() - CGLIB 动态代理:基于类继承,生成子类
- Spring AOP 默认:有接口用 JDK,无接口用 CGLIB
// JDK 动态代理示例public class LogProxy implements InvocationHandler { private Object target; public LogProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("调用前: " + method.getName()); Object result = method.invoke(target, args); System.out.println("调用后: " + method.getName()); return result; } public static <T> T createProxy(T target, Class<T> interfaceType) { return (T) Proxy.newProxyInstance( target.getClass().getClassLoader(), new Class[]{interfaceType}, new LogProxy(target) ); }}Q19:try { return } finally { } 的执行顺序?
finally在return之前执行- 如果
finally中有return,会覆盖 try 中的 return finally修改基本类型的返回值不影响结果,修改引用类型会影响
public int test() { int x = 1; try { return x++; // 先保存 x=1 作为返回值,然后 x=2 } finally { System.out.println(x); // 输出 2 // return 100; // 如果这里 return 100,会覆盖返回值 }}// 结果是 1,不是 2Q20:Java 中如何实现单例模式?
// 饿汉式(线程安全)public class Singleton1 { private static final Singleton1 INSTANCE = new Singleton1(); private Singleton1() {} public static Singleton1 getInstance() { return INSTANCE; }}// 懒汉式(双重检查锁定)public class Singleton2 { private static volatile Singleton2 instance; private Singleton2() {} public static Singleton2 getInstance() { if (instance == null) { synchronized (Singleton2.class) { if (instance == null) { instance = new Singleton2(); } } } return instance; }}// 静态内部类(推荐)public class Singleton3 { private Singleton3() {} private static class Holder { private static final Singleton3 INSTANCE = new Singleton3(); } public static Singleton3 getInstance() { return Holder.INSTANCE; }}十一、最佳实践
- 命名规范:类名大驼峰(UserService),方法名小驼峰(getUserById),常量全大写(MAX_COUNT)
- 阿里巴巴 Java 规范:禁止使用 Executors 创建线程池、谨慎使用魔法值、日志至少保留 15 天
- 异常处理:不捕获 Exception,不吞没异常,用自定义异常传递错误信息
- 集合初始化:指定初始容量避免频繁扩容(
new HashMap<>(initialCapacity)) - 字符串拼接:循环中用
StringBuilder,不要在循环体里用+ - 资源释放:用 try-with-resources,不要在 finally 里手动 close
- 判空:用
Optional替代多层 if-null 判断 - 并发:用
ConcurrentHashMap替代HashMap+synchronized - 线程池:手动创建
ThreadPoolExecutor,明确核心参数 - 日志:使用 SLF4J + Logback,占位符
{}替代字符串拼接
十二、总结
Java 生态系统庞大而成熟,核心知识点可以归纳为三层:
- 基础能力:OOP、集合、异常、泛型、I/O —— 日常编码的"建筑材料"
- 进阶能力:多线程、JVM、Lambda/Stream —— 写出高效、可靠的代码
- 框架能力:Spring Boot、MyBatis —— 企业级开发的"脚手架"
掌握这些核心知识,再加上良好的编码习惯,就能胜任绝大部分企业级 Java 开发工作。