每日5题Java面试系列(5):基础篇(JDK新特性、UUID、ClassNotFoundException 和 NoClassDefFoundError 的区别是什么、String不可变等等)

系列介绍

欢迎来到"Java面试基础篇"系列!本系列旨在帮助Java开发者系统性地准备面试,每天精选5道经典面试题,涵盖Java基础、进阶、框架等各方面知识。坚持学习21天,助你面试通关!
历史面试题:
每日5题Java面试系列(1)
每日5题Java面试系列(2)
每日5题Java面试系列(3)
每日5题Java面试系列(4)

今日面试题

1. JDK新版本中的新特性

JDK新特性不仅仅是语法糖,更是编程范式和工程实践的演进。以下是几个重要版本的关键特性:

JDK 8 (LTS):

• Lambda表达式:函数式编程的革命性改变,使代码更简洁,为Stream API奠定基础

• Stream API:数据处理管道化,支持并行操作,大幅提升集合处理效率

• 方法引用:进一步简化Lambda表达式

• Optional:优雅处理null值,减少NullPointerException

• 新的日期时间API:解决旧Date/Calendar类的线程安全和设计问题

JDK 11 (LTS):

• 局部变量类型推断(var):减少样板代码,提高可读性

• HTTP Client API:支持HTTP/2和WebSocket,替代HttpURLConnection

• Epsilon GC:无操作垃圾收集器,适合性能测试和短生命周期应用

• ZGC (实验性):低延迟垃圾收集器,暂停时间不超过10ms

JDK 17 (LTS):

• 密封类(Sealed Classes):增强对继承层次的控制,是模式匹配的重要基础

• 模式匹配instanceof:简化类型检查和转换

• 文本块:简化多行字符串处理

• 新的垃圾收集器Shenandoah:与ZGC竞争的低暂停时间GC

JDK 21 (LTS):

• 虚拟线程(Virtual Threads):轻量级线程,大幅提升并发性能

• 结构化并发:简化多线程编程

• 记录模式:增强模式匹配能力

• 分代ZGC:改进内存管理效率

作为架构师,我会评估团队技术栈和业务需求来决定采用哪些特性。例如,对于高并发系统,虚拟线程可能是革命性的;而对于数据密集型应用,Stream API更为关键。

2. 什么是UUID,能保证唯一性吗

UUID (Universally Unique Identifier)是一个128位的标识符,标准格式为32个十六进制数字,分为5段(8-4-4-4-12)。

UUID版本:
• 版本1:基于时间戳和MAC地址

• 版本2:DCE安全UUID

• 版本3:基于MD5哈希和命名空间

• 版本4:随机生成(最常用)

• 版本5:基于SHA-1哈希和命名空间

唯一性保证:
理论上,UUID可以保证"几乎"唯一,但不是绝对唯一。具体分析:

  1. 版本4(随机)UUID:碰撞概率极低但不为零。根据生日问题,生成约2.71万亿个UUID时,碰撞概率约为百万分之一。

  2. 版本1(时间+MAC)UUID:如果MAC地址唯一且时钟正确,理论上可以保证唯一性。但存在隐私问题(暴露MAC地址)。

  3. 分布式系统考虑:在分布式系统中,即使使用版本1,如果时钟回拨或节点ID冲突,也可能导致重复。

建议:
• 对于大多数业务场景,UUID的唯一性已经足够

• 对强唯一性要求的场景(如金融交易ID),可考虑:

• 雪花算法(Snowflake)

• 数据库序列

• 分布式ID生成服务

• 注意UUID作为数据库主键的性能影响(索引碎片化)

3. ClassNotFoundException 和 NoClassDefFoundError 的区别

这两个异常都涉及类加载问题,但发生在不同阶段,反映了不同的根本原因

特性ClassNotFoundException-NoClassDefFoundError
​​类型​​受检异常(Checked Exception)错误(Error)
​​抛出时机显式类加载时(Class.forName()等)JVM隐式加载类时
​​根本原因​​类在classpath中不存在类编译时存在但运行时缺失依赖
​​典型场景​​1. 拼写错误2. 依赖未正确部署3. 类加载器配置错误1. 静态初始化失败2. 依赖变更未重新编译3. 版本冲突
​恢复策略检查classpath配置检查编译环境和运行时一致性

深度分析:

  1. 类加载机制角度:
    • ClassNotFoundException发生在加载阶段
    • NoClassDefFoundError发生在链接阶段(验证、准备、解析之后)

  2. 微服务环境考量:
    • 在容器化部署中,依赖冲突更易导致这些问题
    • 需要建立完善的依赖管理策略(如Maven的dependencyManagement)

  3. 解决方案:
    • 实现统一的类加载监控
    • 建立编译-打包-部署的标准化流程
    • 使用模块化(JPMS)减少冲突

4. String是如何实现不可变的

String的不可变设计是Java安全性和性能的基石,其实现机制体现了精妙的设计思想:

实现机制:

  1. final类:防止子类修改行为
  2. private final char数组(JDK8及之前)/byte数组(JDK9+)
    • 数组引用不可变
    • 没有暴露内部数组的方法
  3. 无修改状态的公共方法:
    • 所有看似修改的方法(如concat, replace)都返回新String对象
  4. 构造函数防御性拷贝:
    • 避免外部修改影响内部状态

JDK9优化:

• 引入紧凑字符串:根据内容自动选择Latin-1(1字节)或UTF-16(2字节)编码

• 减少内存占用,同时保持不可变性

独特角度:

  1. 安全性:
    • 作为参数传递时无需担心被修改
    • 安全的哈希表键值

  2. 性能优化:
    • 字符串常量池(节省内存)
    • 哈希值缓存(提升集合操作性能)

  3. 线程安全:
    • 天生线程安全,减少同步开销

  4. 设计模式:
    • 享元模式的经典实现

不可变性的代价:
• 频繁修改时产生大量中间对象
• 解决方案:StringBuilder/StringBuffer

5. 为什么建议多用组合少用继承

"组合优于继承"是面向对象设计的重要原则,其背后有深刻的工程学考量:

技术层面原因:

  1. 继承破坏封装性:
    • 子类依赖父类实现细节(白箱复用)
    • 父类变更可能破坏子类(脆弱的基类问题)

  2. 组合提供更大灵活性:
    • 运行时动态改变行为(通过切换组件)
    • 避免类爆炸(避免为每种组合创建子类)

  3. 单一职责原则:
    • 继承容易导致子类承担过多责任
    • 组合更易于分离关注点

  4. 类型层次简化:
    • 过深的继承树增加系统复杂度
    • 组合保持扁平结构

架构设计影响:

  1. 领域模型设计:
    • 组合更贴近现实世界的关系模型
    • 例如:汽车"有"发动机,而不是"是"发动机

  2. 设计模式应用:
    • 策略模式、装饰器模式等都基于组合
    • 模板方法模式是继承的合理使用场景

  3. API设计考量:
    • 组合API更稳定,内部实现可自由变化
    • 继承暴露太多内部细节,限制演化

合理使用继承的场景:

  1. 严格的"is-a"关系
  2. 框架需要控制行为流程(如模板方法)
  3. 需要多态但无法修改的第三方类

实践建议:

  1. Liskov替换原则:
    • 如果考虑继承,必须确保子类能完全替代父类

  2. 设计评审检查点:
    • 超过两层的继承关系需要特别论证
    • 检查是否有方法仅为某些子类而存在

  3. 现代Java特性:
    • 利用接口默认方法提供部分实现
    • 密封类控制可扩展性

这些原则在微服务架构中尤为重要,因为服务之间更应该通过组合(服务调用)而非继承来构建系统。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值