一、异常处理机制
1、编译时异常的处理机制
- 编译时异常是编译阶段就会报错!所以必须进行处理,否则代码无法通过!
2、编译时异常的三种处理形式
- 出现异常直接抛出给调用者,调用者继续抛出去。
- 出现异常自己捕获处理,不麻烦别人。
- 前两者结合,出现异常直接抛出给调用者,调用者捕获处理。
(1)异常处理方式一
- 关键字:
throws
,用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理。 - 这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给JVM虚拟机将引起程序终止。
(1-1)抛出异常格式
方法 throws 异常1, 异常2, 异常3, ...{
}
package com.app.d8_exception_handle;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
目标:掌握编译时异常处理方式一,并清楚其缺陷
方式一:
在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机。
JVM虚拟机输出异常信息,直接终止程序,这种方式与默认方式是一样的。
虽然可以解决代码编译时的报错!但是一旦运行时真的出现异常,就会终止程序,项目也无法正常运行!
因此,这种方式并不好!!
*/
public class ExceptionDemo1 {
public static void main(String[] args) throws ParseException, FileNotFoundException {
System.out.println("程序开始...");
parseTime("2022-09-07 14:33:22");
System.out.println("程序结束.");
}
public static void parseTime(String date) throws ParseException, FileNotFoundException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM-dd HH:mm:ss"); // 运行时会报错!
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(date);
System.out.println(d);
// 不需要理解这行代码的意义
InputStream is = new FileInputStream("E:/ab.jpg"); // 当上一步代码没有bug时,JVM虚拟机才会检测到这里的异常!
}
}
-
这里可以看到出现了一个编译时异常了:
-
直接使用
throws
关键字,将异常抛出给调用者:
-
调用者也直接使用
throws
关键字,将异常抛出给JVM虚拟机:
-
在编译阶段已经看不到出错了!将异常抛出给JVM虚拟机时,当程序没有bug,就可以正常运行:
-
将异常抛出给JVM虚拟机时,当程序有bug,JVM虚拟机就会输出异常信息,程序终止:
-
注意:
- 小结:无论你使用throws抛出多少个异常,其实真正抛出的只有一个异常,只有处理完前一个的bug,JVM虚拟机才会检测到后一个的bug。
(1-2)规范做法
方法 throws Exception {
}
package com.app.d8_exception_handle;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
目标:掌握编译时异常处理方式一,并清楚其缺陷
方式一:
在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机。
JVM虚拟机输出异常信息,直接终止程序,这种方式与默认方式是一样的。
虽然可以解决代码编译时的报错!但是一旦运行时真的出现异常,就会终止程序,项目也无法正常运行!
因此,这种方式并不好!!
*/
public class ExceptionDemo1 {
public static void main(String[] args) throws Exception {
System.out.println("程序开始...");
parseTime("2022-09-07 14:33:22");
System.out.println("程序结束.");
}
public static void parseTime(String date) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM-dd HH:mm:ss"); // 运行时会报错!
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(date);
System.out.println(d);
// 不需要理解这行代码的意义
InputStream is = new FileInputStream("E:/ab.jpg"); // 当上一步代码没有bug时,JVM虚拟机才会检测到这里的异常!
}
}
(1-3)总结:这种方式不好!
(2)异常处理方式二
try...catch...
- 监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。
- 这种方式还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行。
(2-1)格式
try{
// 监视可能出现异常的代码!
}catch(异常类型1 变量) {
// 处理异常
}catch(异常类型2 变量) {
// 处理异常
}...
(2-2)单独处理(不推荐)
package com.app.d8_exception_handle;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
目标:掌握编译时异常处理方式二,并清楚其作用
方式二:
在编译阶段,监视捕获异常,发生异常的方法自己独立完成异常的处理,
这样程序就可以继续往下执行,不会终止!
*/
public class ExceptionDemo2 {
public static void main(String[] args) {
System.out.println("程序开始...");
parseTime("2022-09-07 14:33:22");
System.out.println("程序结束.");
}
public static void parseTime(String date) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM-dd HH:mm:ss"); // 运行时会报错!
Date d = sdf.parse(date);
System.out.println(d);
} catch (ParseException e) {
// 解析出现的问题
System.out.println("出现了解析时间异常!!");
}
try {
// 不需要理解这行代码的意义
InputStream is = new FileInputStream("E:/ab.jpg");
} catch (FileNotFoundException e) {
System.out.println("找不到指定文件!!");
}
}
}
程序开始...
出现了解析时间异常!!
找不到指定文件!!
程序结束.
Process finished with exit code 0
- 可以看到,使用
try...catch...
监视捕获异常后,程序可以正常的运行完成!!即使存在异常,也不会让程序立即终止!!
(2-3)整体处理(推荐)
package com.app.d8_exception_handle;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
目标:掌握编译时异常处理方式二,并清楚其作用
方式二:
在编译阶段,监视捕获异常,发生异常的方法自己独立完成异常的处理,
这样程序就可以继续往下执行,不会终止!
*/
public class ExceptionDemo2 {
public static void main(String[] args) {
System.out.println("程序开始...");
parseTime("2022-09-07 14:33:22");
System.out.println("程序结束.");
}
public static void parseTime(String date) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM-dd HH:mm:ss"); // 运行时会报错!
Date d = sdf.parse(date);
System.out.println(d);
// 不需要理解这行代码的意义
InputStream is = new FileInputStream("E:/ab.jpg"); // 当前面的代码没有bug时,才会检测到此处的bug
} catch (ParseException e) {
// 解析出现的问题
System.out.println("出现了解析时间异常!!");
} catch (FileNotFoundException e) {
System.out.println("找不到指定文件!!");
}
}
}
程序开始...
出现了解析时间异常!!
程序结束.
Process finished with exit code 0
- 可以看到,方法内部的前面的代码出现了异常,因此后面的代码不会执行,但是不会终止程序的运行!!
- 既然已经出现了异常,那就先解决前面的异常,再去捕获后面的异常,再进行解决!!
(2-4)建议格式(企业级写法)
Exception可以捕获处理一切异常类型
try{
// 可能出现异常的代码!
}catch(Exception e) {
e.printStackTrace(); // 直接打印异常栈信息
}
package com.app.d8_exception_handle;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
目标:掌握编译时异常处理方式二,并清楚其作用
方式二:
在编译阶段,监视捕获异常,发生异常的方法自己独立完成异常的处理,
这样程序就可以继续往下执行,不会终止!
*/
public class ExceptionDemo2 {
public static void main(String[] args) {
System.out.println("程序开始...");
parseTime("2022-09-07 14:33:22");
System.out.println("程序结束.");
}
public static void parseTime(String date) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM-dd HH:mm:ss"); // 运行时会报错!
Date d = sdf.parse(date);
System.out.println(d);
// 不需要理解这行代码的意义
InputStream is = new FileInputStream("E:/ab.jpg"); // 当前面的代码没有bug时,才会捕获到此处的bug
} catch (Exception e) {
// 解析出现的异常
e.printStackTrace(); // 调用printStackTrace方法,打印异常栈信息(规范)
}
}
}
(2-5)异常处理方式三
- 前两种处理方式的结合体
- 方法直接将异常通过
throws
抛出去给调用者; - 调用者收到异常后直接捕获处理。
- 方法直接将异常通过
- 出现第三种方式的原因:
- 举个例子:
- 我请小王帮我去买一支笔,小王很牛的说:“我干嘛帮你买?”,随后小王让小白去帮我买,小白去了。
- 结果小白到的时候,店已经打烊了,此时小白发现了异常,然后小白自己独立处理掉了这个异常!
- 之后,小王就懵了,啥情况都不知道!
- 最后,我也懵了!!不知道笔买了还是没买!
- 如果异常都在底层独立处理完了,那我根本不知道底层到底是成功了还是失败了!!
- 举个例子:
package com.app.d8_exception_handle;
import java.io.FileInputStream;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
目标:掌握编译时异常处理方式三,并清楚其作用
方式三:
在编译阶段,将异常抛出去给调用者,调用者再使用try...catch...捕获处理异常,
这样的好处是调用者既能了解到底层的执行情况,又能让程序继续往下执行,不会终止!
*/
public class ExceptionDemo3 {
public static void main(String[] args) {
System.out.println("程序开始...");
try {
parseTime("2022-09-07 14:33:22");
System.out.println("功能操作成功~");
} catch (Exception e) {
e.printStackTrace(); // 调用printStackTrace方法,打印异常栈信息(规范)
System.out.println("功能操作失败!");
}
System.out.println("程序结束.");
}
public static void parseTime(String date) throws Exception {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM-dd HH:mm:ss"); // 运行时会报错!
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(date);
System.out.println(d);
// 不需要理解这行代码的意义
InputStream is = new FileInputStream("E:/ab.jpg"); // 当前面的代码没有bug时,才会捕获到此处的bug
}
}
总结
1、异常处理的总结
- 开发中按照规范来说第三种方式是最好的:底层的异常抛出去给最外层,最外层集中捕获处理。好处就是外层可以知道底层是否执行成功。
- 实际应用中,只要代码能通过编译阶段,并且功能可以完成 ,那么每一种异常处理方式似乎也都是可以的。
3、运行时异常的处理机制
- 运行时异常编译阶段不会报错,是运行时才可能报错的,所以编译阶段不处理也可以。
- 按照规范建议还是处理:建议在最外层调用处集中捕获处理即可。
package com.app.d9_exception_handle_runtime;
/**
目标:运行时异常的处理机制
可以不处理,编译阶段代码也通过了
按照理论规则:建议还是处理,只需要在外层捕获处理异常即可!
*/
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("程序开始...");
try {
chu(10, 0);
System.out.println("操作成功!");
} catch (Exception e) {
System.out.println("出现异常!");
e.printStackTrace(); // 打印异常栈信息
}
System.out.println("程序结束.");
}
public static void chu(int a, int b) {
System.out.println(a);
System.out.println(b);
int c = a / b;
System.out.println(c);
}
}
二、异常处理使代码更稳健的案例
1、案例一
需求:
- 键盘录入一个合理的价格为止(必须是数值,值必须大于0)。
分析:
- 定义一个死循环,让用户不断的输入价格。
未处理异常前:
package com.app.d10_exception_handle_test;
import java.util.Scanner;
/**
目标:通过案例清楚并理解异常在实际开发中的作用
*/
public class Test1 {
public static void main(String[] args) {
// 需求:键盘录入一个合理的价格为止(必须是数值,值必须大于0)
// a、创建键盘录入对象,用于用户录入价格
Scanner sc = new Scanner(System.in);
// b、定义死循环,让用户不断输入价格,直到输入的价格合理为止
while (true) {
System.out.println("请您输入一个价格:");
// c、定义一个字符串变量用于存放用户输入的价格
/*
为啥用String接收,而不是用double接收??
因为用户难免会输错!!比如:23.2jjghj 等等
*/
String priceStr = sc.nextLine();
// d、将接收到的String类型的价格转换成double类型的价格
double price = Double.valueOf(priceStr);
// e、判断用户输入的价格是否大于0
if (price > 0) {
// 是,说明价格是正数
System.out.println("定价:" + price);
break;
}else {
// 否,说明价格不是正数
System.out.println("sorry!输入的价格必须是正数哦~~");
}
}
}
}
- 可以看到,用户如果输入负数,判断正常;用户输入合理价格,也正常!
- 但是,有个问题,假如用户这样子输入呢?
- 这样子,就会出现数据类型转换异常:NumberFormatException,如果不处理异常,程序就会死亡、终止!
处理异常后:
package com.app.d10_exception_handle_test;
import java.util.Scanner;
/**
目标:通过案例清楚并理解异常在实际开发中的作用
*/
public class Test1 {
public static void main(String[] args) {
// 需求:键盘录入一个合理的价格为止(必须是数值,值必须大于0)
// a、创建键盘录入对象,用于用户录入价格
Scanner sc = new Scanner(System.in);
// b、定义死循环,让用户不断输入价格,直到输入的价格合理为止
while (true) {
try {
System.out.println("请您输入一个价格:");
// c、定义一个字符串变量用于存放用户输入的价格
/*
为啥用String接收,而不是用double接收??
因为用户难免会输错!!比如:23.2jjghj 等等
*/
String priceStr = sc.nextLine();
// d、将接收到的String类型的价格转换成double类型的价格
double price = Double.valueOf(priceStr);
// e、判断用户输入的价格是否大于0
if (price > 0) {
// 是,说明价格是正数
System.out.println("定价:" + price);
break;
}else {
// 否,说明价格不是正数
System.out.println("sorry!输入的价格必须是正数哦~~");
}
} catch (Exception e) {
System.out.println("您输入的价格不合理,请重新输入一个合理的价格!");
}
}
}
}
- 可以看到,处理异常后,当出现异常时,就可以捕获处理掉,就不会让程序死亡、终止!!
三、自定义异常
- Java无法为这个世界上全部的问题提供异常类。
- 如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了。
1、好处
- 可以使用异常的机制管理业务问题,如提醒程序员注意。
- 同时一旦出现bug,可以用异常的形式清晰的指出出错的地方。
2、分类
(1)自定义编译时异常
- 定义一个异常类继承Exception。
- 重写构造器。
- 在出现异常的地方用
throws new
自定义对象抛出。 - 作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!
package com.app.d11_exception_custom;
/**
目标:学会自定义异常类(了解)
自定义异常类:
1、自定义编译时异常
2、自定义运行时异常
需求:年龄小于0岁 或 大于200岁,就为异常
*/
public class ExceptionDemo {
public static void main(String[] args) {
// 模拟调用者使用checkAge方法
try {
checkAge(13);
checkAge(-23);
} catch (AgeIsIllegalException e) {
e.printStackTrace();
}
}
public static void checkAge(int age) throws AgeIsIllegalException {
if (age < 0 || age > 200) {
// 抛出去一个异常给对象给调用者
// throw:在方法内部直接创建一个异常对象,并从此点抛出
// throws:用在方法申明上的,抛出方法内部的异常
throw new AgeIsIllegalException(age + " is illegal!");
}else {
System.out.println(age + "岁,年龄合法!");
}
}
}
(2)自定义运行时异常
- 定义一个异常类继承RuntimeException。
- 重写构造器。
- 在出现异常的地方用
throws new
自定义对象抛出。 - 作用:运行时异常是编译阶段不报错,提醒不强烈,运行时才可能出现错误!
总结
1、自定义编译时异常
- 定义一个异常类继承Exception;
- 重写构造器;
- 在出现异常的地方用
throw new
自定义对象抛出; - 作用:编译时异常是编译阶段就报错!提醒更加强烈,一定需要处理异常!!
2、自定义运行时异常
- 定义一个异常类继承RuntimeException;
- 重写构造器;
- 在出现异常的地方用
throw new
自定义对象抛出; - 作用:提醒不强烈,编译阶段不会报错!运行时才可能会报错!