异常
1 异常概述
异常:就是程序在“编译”或“执行”的过程中出现了不正常的情况。异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止
❗️注意:
- 语法错误不属于异常体系
❗️注意:编译时异常和运行时异常的区别
- 运行时异常
- 都是
RuntimeException
类及其子类- 一般都是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误
- 编译时异常
- 都是
Exception
类及其子类- 是担心程序员的技术不行,在编译阶段就爆出一个错误,目的在于提醒不要出错
- 必须显示处理,否则程序就会发生错误,无法通过编译
例如:
2 异常处理方式
2.1 JVM 默认处理异常的方式
如果程序出现了问题,我们没有做任何处理,最终 JVM 会做默认的处理:
- 默认会在出现异常的代码那里自动的创建一个异常对象:
ArithmeticException
- 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给 JVM 虚拟机
- 虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据
- 直接从当前执行的异常点干掉当前程序
- 后续代码没有机会执行了,因为程序已经死亡
🙋举个栗子:
public class Test {
public static void main(String[] args) {
System.out.println("程序开始……");
chu(10, 0);
System.out.println("程序结束……");
}
private static void chu(int a, int b) {
System.out.println(a);
System.out.println(b);
int c = a / b;
System.out.println(c);
}
}
默认的异常处理机制并不好,一旦真的出现异常,程序立即死亡!
2.2 编译时的异常处理机制
编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过
编译时异常的处理形式有三种:
- 出现异常直接抛出去给调用者,调用者也继续抛出去。
- 出现异常自己捕获处理,不麻烦别人。
- 前两者结合,出现异常直接抛出去给调用者,调用者捕获处理
2.2.1 throws
throws
用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理,这种方式并不好,发生异常的方法自己不处理异常,如果异常 最终抛出去给虚拟机将引起程序死亡。
抛出异常格式:
public void 方法() throws 异常类名1, 异常类名2……{
}
规范做法:
方法() throws Exception{
}
Exception
可以捕获处理一切异常类型!
🙋举个栗子:
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 Test {
public static void main(String[] args) throws ParseException, FileNotFoundException {
System.out.println("程序开始……");
parseTime("2023-8-18 18:20:16");
System.out.println("程序结束……");
}
private static void parseTime(String date) throws ParseException, FileNotFoundException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(date);
System.out.println(d);
InputStream is=new FileInputStream("E:/aaa.jpg");
}
}
2.2.2 try…catch…
监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。
这种方式还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行。
捕获异常格式:
try {
可能出现异常的代码;
} catch(异常类名1 变量名) {
异常的处理代码;
} catch(异常类名2 变量名) {
异常的处理代码;
}…
建议格式:
try{
可能出现异常的代码;
}catch (Exception e){
e.printStackTrace();
直接打印异常栈信息;
}
执行流程:
- 程序从
try
里面的代码开始执行 - 出现异常,就会跳转到对应的
catch
里面去执行 - 执行完毕之后,程序还可以继续往下执行
🙋举个栗子:
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 Test {
public static void main(String[] args) {
System.out.println("程序开始……");
parseTime("2023-8-18 18:20:16");
System.out.println("程序结束……");
}
private 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:/aaa.jpg");
// } catch (ParseException e) {
// e.printStackTrace();
// } catch (FileNotFoundException e) {
// e.printStackTrace();
// }
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:/aaa.jpg");
} catch (Exception e) {
e.printStackTrace();
}
}
}
try…catch…
方式处理异常的好处:
- 即使出现了异常也可以让程序继续往下执行
2.2.3 前两者结合
方法直接将异常通过 throws
抛出去给调用者
调用者收到异常后直接捕获处理
🙋举个栗子:
import java.io.FileInputStream;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test {
public static void main(String[] args) {
System.out.println("程序开始……");
try {
parseTime("2023-8-18 18:20:16");
System.out.println("操作成功");
} catch (Exception e) {
System.out.println("操作失败");
}
System.out.println("程序结束……");
}
private static void parseTime(String date) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(date);
System.out.println(d);
InputStream is = new FileInputStream("E:/aaa.jpg");
}
}
- 在开发中按照规范来说第三种方式是最好的:底层的异常抛出去给最外层,最外层
集中捕获处理。- 实际应用中,只要代码能够编译通过,并且功能能完成,那么每一种异常处理方式
似乎也都是可以的。
2.3 运行时的异常处理机制
运行时异常编译阶段不会出错,是运行时才可能出错的,所以编译阶段不处理也可以。
按照规范建议还是处理,建议在最外层调用处集中捕获处理即可
🙋举个栗子:
public class Test {
public static void main(String[] args) {
System.out.println("程序开始……");
try {
chu(10, 0);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("程序结束……");
}
private static void chu(int a, int b) { // throws RuntimeException运行时异常是默认的,不需要抛
System.out.println(a);
System.out.println(b);
int c = a / b;
System.out.println(c);
}
}
2.4 异常处理使代码更稳健
需求
- 键盘录入一个合理的价格为止(必须是数值)。
分析
- 定义一个死循环,让用户不断的输入价格。
示例代码:
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (true) {
try { //避免用户乱输入,如aaa,vdtf等不合法输入
System.out.println("请输入合法的价格:");
String priceStr = sc.nextLine();
// 转为double类型
double price=Double.valueOf(priceStr);
if (price>0){
System.out.println("定价:"+price);
break;
}else{
System.out.println("价格必须为正数!");
}
} catch (NumberFormatException e) {
System.out.println("输入的数据不合法!");
}
}
}
}
3 自定义异常
自定义异常的必要?
- Java 无法为这个世界上全部的问题提供异常类。
- 如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了。
自定义异常的好处:
- 可以使用异常的机制管理业务问题,如提醒程序员注意。
- 同时一旦出现
bug
,可以用异常的形式清晰的指出出错的地方
自定义异常的分类:
- 自定义编译时异常
- 定义一个异常类继承
Exception
. - 重写构造器。
- 在出现异常的地方用
throw new 自定义对象
抛出,
- 定义一个异常类继承
作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!
🙋举个栗子:
自定义异常类:AgeIllegalException
public class AgeIllegalException extends Exception{
public AgeIllegalException() {
}
public AgeIllegalException(String message) {
super(message);
}
}
测试类:
public class Test {
public static void main(String[] args) {
try {
checkAge(-12);
} catch (AgeIllegalException e) {
e.printStackTrace();
}
}
private static void checkAge(int age) throws AgeIllegalException {
if(age<0||age>200){
// 抛出一个异常对象给调用者
throw new AgeIllegalException(age+"不合法");
}else{
System.out.println("年龄合法!");
}
}
}
- 自定义运行时异常
- 定义一个异常类继承
RuntimeException
. - 重写构造器。
- 在出现异常的地方用
throw new 自定义对象
抛出 !
- 定义一个异常类继承
作用:提醒不强烈,编译阶段不报错!!运行时才可能出现!!
🙋举个栗子:
自定义异常类:AgeIllegalRuntimeException
public class AgeIllegalRuntimeException extends RuntimeException{
public AgeIllegalRuntimeException() {
}
public AgeIllegalRuntimeException(String message) {
super(message);
}
}
测试类:
public class Test {
public static void main(String[] args) {
try {
checkAge(-12);
} catch (AgeIllegalRuntimeException e) {
e.printStackTrace();
}
}
private static void checkAge(int age){ // 运行时异常不需要抛了
if(age<0||age>200){
// 抛出一个异常对象给调用者
throw new AgeIllegalRuntimeException(age+"不合法");
}else{
System.out.println("年龄合法!");
}
}
}
❗️注意:
throws
和throw
的区别
❗️注意:
- 如果
try
中没有遇到问题,怎么执行?
- 会把
try
中所有的代码全部执行完毕,不会执行catch
里面的代码- 如果
try
中遇到了问题,那么try
下面的代码还会执行吗?
- 那么直接跳转到对应的 catch 语句中,
try
下面的代码就不会再执行了 。当catch
里面的语句全部执行完毕,表示整个体系全部执行完全,继续执行下面的代码- 如果出现的问题没有被捕获,那么程序如何运行?
- 那么
try...catch
就相当于没有写,那么也就是自己没有处理,默认交给虚拟机处理- 同时有可能出现多个异常怎么处理?
- 出现多个异常,那么就写多个catch就可以了。还要特别注意 如果多个异常之间存在子父类关系,那么父类一定要写在下面