视频加载失败

Java 进阶 Day01:异常体系与泛型入门

1209 字
6 分钟
Java 进阶 Day01:异常体系与泛型入门

异常体系#

两大类异常#

类型父类特点例子
编译时异常Exception(不含 RuntimeException 分支)编译阶段就报错,不处理过不了编译ParseException(SimpleDateFormat.parse() 抛出)
运行时异常RuntimeException编译不报错,运行时才出现ArrayIndexOutOfBoundsException、ArithmeticException、NullPointerException
// 编译时异常:必须 try-catch 或 throws 才能通过编译
try {
show2();
} catch (Exception e) {
System.out.println("编译时异常出现了:" + e.getMessage());
}
public static void show2() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse("2026-08-05"); // parse 声明 throws ParseException
}
// 运行时异常:编译不报错,运行到这行才崩
String str = null;
System.out.println(str.charAt(0)); // NullPointerException

异常的作用#

底层方法不处理,直接 throw 抛给上层;上层 catch 拿到异常对象,可以:

  • e.printStackTrace() 打印完整堆栈
  • e.getMessage() 取携带的提示信息
  • 决定怎么响应(记录 / 提示用户 / 降级)
public static int getResult(int a, int b) throws Exception {
if (b == 0) {
throw new Exception("除数不能为0"); // 用异常携带业务信息
}
return a / b;
}

异常 vs 返回值传错:返回值只能告诉「结果是什么」,异常还能携带「为什么失败 + 在哪失败」,并且强制调用方处理。

自定义异常#

自定义编译时异常#

继承 Exception:

public class RyukiAgellleag extends Exception {
public RyukiAgellleag() { }
public RyukiAgellleag(String message) {
super(message); // 把 message 交给父类存起来
}
}

自定义运行时异常#

继承 RuntimeException:

public class RyukiAgellleagRuntime extends RuntimeException {
public RyukiAgellleagRuntime() { }
public RyukiAgellleagRuntime(String message) {
super(message);
}
}

使用对比#

// 编译时异常:调用处必须 try-catch 或继续 throws
public static void checkAge(int age) throws RyukiAgellleag {
if (age < 1 || age > 200) {
throw new RyukiAgellleag("年龄非法");
}
}
// 运行时异常:调用处不用强制处理,运行时直接崩
public static void checkAge(int age) {
if (age < 1 || age > 200) {
throw new RyukiAgellleagRuntime("年龄非法");
}
}

怎么选:希望调用方必须处理(比如参数校验)→ 编译时异常;不强制、当 bug 直接暴露 → 运行时异常。后端里 Spring 的异常处理大多基于运行时异常。

异常处理的总原则#

底层异常全部抛出去,交给最外层调用者捕获,最外层记录异常 + 响应合适信息给用户。

这就是后端「全局异常处理」的雏形:底层(DAO/Service)只管抛,Controller 统一接住返回友好提示。

泛型入门#

  • 泛型 = 类型安全:ArrayList<String> 告诉编译器这个集合只能存 String
  • 编译期检查:list.add(100) / list.add(true) 直接编译报错(不用等到运行)
  • 好处:取出时不用强转,遍历直接是 String
ArrayList<String> list = new ArrayList<>();
list.add("hello");
for (String s : list) { // 编译器知道是 String,无需强转
System.out.println(s);
}

使用层面之外,自定义泛型类/方法、通配符 ?、泛型边界属于更进一步的内容。

重点速记#

  1. 编译时异常(Exception):编译就报错,必须 try-catch/throws
  2. 运行时异常(RuntimeException):编译不报错,运行才崩(NPE/越界/除零)
  3. throw vs throws:throw 在方法里抛出一个异常对象;throws 在方法签名上声明会抛,告诉调用方
  4. 异常信息:e.printStackTrace() 看堆栈定位,e.getMessage() 取提示信息
  5. 自定义异常:继承 Exception(编译时)或 RuntimeException(运行时),重写带 String 参数的构造器,super(message) 存信息
  6. 异常处理总原则:底层全抛,最外层统一捕获响应(= 后端全局异常处理雏形)
  7. 泛型:编译期类型检查,取数据不用强转

实战:控制台程序中的异常落地#

自定义业务异常与全局捕获#

自定义一个业务用运行时异常:

public class ArticleValidationException extends RuntimeException {
public ArticleValidationException() { }
public ArticleValidationException(String message) { super(message); }
}

Service 层负责业务规则校验:validateInput 用 trim() 加 isEmpty() 判断,为空则 throw new ArticleValidationException("标题不能为空")。

主循环做全局捕获,对应「底层抛、最外层收」的原则:

while (true) {
try {
printMenu();
... switch 分发 ...
} catch (ArticleValidationException e) {
System.out.println("输入不合法:" + e.getMessage()); // 业务异常友好提示
} catch (Exception e) {
System.out.println("系统异常:" + e.getMessage()); // 兜底
}
}

try 的位置决定程序生死#

  • 错误写法 try { while (true) {...} } catch:异常跳出整个循环,catch 完程序直接退出
  • 正确写法 while (true) { try {...} catch }:catch 完回到循环顶继续跑

这正对应后端「接口不挂、响应友好」的全局异常处理思想。

校验为什么放在 Service 层#

  • 校验 = 业务规则(标题能不能为空)→ 归 Service;存取 = 细节(放 ArrayList、分配 id)→ 归 Repository
  • 方向是单向的:Service 校验后交给 Repository 存;数据层不该反向持有 Service,否则会造成循环依赖
  • 更新文章时要校验新输入(newTitle/newContent),不是数据库里的旧值

上面的全局 catch 只做打印;实际项目通常引入日志框架(如 Slf4j)记录异常,并把「写日志」和「给用户的提示」分成两层处理。

支持与分享

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

打赏
Java 进阶 Day01:异常体系与泛型入门
https://ryuki.icu/posts/java-advanced-day01/
作者
RK
发布于
2026-08-05
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
RK
Long may the sunshine
公告
Long may the sunshine
分类
标签
最新动态
站点天气
天气加载中…
站点统计
文章
27
分类
6
标签
59
总字数
39,139
篝火已燃
0 天
最后活动
0 天前
每日一签