欢迎来到每日 Java 面试题分享栏目!
订阅专栏,不错过每一天的练习
今日分享 3 道面试题目!
评论区复述一遍印象更深刻噢~
Exception
,但不是RuntimeException
的子类。IOException
、SQLException
、ClassNotFoundException
。try-catch
捕获或 throws
声明),否则编译失败。RuntimeException
(RuntimeException
本身继承 Exception
)。NullPointerException
、ArrayIndexOutOfBoundsException
、IllegalArgumentException
。维度 | 编译时异常 | 运行时异常 |
---|---|---|
处理要求 | 必须显式处理,否则编译失败 | 可不处理,由 JVM 抛出并终止线程 |
设计目的 | 表示程序外部可控问题(如文件不存在、网络中断) | 表示程序内部逻辑错误(如空指针、数组越界) |
代码可读性 | 通过 throws 声明明确调用方需处理的异常类型 |
通常通过代码逻辑规避,而非显式处理 |
IOException
,防止程序因外部资源问题崩溃。@ControllerAdvice
)。场景:在电商项目的订单支付模块中,调用第三方支付接口时可能发生网络超时(编译时异常),而参数校验不通过(如金额为负数)属于运行时异常。
处理方式:
- 编译时异常:
try { PaymentResponse response = paymentClient.call(externalApi); } catch (IOException e) { // 记录日志并触发重试机制 log.error("支付接口调用失败", e); retryPolicy.retry(); }
- 运行时异常:
// 参数校验(通过Preconditions或Assert) public void createOrder(BigDecimal amount) { Preconditions.checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "金额必须大于0"); // 业务逻辑 }
总结:
- 编译时异常用于外部依赖的容错处理(如 IO、数据库连接)。
- 运行时异常用于快速暴露代码逻辑缺陷,避免无效状态扩散。
为什么 Java 要设计两种异常?
try-catch
代码。如何自定义异常?应该继承哪一类?
RuntimeException
(如 BusinessException
),避免调用方强制处理;若异常需调用方显式关注(如特定 API 的错误码),可继承 Exception
。实际项目中常见的异常处理误区?
Exception
但不处理(如 e.printStackTrace()
),导致问题被掩盖。RuntimeException
传递业务错误,应通过返回错误码或自定义状态对象。通过分层拆解异常的设计哲学、处理机制和实战场景,可以体现对 Java 异常体系的深入理解,这正是大厂面试官期待的答案!
定义:继承是面向对象编程中类与类之间的一种关系,允许子类(派生类)复用父类(基类)的属性和方法,并可以通过重写(Override)或扩展实现新功能。
语法:
class Parent {
public void print() {
System.out.println("Parent Method");
}
}
class Child extends Parent {
@Override
public void print() {
super.print(); // 调用父类方法
System.out.println("Child Method");
}
}
关键字:extends
(单继承)、super
(访问父类成员)、@Override
(注解声明方法重写)。
implements
)实现多继承效果。public
、protected
成员,但无法直接访问 private
成员(需通过父类提供的公共方法)。super()
)。protected
,子类不能改为 private
)。private
,但无法直接访问)。Object
类)。场景:在电商系统的订单模块中,抽象出
BaseOrder
类,包含订单创建时间、订单状态等公共字段和方法,NormalOrder
(普通订单)和GroupBuyOrder
(团购订单)继承并扩展特定逻辑。public abstract class BaseOrder { protected LocalDateTime createTime; protected OrderStatus status; public void validate() { if (createTime == null) { throw new IllegalArgumentException("创建时间不能为空"); } } } public class GroupBuyOrder extends BaseOrder { private int groupId; @Override public void validate() { super.validate(); // 复用父类校验 if (groupId <= 0) { throw new IllegalArgumentException("团购ID无效"); } } }
设计要点:
- 通过继承实现代码复用,避免重复校验逻辑。
- 使用抽象类定义通用行为,子类通过重写扩展差异化逻辑。
维度 | 继承(is-a) | 组合(has-a) |
---|---|---|
关系 | 强耦合,子类依赖父类实现 | 松耦合,通过持有其他类的对象实现功能复用 |
灵活性 | 父类修改可能破坏子类 | 可动态替换组合对象(如策略模式) |
适用场景 | 明确 " 是一种 " 关系(如 Dog extends Animal ) |
功能复用但无需继承全部能力(如 Car has Engine ) |
最佳实践:优先使用组合,仅在逻辑上严格符合 “is-a” 关系时使用继承(遵循里氏替换原则)。
为什么 Java 不支持多继承?
子类实例化时父类的构造方法如何调用?
super()
),若父类没有无参构造器,子类必须显式调用 super(args)
。重写(Override)和重载(Overload)的区别?
通过结合语法、底层原理、设计原则和实战案例,可以全面展示对继承机制的掌握,这正是大厂面试中区分候选人的关键点!
private
/protected
/public
)和方法的合理使用。ArrayList
隐藏动态扩容细节)。访问控制修饰符:
修饰符 | 类内 | 包内 | 子类 | 任意位置 |
---|---|---|---|---|
private |
✔ | ✖ | ✖ | ✖ |
protected |
✔ | ✔ | ✔ | ✖ |
public |
✔ | ✔ | ✔ | ✔ |
典型实现方式:
public class BankAccount {
// 私有字段:外部无法直接访问
private double balance;
// 公有方法:受控的访问入口
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
} else {
throw new IllegalArgumentException("存款金额必须大于0");
}
}
public double getBalance() {
return balance;
}
}
balance
字段私有,防止外部直接修改。deposit()
方法实现校验和计算。setAccessible(true)
),但封装是设计层面的约束,依赖于开发者遵守规范。场景:在电商系统的用户模块中,封装用户敏感信息(如密码),确保数据安全和一致性。
public class User { private String username; private String encryptedPassword; // 加密后的密码 public void setPassword(String plainPassword) { if (plainPassword.length() < 8) { throw new IllegalArgumentException("密码长度至少8位"); } this.encryptedPassword = encrypt(plainPassword); // 加密逻辑封装在内部 } public boolean validatePassword(String input) { return encrypt(input).equals(encryptedPassword); } // 私有方法:隐藏加密算法细节 private String encrypt(String data) { // 使用SHA-256等算法加密 } }
设计优势:
- 密码存储与校验逻辑封装在
User
类内部,外部无法绕过规则直接修改。- 加密算法变更时(如从 MD5 升级为 SHA-256),只需修改
encrypt()
方法,不影响调用方。
层级 | 示例 | 封装目标 |
---|---|---|
类级别 | 字段私有化 + 公共方法 | 保护对象状态,隐藏实现细节 |
包级别 | 使用包级私有(无修饰符)类或方法 | 限制跨包访问,实现模块内高内聚 |
模块级 | Java 9 模块化(module-info.java ) |
控制模块间的依赖和暴露(如 Spring Boot) |
封装与抽象的区别?
List
接口抽象了 " 线性表 " 操作,ArrayList
封装了动态数组的实现细节。什么时候该用 protected
修饰符?
如何避免过度封装?
totalPrice
可能不需要 setter,而是通过 calculateTotal()
方法内部计算。通过结合语法规范、设计原则和实战案例,可以清晰展示对封装特性的深入理解,这正是大厂面试中区分候选人的关键!
今天的 3 道 Java 面试题,您是否掌握了呢?持续关注我们的每日分享,深入学习 Java 面试的各个细节,快速提升技术能力!如果有任何疑问,欢迎在评论区留言,我们会第一时间解答!
明天见!