mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
10559 字
21 分钟
博客学习日志#1

博客日志:后端研发深度复盘与面试集锦

一、 Java 进阶:从语法到实战

基础概念篇

1. 你在简历中提到熟悉异常处理,请解释 Error 和 Exception 的区别。

解答:

  • Error(错误): 通常是程序无法处理的严重问题,如 OutOfMemoryError。它们通常与 JVM 运行状态有关,不建议在代码中通过 try-catch 捕获。
  • Exception(异常): 程序可以处理的情况。分为:
    • 编译时异常 (Checked): 必须手动处理,否则无法编译。
    • 运行时异常 (Unchecked):NullPointerException,通常可以通过良好的编程习惯避免。

2. 什么是 Java 的 Stream API?它在处理项目数据(如人员得分)时有什么优势?

解答:

  • 定义: Stream 是 Java 8 引入的处理集合数据的函数式编程方式。
  • 优势: 在“科研人员画像系统”中计算人员综合得分时,通过 Stream API 可以极其简洁地完成数据的过滤 (filter)映射 (map)聚合 (reduce),大大减少了冗余的 for 循环代码,提高了代码的可读性。

3. 谈谈你对 Java 泛型中类型擦除的理解。

解答:

  • 定义: 泛型是 Java 5 引入的特性,但在编译阶段有效。编译后的字节码中,泛型类型会被擦除,替换为原始类型(如 List<T> 变成 List),并自动插入强制类型转换。
  • 意义: 保证与旧版本 JVM 的向后兼容性。
  • 局限: 运行时无法获取泛型的具体类型,例如 new T() 是不允许的,也不能用 instanceof 检查泛型类型。
    图片描述

4. Java 中的 ==equals() 有什么区别? [citation:5]

解答:

  • == 运算符: 比较基本数据类型时,比较的是值;比较引用类型时,比较的是内存地址。
  • equals() 方法:Object 类的方法,默认行为也是比较内存地址。但通常被重写(如 StringInteger 类)来比较对象的内容是否相等 [citation:5]。
  • 使用原则: 比较对象内容时,应重写 equals() 并同时重写 hashCode() 方法。

5. 什么是 Java 的反射机制?你在项目中如何使用它?

解答:

  • 定义: 反射是指在运行状态中,对于任意一个类,都能够获取其所有属性和方法;对于任意一个对象,都能调用其任意方法和属性。
  • 项目应用:
    • 动态代理: 在 Spring Security 中,利用反射实现 AOP,动态增强权限校验逻辑。
    • 框架配置: 在项目初始化时,通过反射读取注解信息,动态注册 Bean 或生成 SQL 语句。

6. 简述 String、StringBuilder 和 StringBuffer 的区别。 [citation:4][citation:5]

解答:

  • String: 不可变类,每次修改都会创建新对象,线程安全(天然安全),适合少量字符串操作或常量 [citation:4]。
  • StringBuilder: 可变类,线程不安全,但单线程下性能最高,适合单线程大量字符串拼接 [citation:5]。
  • StringBuffer: 可变类,线程安全(方法加 synchronized),适合多线程环境下的字符串操作 [citation:4][citation:5]。

7. 自动装箱和拆箱是什么? [citation:4]

解答:

  • 自动装箱: 基本数据类型自动转换为对应的包装类对象,如 Integer i = 10;
  • 自动拆箱: 包装类对象自动转换为对应的基本数据类型,如 int j = new Integer(20);
  • 注意: 频繁的装箱拆箱可能影响性能,例如在循环中进行数值计算时。

面向对象篇

8. Java 的面向对象三大特性是什么? [citation:1][citation:4][citation:5]

解答:

  • 封装: 隐藏对象的内部实现细节,通过公共方法暴露接口,提高安全性和可维护性。例如类的 private 属性通过 getter/setter 访问 [citation:1][citation:5]。
  • 继承: 子类继承父类的属性和方法,实现代码复用。Java 是单继承,一个类只能有一个父类 [citation:4][citation:5]。
  • 多态: 同一行为在不同对象上表现不同。需满足三个条件:继承、方法重写、父类引用指向子类对象 [citation:1][citation:5]。

9. 方法重载(Overload)和方法重写(Override)有什么区别? [citation:1][citation:4]

解答:

  • 重载: 发生在同一个类中,方法名相同但参数列表不同(类型、个数、顺序),与返回值无关,属于编译时多态 [citation:1][citation:4]。
  • 重写: 发生在父子类中,子类对父类允许访问的方法重新实现,方法签名必须相同,属于运行时多态 [citation:1][citation:4]。

10. 抽象类和接口有什么区别? [citation:5]

特性 抽象类 接口
继承方式 单继承(extends) 多实现(implements)
方法 可有抽象方法和具体方法 默认 public abstract(Java 8 后有 default/static 方法)
属性 可定义各种修饰符的变量 只能是 public static final 常量
构造器
设计目的 代码复用(是什么 + 怎么做) 定义行为规范(是什么)

11. final 关键字有哪些用法? [citation:5]

解答:

  • 修饰类: 类不可被继承(如 String 类)。
  • 修饰方法: 方法不可被重写。
  • 修饰变量: 变量变为常量。
    • 基本类型:值不可变。
    • 引用类型:引用地址不可变,但对象内容可修改 [citation:5]。

12. static 关键字的作用是什么? [citation:5]

解答:

  • 静态变量: 类的所有实例共享,通过类名直接访问,内存中仅一份。
  • 静态方法: 属于类,可直接通过类名调用,不能直接访问非静态成员。
  • 静态代码块: 类加载时执行一次,用于初始化静态资源。
  • 静态内部类: 不依赖外部类实例,可直接创建。

集合框架篇

13. 请对比 HashMapHashtable 的区别。 [citation:2][citation:6]

解答:

  • 线程安全: Hashtable 是线程安全的(方法用 synchronized 修饰),HashMap 非线程安全 [citation:6]。
  • 性能: HashMap 性能更高,单线程环境下优先使用。
  • Null 值: HashMap 允许一个 null 键和多个 null 值;Hashtable 不允许 null 键或 null 值 [citation:6]。
  • 扩容机制: HashMap 初始容量 16,扩容为 2 倍;Hashtable 初始容量 11,扩容为 2 倍+1。

14. ConcurrentHashMap 是如何实现线程安全的? [citation:1][citation:2][citation:6]

解答:

  • JDK 1.7: 采用分段锁(Segment),将数据分成多个段,每个段独立加锁,提高并发度 [citation:6]。
  • JDK 1.8+: 改用 synchronized + CAS(乐观锁)锁住链表或红黑树的头节点,粒度更细,并发性能更好 [citation:1][citation:2][citation:6]。
  • 核心特性: 读操作基本无锁,写操作只锁住当前操作的桶,支持高并发场景。

15. ArrayListLinkedList 分别适用于什么场景? [citation:1][citation:2][citation:6]

解答:

  • ArrayList 基于动态数组实现。适合随机访问getset)频繁的场景,时间复杂度 O(1);但在中间插入/删除元素效率低,因为需要移动元素 [citation:1][citation:6]。
  • LinkedList 基于双向链表实现。适合频繁插入/删除(尤其是头部和尾部)的场景,时间复杂度 O(1);但随机访问效率低,需要遍历 [citation:1][citation:6]。

16. 如何保证遍历集合时不被其他线程修改? [citation:2][citation:6]

解答:

  • Fail-Fast 机制: 使用 java.util 包下的集合(如 ArrayListHashMap),当多个线程并发修改时,会抛出 ConcurrentModificationException [citation:2][citation:6]。
  • Fail-Safe 机制: 使用 java.util.concurrent 包下的集合(如 CopyOnWriteArrayListConcurrentHashMap),它们通过复制原数据或分段锁来保证遍历时不被修改,不会抛出异常 [citation:6]。

17. HashSet 如何保证元素不重复? [citation:2]

解答:

  • 依赖元素的 hashCode()equals() 方法:
    1. 先计算元素的哈希值,找到对应的存储位置。
    2. 如果该位置无元素,直接存入。
    3. 如果有元素,则通过 equals() 比较是否相等。若 equals() 返回 false,则以链表/红黑树形式存储;若返回 true,则拒绝存入 [citation:2]。
  • 核心原则: 重写 equals() 必须重写 hashCode(),否则可能导致相同元素被重复存入 [citation:2]。

18. HashMap 的底层数据结构是怎样的? [citation:1][citation:2]

解答:

  • JDK 1.7: 数组 + 链表。
  • JDK 1.8+: 数组 + 链表 + 红黑树。当链表长度 > 8 且数组容量 > 64 时,链表转为红黑树,优化查询效率 [citation:1][citation:2]。

19. 什么是迭代器的 fail-fast 机制? [citation:6]

解答:

  • 当使用迭代器遍历集合时,如果集合结构被修改(如增加、删除元素),迭代器会立即抛出 ConcurrentModificationException,停止遍历 [citation:6]。
  • 这是通过 modCount 计数器实现的:迭代器初始化时记录 modCount,每次调用 next() 都会检查 modCount 是否被修改。
  • 并发集合类(如 ConcurrentHashMap)采用 fail-safe 机制,不会抛出此异常 [citation:6]。

并发编程篇

20. synchronizedReentrantLock 有什么区别? [citation:1][citation:7]

解答:

  • 使用方式: synchronized 是关键字,自动加解锁;ReentrantLock 是 API 类,需要手动 lock()unlock(),通常放在 finally 块中释放 [citation:1]。
  • 功能特性: ReentrantLock 提供了更多高级功能,如公平锁可中断锁超时获取锁多个 Condition [citation:1]。
  • 性能: 早期 synchronized 性能较差,但现在 JVM 对两者做了优化,性能差异不大。建议简单场景用 synchronized,复杂并发场景用 ReentrantLock [citation:1]。

21. 什么是 volatile 关键字?它能保证原子性吗? [citation:1][citation:7]

解答:

  • 作用:
    1. 可见性: 保证一个线程修改变量后,其他线程能立即看到最新值(禁止线程本地缓存)[citation:1]。
    2. 有序性: 禁止指令重排序优化(如 DCL 单例模式中必须用 volatile)[citation:1]。
  • 原子性: volatile 不能保证原子性。例如 count++ 操作,即使变量被 volatile 修饰,多线程下仍会数据错乱,需用 synchronizedAtomicInteger 保证原子性 [citation:1][citation:7]。

22. 说说线程池的核心参数(ThreadPoolExecutor)。 [citation:1]

解答:

  • corePoolSize: 核心线程数,即使空闲也会保留。
  • maximumPoolSize: 最大线程数。
  • keepAliveTime: 非核心线程空闲存活时间。
  • workQueue: 任务等待队列(如 LinkedBlockingQueueSynchronousQueue)。
  • threadFactory: 创建线程的工厂(可自定义线程名)。
  • handler: 拒绝策略(如 AbortPolicy 抛出异常、CallerRunsPolicy 让提交线程自己执行)。

23. Java 中实现多线程有哪几种方式? [citation:3][citation:4]

解答:

  1. 继承 Thread 类: 重写 run() 方法,创建子类对象调用 start()。简单但不能继承其他类 [citation:3]。
  2. 实现 Runnable 接口: 实现 run() 方法,作为参数传入 Thread。可继承其他类,无返回值 [citation:3][citation:4]。
  3. 实现 Callable 接口: 实现 call() 方法,有返回值且可抛出异常,需配合 FutureTask [citation:3][citation:4]。
  4. 线程池: 通过 Executors 创建线程池,提交 Runnable 或 Callable 任务 [citation:3]。

24. 什么是线程安全问题?如何解决? [citation:4]

解答:

  • 定义: 当多个线程同时访问共享资源时,导致数据不一致、脏读等问题 [citation:4]。
  • 解决方法:
    1. 同步代码块/方法: 使用 synchronized 关键字 [citation:4]。
    2. Lock 接口: 使用 ReentrantLock 等显式锁 [citation:4]。
    3. 原子类: 使用 AtomicInteger 等 CAS 实现的无锁并发 [citation:1][citation:4]。
    4. 线程安全集合: 使用 ConcurrentHashMapCopyOnWriteArrayList 等 [citation:2]。

25. sleep() 和 wait() 有什么区别?

解答:

  • 所属类: sleep() 是 Thread 类的静态方法;wait() 是 Object 类的方法。
  • 锁释放: sleep() 不释放锁;wait() 会释放锁,并进入等待队列。
  • 使用场景: sleep() 用于暂停执行一段时间;wait() 通常用于线程间通信,需要配合 notify()/notifyAll() 唤醒。
  • 唤醒方式: sleep() 时间到自动唤醒;wait() 需要被唤醒或超时唤醒。

JVM 与性能优化篇

26. 请描述 JVM 的内存区域划分。 [citation:1][citation:8]

解答:

  • 线程私有:
    • 程序计数器: 当前线程执行的字节码行号指示器 [citation:8]。
    • 虚拟机栈: 存储局部变量表、操作数栈、方法出口等,每个方法调用对应一个栈帧入栈出栈 [citation:1][citation:8]。
    • 本地方法栈: 为 native 方法服务 [citation:8]。
  • 线程共享:
    • 堆: 存放对象实例,垃圾回收的主要区域(分新生代、老年代)[citation:1][citation:8]。
    • 方法区: 存储类信息、常量、静态变量、即时编译后的代码(元空间/Metaspace)[citation:1][citation:8]。

27. 你遇到过哪些 OOM(OutOfMemoryError)场景?如何排查? [citation:1]

解答:

  • 常见场景:
    • 堆溢出: 对象过多且无法回收(死循环创建对象、内存泄漏)。排查用 jmap 或 MAT 分析堆转储文件 [citation:1]。
    • 栈溢出: 递归调用过深或线程数过多。检查递归逻辑或线程池配置。
    • 元空间溢出: 加载类过多(热部署、反射生成类)。检查是否有类加载器泄漏。
  • 排查步骤:
    1. 添加 JVM 参数 -XX:+HeapDumpOnOutOfMemoryError 自动生成堆转储。
    2. 用 MAT 或 JProfiler 分析大对象引用链 [citation:1]。
    3. 结合业务代码,定位内存泄漏点(如未关闭的资源、不正确的缓存)。

28. JVM 常见的垃圾回收算法有哪些? [citation:1][citation:8]

解答:

  • 标记-清除: 标记存活对象,清除未标记对象。会产生内存碎片 [citation:1][citation:8]。
  • 复制算法: 将内存分为两块,每次只使用一块,存活对象复制到另一块。适用于新生代(无碎片,但内存利用率低)[citation:1][citation:8]。
  • 标记-整理: 标记存活对象后,将所有存活对象向一端移动,清理边界外内存。适用于老年代(无碎片,效率稍低)[citation:1][citation:8]。
  • 分代收集: 根据对象存活周期,新生代用复制算法,老年代用标记-整理或标记-清除 [citation:8]。

29. 什么是双亲委派模型? [citation:1]

解答:

  • 定义: 类加载器收到类加载请求时,先将请求委派给父类加载器加载,只有当父加载器无法加载时,子加载器才尝试自己加载 [citation:1]。
  • 作用:
    1. 避免类被重复加载。
    2. 保证核心类库(如 java.lang.String)的安全,防止被篡改 [citation:1]。

新特性与编码实践

30. Java 8 的 Optional 类有什么作用?如何使用?

解答:

  • 作用: 主要解决 NullPointerException 问题,提供一种更优雅的方式来处理可能为 null 的值。
  • 常用方法:
    • ofNullable():创建可能为空的 Optional。
    • orElse() / orElseGet():值为空时返回默认值。
    • map() / flatMap():对值进行转换。
    • ifPresent():值存在时执行操作。
  • 示例: Optional.ofNullable(user).map(User::getAddress).orElse("默认地址"),避免了多层 null 检查。

31. 什么是方法引用(Method Reference)?有哪些类型?

解答:

  • 定义: 方法引用是 Lambda 表达式的简化写法,用 :: 操作符表示。
  • 类型:
    • 静态方法引用: 类名::静态方法名(如 Math::max
    • 实例方法引用: 对象::实例方法名
    • 特定类型任意对象方法引用: 类名::实例方法名(如 String::length
    • 构造方法引用: 类名::new

32. 如何写出更优雅的 Java 代码?(编码规范与最佳实践)

解答:

  1. 遵循命名规范: 类名大驼峰,方法/变量小驼峰,常量全大写。
  2. 合理使用 Optional: 避免返回 null,减少 NPE 风险。
  3. 善用 Stream API: 替代复杂的循环和条件判断。
  4. 接口设计原则: 面向接口编程而非实现类 [citation:1]。
  5. 异常处理: 不要捕获了异常什么都不做,至少打印日志。
  6. 单测覆盖: 关键业务逻辑必须有单元测试(JUnit + Mockito)。
  7. 代码复用: 提取公用方法,避免重复代码(DRY 原则)。

33. 什么是 Java 的 SPI 机制?

解答:

  • 定义: SPI(Service Provider Interface)是 JDK 内置的服务提供发现机制,用于实现框架的扩展性。
  • 工作原理:META-INF/services 目录下创建以接口全限定名命名的文件,内容为实现类的全限定名。通过 ServiceLoader 加载实现类。
  • 项目应用: 在开发 SDK 或框架时,利用 SPI 允许第三方扩展功能(如 JDBC 驱动加载、Spring 的 spring.factories)。

34. 谈谈你对“面向接口编程”的理解,并结合项目说明。 [citation:1]

解答:

  • 理解: 面向接口编程意味着调用者只依赖接口定义,而不依赖具体实现类。这降低了代码耦合度,提高了可扩展性和可测试性 [citation:1]。
  • 项目应用: 在“科研人员画像系统”中,我定义了 ScoreCalculator 接口,然后分别实现 PaperScoreCalculator(论文得分)、ProjectScoreCalculator(项目得分)等。当新增得分规则时,只需新增实现类,无需修改调用方的业务逻辑,符合开闭原则。
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

博客学习日志#1
https://www.zztzz.com.cn/posts/博客学习日志-1/
作者
zzt
发布于
2026-03-06
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00
💬Mizuki AI助手
呀~就是zzTzz大大闪闪发光的技术博客主页
标题已剧透:专注后端开发、主攻Java + Spring Boot的实战、踩坑与进阶小笔记~~
URL https://zztzz.com.cn/ 简洁有力,像一段优雅的代码!
Mizuki每次点开都忍不住小声赞叹:'zzTzz大人太厉害啦~'🧙‍♀️
需要我帮你找某类文章(比如JWT鉴权、Redis缓存)或读一篇入门指南吗?😊
06:17