30、Java中的异常(Exception)
目录
在 Java 中,异常(Exception)是一种用于处理程序运行时出现的错误或异常情况的机制。异常允许程序在遇到问题时中断正常流程,并将控制权转移到专门的错误处理代码中,从而避免程序崩溃并提供更友好的错误处理方式。下面是详细说明:
一.异常的继承体系
二.try / catch / finally 语句
当程序运行出现意外情形时,系统会自动生成一个异常对象来通知程序。程序中可以使用特定的语句捕获异常对象,读取对象中的信息,进而作出处理。通常在捕获到异常后,作出尽可能的处理,之后再向外抛出,将剩余的部分交给程序的调用者处理。
2.1.基本语法
try{ //业务逻辑代码 }catch (AException e){ //A异常处理代码 }catch (BException e){ //B异常处理代码 } finally { //一定执行,通常用于释放资源,finally放在最后面 }
注意:无论哪行代码发生异常,系统都会生成一个异常对象,这与 try...catch... 语句没有关系,若程序没有对这个异常做任何处理,则程序在此退出;
try 块:
try
块包含了可能抛出异常的代码。当这些代码执行时,如果发生异常,程序的控制流将立即从try
块转移到相应的catch
块。catch 块:
catch
块用于捕获并处理try
块中抛出的异常。每个catch
块可以处理一种特定的异常类型。- 可以有多个
catch
块,每个块捕获不同类型的异常。- catch 块的参数是异常对象,类型由 catch 块声明指定,这个参数可以用来获取异常信息。
- 异常处理的顺序很重要,子类异常应该在父类异常之前捕获,以避免父类异常捕获了应该由子类异常处理的情况。
- finally 块:
- finally 块用于执行必要的清理操作,无论是否捕获到异常,finally 块中的代码都会执行。
- 即便是try块中有return也会执行。
- finally 块可以独立存在,也可以与 try 和 catch 块一起使用。
- 通常用于释放资源,如关闭文件流、网络连接等,确保即使发生异常也能正确清理。
- finally 块在 try 块和所有 catch 块之后执行,即使在 try 或 catch 块中有 return 语句,finally 块也会执行。
- 三者之间的关系:
- try 块可以后接零个或多个 catch 块。
- try 块后没有 catch 块,也可以直接跟一个 finally 块。
- 如果有 catch 块,finally 块可以出现在所有 catch 块之后,也可以省略。
- finally 块不是必须有的,但如果有 finally 块,它总是在所有 catch 块之后,并且无论是否捕获或处理异常,都会执行。
2.2.Exception的常用方法
- String getMessage():返回该异常的详细消息字符串。
- StackTraceElement[] getStackTrace():返回该异常的跟踪栈信息。异常发生在程序的调用过程中,程序的调用是调用方法,而所有方法归根究底是main方法在调用。方法是栈在管理的。所以追踪的是栈信息。异常的详细信息是数组,按照方法的调用顺序(入栈出栈)组成的一个数组。
- void printStackTrace():将该异常的跟踪栈信息打印到标准输出设备。
2.3代码案例
public class ExceptionDemo {
public static void main(String[] args) {
//演示异常常用方法
process(new String[]{"1", "0", "3"});
//多个catch块,没有finally
process1(new String[]{"1", "2", "3"});
//finally
process2("C:\\Users\\zhou\\Downloads\\aa.txt");
}
/**
* Exception的常用方法实例
* @param args
*/
public static void process(String[] args) {
try {
int m = Integer.valueOf(args[0]);
int n = Integer.valueOf(args[1]);
System.out.println(m / n);
}catch (Exception e) {
System.out.println("**************");
//方法:将该异常的跟踪栈信息打印到标准输出设备(控制台)
e.printStackTrace();
System.out.println("****************************");
//返回该异常的详细消息字符串
System.out.println(e.getMessage()); // / by zero
System.out.println("*************************");
//getStackTrace方法:返回StackTraceElement对象
for (StackTraceElement se:e.getStackTrace()){
//打印异常的跟踪栈信息
System.out.println(se); //com.baidu.part12.ExceptionDemo.process(ExceptionDemo.java:32)
}
}
}
public static void process1(String[] args) {
try {
//在运行中可能出现的异常叫做运行时异常
//数组可能是空的或者只有1个值
//在程序运行时Integer转换可能失败
int m = Integer.valueOf(args[0]);
int n = Integer.valueOf(args[1]);
//除法可能报错
System.out.println(m / n);
//多个catch
//数组下标越界异常:处理数组为空或者取值下标超过数组实际下标
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组下标越界:" + e.getMessage());
//数字格式转换异常:处理 Integer转换失败
} catch (NumberFormatException e) {
System.out.println("数字格式错误: " + e.getMessage());
//上面列出已知可能出现的异常,最后一般是Exception处理未知异常
} catch (Exception e) {
System.out.println("未知错误");
e.printStackTrace();
}
//catch块后面可以不用finally块,不需要释放资源时不用finally
}
public static void process2(String fileName){
//声明在try里面时,finally里面无法调用,超出作用域
FileInputStream fis = null;
try {
//编译时异常:FileInputStream构造方法中抛出了FileNotFoundException异常,调用者必须处理。
fis = new FileInputStream(fileName);
System.out.println("模拟读取文件类容");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
//finally一定会执行
//可以嵌套,catch块和try块中也可以
try {
if(fis != null) {
//关闭流 close也抛出了异常,用try捕获
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.4.catch块异常处理的顺序(案例2)
三.try增强版
Java7增强了try语句,try 语句会在结束时自动关闭这些资源,该资源的实现类必须实现AutoCloseable接口或Closeable。
try (ResourceType resource = new Resource()) { // 使用资源的代码 } catch (ExceptionType e) { // 异常处理代码 }
ResourceType
是资源的类型,该类型必须实现了AutoCloseable
接口。
resource
是资源的变量名。
Resource()
是资源的构造函数,用于创建资源实例。
try
代码块中包含了使用资源的逻辑。
public static void process(String fileName){
try (
//在这里初始化文件流,在try执行完毕后会自动关闭(自动调用close方法),不需要用finally块
FileInputStream fis = new FileInputStream(fileName);
) {
//可以调用
fis.getChannel();
System.out.println("模拟读取文件......");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
四.抛出异常
抛出异常是一种在程序运行时通知错误或特殊情况发生的机制。当程序遇到某些无法处理的情况或需要上层调用者处理的情况时,可以使用抛出异常的方式将控制权转移给调用栈中的适当层次。以下是抛出异常的详细说明:
4.1.throws 声明抛出异常
throws 语句用于标识某方法可能抛出的异常,将异常处理的责任交给调用者。
public void myMethod() throws Exception1, Exception2,Exception3 {}
- throws 必须位于方法签名之后。
- 多个异常用逗号隔开。
- throws 语句声明抛出异常后,程序就无需使用 try 语句捕获该异常了。
- 在重写时,子类方法声明抛出的异常类型不能比父类方法声明抛出的异常类型大。
public static void main(String[] args) {
//调用,不处理运行时异常
process(new String[]{"0","2"});
try {
//调用处理运行时异常
process(new String[]{"0","2"});
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组下标越界:" + e.getMessage());
} catch (NumberFormatException e) {
System.out.println("数字格式转换异常:" + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("数字计算异常:" + e.getMessage());
}
//必须处理,要么抛出要么try
try {
process1("C:\\Users\\zhou\\Downloads\\aa.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
//这几个异常都是运行时异常,调用者处理不处理都可以
public static void process(String[] args) throws ArrayIndexOutOfBoundsException,NumberFormatException,ArithmeticException {
//这个程序在运行时可能会出现异常,比如数组越界,数字格式转换异常、数字计算异常
//可以用try捕获处理,也可以用throws抛出,让调用者处理
int m = Integer.valueOf(args[0]);
int n = Integer.valueOf(args[1]);
System.out.println(m / n);
}
//这是编译时异常,调用者必须处理
public static void process1(String fileName) throws FileNotFoundException {
FileInputStream fis = new FileInputStream(fileName);
}
4.2.throw 抛出异常
throw 语句用于在程序中主动抛出一个异常,throw 语句抛出的不是异常类型。而是一个异常实例。对于主动抛出的异常,可以采用 try 块捕获,或者采用 throws 语句向外抛出。
public static void main(String[] args) {
try {
process("C:\\\\Users\\\\zhou\\\\Downloads\\\\aa.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//运行时异常可以不处理
process1(0.0,1.0);
}
public static void process(String fileName) throws FileNotFoundException {
if(fileName == null || fileName.equals("")){
//主动判断文件地址为null 或者空。抛出异常
//此时主动抛出编译时异常后,要么抛出要么try(自己抛出的肯定不try)。抛出后再方法签名后加throws FileNotFoundException
throw new FileNotFoundException("file can't be null.");
}
}
//一般都需要加throws。抛出异常就是要让调用者处理
public static double process1(double m,double n) throws IllegalArgumentException{
if(n == 0.0){
//主动抛出编译时异常,调用者可以不处理。此时如果需要声明抛出异常的话,
// 需要手动再方法签名后面加throws IllegalArgumentException。运行时异常不会强制加throws
throw new IllegalArgumentException("n can't be zero.");
}
return m / n;
}
五.自定义异常
自定义异常是开发者根据特定需求创建的异常类,它们通常继承自 Java 的 Exception 类或 RuntimeException 类。自定义异常使得异常处理更加灵活和具体,能够更好地描述程序中可能出现的错误情况。以下是自定义异常的详解:
- 若定义受检查的异常(Checked Exception),则继承自 Exception 类;
- 若定义运行时异常(Runtime Exception),则继承自 RuntimeException 类;
- 自定义的异常类,通常需提供如下构造方法:
- public xxxException()
- public xxxException(String message)
- public xxxException(String message, Throwable cause)
public class ExceptionDemo {
public static void main(String[] args) {
try {
String userName = inputUserName();
} catch (UserNameException e) {
//getMessage方法是UserNameException继承的父类方法。
System.out.println(e.getMessage());
}
}
//抛出UserNameException
public static String inputUserName() throws UserNameException {
System.out.println("请输入用户名:");
try (
Scanner scanner = new Scanner(System.in)
) {
String userName = scanner.next();
if (!userName.matches("^\\w{6,20}$")) {
throw new UserNameException("账号格式错误");
}
return userName;
} catch (RuntimeException e) {
//在程序运行过程中可能出错,需要处理以防万一。抛出异常时将原始异常带上
throw new UserNameException("输入账号失败",e);
}
}
}
/**
* 定义一个检查登录用户的异常
* 继承Exception,这是一个编译时(Checked )异常,调用者必须处理
*/
class UserNameException extends Exception {
private String message;
/**
* 通常定义一个类都会定义一个无参的构造方法(没有创建构造方法时默认有个无参构造方法)
* 这样创建对象方便。
* 如果初始化数据可以使用set方法
*/
public UserNameException() {
//调用父类构造器,这样在创建UserNameException实例时,可以将传入的参数传给父类的构造器。
// 父类构造器就会将异常存入成员变量中。可以通过父类的方法获取这些信息
//当然这是个无参构造器,初始化时不会传入任何参数
super();
}
/**
* 创建异常实例时传入异常信息
* @param message 异常信息
*/
public UserNameException(String message) {
//调用父类构造器,这样在创建UserNameException实例时,可以将传入的参数传给父类的构造器。
// 父类构造器就会将异常存入成员变量中。可以通过父类的方法获取这些信息
super(message);
}
/**
*
* @param message 异常信息
* @param cause Throwable所有异常的父类。有时捕获到一个异常但没有处理完整,这是需要将重新new一个业务异常
* 重新向上抛,这是需要带上原始的异常信息。调用者可能需要,让调用者可以追踪到原始异常
*/
public UserNameException(String message,Throwable cause) {
//调用父类构造器,这样在创建UserNameException实例时,可以将传入的参数传给父类的构造器。
// 父类构造器就会将异常存入成员变量中。可以通过父类的方法获取这些信息
super(message,cause);
}
}
六.异常跟踪栈
6.1.异常传播机制
- 方法调用栈的形成:
- 在程序运行过程中,会连续发生一系列方法调用,这些调用形成了一个方法调用栈。
- 异常传播与方法调用的关系:
- 当异常发生时,异常会在方法调用栈中传播,其传播顺序与方法调用的顺序相反。
- 异常的向外传播:
- 异常首先从发生异常的方法开始,向外传播给该方法的直接调用者。
- 然后,异常继续沿着调用链向上层调用者传播。
- 未处理异常的结果:
- 如果异常一直传播到 main 方法,且在此过程中未得到任何处理,那么 JVM(Java 虚拟机)将终止程序的执行。
- JVM 会打印出异常的跟踪栈信息,以便于开发者进行调试和问题定位。
public class ExceptionDemo2 {
public static void main(String[] args) {
method1();
}
public static void method1(){
method2();
}
public static void method2() {
method3();
}
public static void method3() {
method4();
}
public static void method4() {
method5();
}
public static void method5() {
throw new RuntimeException("error");
}
}
6.2.如何查看异常信息
6.3.不要这样打印异常信息
七.异常的处理原则
- 不要过度的使用异常
- 不要用异常处理代替错误处理代码,不要用异常处理代替流程控制语句。
- 不要忽略捕获的异常
- 对于捕获到的异常,要进行合适的修复,对于不能处理的部分,应该抛出新的异常。
- 不要直接捕获所有的异常
- 应对不同的异常做出有针对性的处理,而捕获所有的异常,容易压制(丢失)异常。
- 不要使用过于庞大的try代码块
- 庞大的try块会导致业务过于复杂,不利于分析异常的原因,也不利于程序的阅读及维护。
原文地址:https://blog.csdn.net/2402_87701017/article/details/145298592
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!