认识异常
什么是异常?
- 异常代表程序出现的问题,如下方代码
int[] arr = {10, 20, 30};
System.out.println(arr[3]);
-
程序运行报错如下
-
例如:如下代码
System.out.println(10 / 0);
-
程序运行报错如下
-
还有其他问题:
-
读取的文件不存在了
-
读取网络数据,断网了
-
Java异常体系
-
异常的根类是
java.lang.Throwable,其下有两个子类:java.lang.Error与java.lang.Exception,平常所说的异常指java.lang.Exception,而因为断网断电导致的不可处理的异常指Error。在开发中经常提到的异常就是指Exception,因为这类异常一旦出现,我们就要对代码进行更正,修复程序。
-
可以通过查看api树的方式来查看异常类,和其子类的详细说明Class Hierarchy (Java SE 17 & JDK 17)
- Exception:叫异常,它代表的才是我们程序可能出现的问题,所以,我们程序员通常会用Exception以及它的子类来封装程序出现的问题
- 运行时异常: RuntimeException及其子类,编译阶段不会出现错误提醒,运行时出现的异常(如:数组索引越界异常)
- 编译时异常:编译阶段就会出现错误提醒的。(如:日期解析异常)
我们在调用/** * @Description Demo011 * @Author songyu * @Date 2025-09-30 8:54 */ public class Demo011 { public static void main(String[] args) { //运行时异常(代码编译完成后运行的时候出现异常) int a = 1/0; //编译时异常(在写代码,和代码编译阶段就会出现异常) InputStream in = new FileInputStream ( "d:/demo.txt" ); } }new FileInputStream("d:/demo.txt")方法时,可能磁盘并没有这个文件所以这里可能会出现异常。 - Java比较贴心,它为了更加强烈的提醒方法的调用者,设计了编译时异常,它把异常的提醒提前了,只要可能有问题就给你报出异常提示(代码上的红色波浪线报错)。
- 运行时异常特点:
- 在运行时在控制台弹出异常信息并停止JVM运行
- 编译时异常的特点:
- 编译阶段有警告提示,需要我们进行手动处理;
- 另外与与运行期异常一样的是,运行中出现的非正常的情况,最终会导致JVM的非正常停止。
处理异常
抛出异常(throws)
- 在方法上使用throws关键字,可以将方法内部出现的异常抛出去给调用者处理。
方法 throws 异常1 ,异常2 ,异常3 ..{ … }
捕获异常(try…catch)
- 直接捕获程序出现的异常。
try{ // 监视可能出现异常的代码! }catch(异常类型1 变量){ // 处理异常 }catch(异常类型2 变量){ // 处理异常 }... - 企业实践常见异常处理方案:底层异常层层往上抛出,最外层捕获异常(捕获异常时最后要捕获父类父类异常,用于兜底),记录下异常信息,并响应适合用户观看的信息进行提示,同时因为在最外层捕获了异常,所以异常不终程序运行,提升了用户体验
- 实例代码:
public class Demo012 {
public static void main(String[] args) {
//这里main就是最外面的方法,所以这里推荐处理异常进行try-catch捕获使用方案1
//调用demo()
try {
demo();
}catch (FileNotFoundException e){
e.printStackTrace(); //打印异常堆栈信息给程序员看的
System.out.println("您访问的资源不存在,请重新操作!");
//友好提示信息给用户看的(以后给前端)
}catch(Exception e){
//兜底异常处理,不知道可能会发生什么异常,这里使用父类可以捕获任何异常返回友好信息
e.printStackTrace();
System.out.println("服务器忙,请稍重试。。。");
}
}
//demo不是最外层,所以推荐将异常抛出去,使用方案2处理
public static void demo()throws FileNotFoundException {
//运行时异常
int a = 1/0;
//编译时异常
InputStream in = new FileInputStream("d:/demo.txt");
}
}
异常处理的注意
- 注意1:当抛出子类异常非常多,可以直接抛出一个父类异常简化代码
方法 throws 子类异常1,子类异常2...{} 优化为如下 方法 throws Exception{} - 注意2:catch(异常类型) 子类异常在上,父类异常在下,因为优先匹配子类明确的异常,不明确由父类兜底,如父类Exception
try{ 监视可能出现异常的代 }catch(子类异常类型 变量){ //1处理异常 }catch(父类异常类型 变量){ //2处理异常 }
异常的作用
-
作用1:异常是用来定位程序bug的关键信息
-
作用2:可以作为方法内部的一种特殊返回值(主动抛出异常),以便通知上层调用者,方法的执行问题
方法(){ throw new RuntimeException(); }如下方案例所示:
/** * @Description Demo013 * @Author songyu * @Date 2025-09-30 9:47 */ public class Demo013 { public static void main(String[] args) { //目标:控制台输入一个性别,如果是男结束操作,抛出提示只允许女性进入 // 代码中抛出异常作为特殊的方法返回值,语法: throw 异常对象 //1.创建Scanner对象 Scanner sc = new Scanner(System.in); //2.提示用户输入性别 System.out.println("请输入您的性别:"); String gender = sc.next(); //3.得到性别,判断是否是男,如果是男抛出异常结束程序(作为特殊的方法返回值) if("男".equals(gender)){ throw new RuntimeException("男性禁止入内"); //抛出异常就是结束程序,不会往下走 } //4.女性允许进入 System.out.println("女性允许进入。。。"); } }
小结
- 异常是什么?
- 异常是代码在编译或者执行的过程中可能出现的错误。
- 异常的代表是谁?分为几类?
- Exception,分为两类:编译时异常、运行时异常。
- 编译时异常:没有继承RuntimeExcpetion的异常,编译阶段就会出错。
- 运行时异常:继承自RuntimeException的异常或其子类,编译阶段不报错,运行时出现的。
- 异常的作用是啥?
- 用来查找bug;可以作为方法内部的特殊返回值,通知上层调用者底层的执行情况。
- 异常处理的常见方案是什么样的?
- 在开发中异常的常见处理方式是:底层的异常抛出去给最外层,最外层集中捕获处理。
自定义异常
为什么需要自定义异常
Java无法为这个世界上全部的问题都提供异常类来代表,如果企业自己的某种问题想通过异常来表示,以便用异常来管理该问题,那就需要自己来定义异常类了。
自定义异常类型两种方案
- 运行时异常
- 定义一个异常类继承RuntimeException
- 重写构造函数
- 通过throw new 异常类(xxx)来创建异常类对象并抛出
- 特点:编译阶段不报错,运行时才可能出现!提醒不属于激进型
- 自定义编译时异常
- 定义一个异常类型Exception
- 重写构造函数
- 通过throw new 异常类(xxx)创建异常对象并抛出
- 特点:编译阶段就报错,提醒比较激进
实际场景演示
需求分析
- 需求:写一个saveAge(int age)方法,在方法中对参数age进行判断,如果age小于0或者大于等于200就认为年龄不合法,如果年龄不合法就抛出一个年龄非法异常给调用者
- 分析:Java的API中没有年龄非法异常这个异常,所以我们需要自定义一个异常类,用来表示年龄非法异常,然后在方法中抛出异常即可。
代码实现
- 自定义运行时异常实现代码
package AgeException; //定义一个运行时异常类继承RuntimeException. public class AgeRunTimeException extends RuntimeException { // 重写构造器 public AgeRunTimeException() { } // 有参构造器 public AgeRunTimeException(String message) { super(message); //将消息给到父类的成员,便于以后调用父类的getMessage()再次获取异常消息 } } - 自定义编译时异常实现代码
package AgeException; public class AgeException extends RuntimeException { //重写构造器 public AgeException() { } // 有参构造器 public AgeException(String message) { //将消息给到父类的成员,便于以后调用父类的getMessage()再次获取异常消息 super(message); } } - 在设计的函数方法中,指定发生年龄异常后抛出异常
package AgeException; import java.util.Scanner; public class compareAge { public static void compareAge(int age) throws AgeRunTimeException { if (age <= 0 || age > 200) { throw new AgeRunTimeException("年龄错误"); } else { System.out.println("您的年龄是:" + age); } } public static void compareAge2(int age) throws AgeException { if (age < 0 || age > 200) { throw new AgeException("年龄错误"); } else { System.out.println("您的年龄是:" + age); } } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入您的年龄:"); int age = scanner.nextInt(); try { compareAge(age); } catch (AgeRunTimeException e) { System.out.println(e.getMessage()); } catch (Exception e){ System.out.println("服务器忙,请稍后重试!"); e.printStackTrace(); } } }
- 可以看到自定义编译时异常,每一步都要处理
- 自定义运行时异常则可以不在代码中处理,但是不处理对用户的时候不友好
注意事项:自定义异常可能是编译时异常,也可以是运行时异常。
- 如果自定义异常类继承Excpetion,则是编译时异常。
- 特点:方法中抛出的是编译时异常,必须在方法上使用throws声明,强制调用者处理。
- 如果自定义异常类继承RuntimeException,则运行时异常。
- 特点:方法中抛出的是运行时异常,不需要在方法上用throws声明。
异常的处理方案
- 方案1:底层异常层层往上抛出,最外层捕获异常,记录下异常信息,并响应适合用户观看的信息进行提示,如我们之前的例子所示
- 方案2:最外层捕获异常后,尝试重新修复,如下方例子所示
- 需求:控制台输入价格,价格不合法可以处理异常并恢复程序重新输入价格,直到输入合法的价格
- 实现:在捕获异常后恢复程序,如下方代码所示:
import java.util.Scanner; public class DemoPrice { public static void main(String[] args) { getPrice(); } public static double getPrice() { //控制台输入价格,价格不合法可以处理异常并恢复程序重新输入价格,直到输入合法的价格 //Scanner scanner = new Scanner(System.in); //Scanner对象不能再for循环外面,在外面下一次循环上次的参数还在scanner对象中, // 循环无法被键盘输入函数阻塞,循环会变为死循环 double price = 0.0D; while (true) { try { Scanner scanner = new Scanner(System.in); // 输入提示词 System.out.println("请输入商品价格:"); // 获取价格 price = scanner.nextDouble(); // 合法的输入则允许接下来的程序,使用return跳出循环 System.out.println("商品价格是:" + price); return price; } catch (Exception e) { System.out.println("输入价格不合法,请重新输入"); } } } }