目录
6、异常处理
在 Java 中,异常(Exception)是指程序运行时可能出现的错误或异常情况,例如除以零、数组越界、文件找不到等。Java 异常是一种对象,它描述了错误的类型和错误发生的位置。
发现错误最佳的阶段就是在运行程序之前,但是显然有些错误只能在运行阶段处理解决,所以这就需要错误源能够通过某种方式,把适当信息传递给某个接受者(用来处理这个问题)
6.1、 异常体系结构
在Java中,异常的体系结构是通过一套类层次结构来定义的,这些类都继承自java.lang.Throwable
类。Java程序在执行过程中所发生的异常事件可分为两类::
- Throwable:所有错误和异常的超类
- Error:程序本身无法处理的一种严重问题,一般是系统级别的问题,如:系统崩溃、虚拟机错误等,无法恢复、无法捕捉。
- Exception:异常,是程序设计方面的错误,为非致命的错误,一般可以通过异常捕捉使来处理。
- RuntimeException(不检查):运行时异常,是程序缺陷所引起的异常,java虚拟机不会去检查此错误,程序应该从逻辑角度尽可能避免这类异常的发生。
- NullPointerException :空指针异常
- classnotfoundexception:类不存在异常
- illegalargumentexception:方法的参数异常
- NoSuchMethodError:方法不存在
- NumberFormatException :数字格式异常
- ClassCastException :类型强制转换异常。
- IllegalAccessException :无访问权限异常
- IllegalArgumentException:传递非法参数异常。
- ArithmeticException :算术运算异常(用了0作为除数)
- ArrayIndexOutOfBoundsException :数组下标越界异常
- IndexOutOfBoundsException :下标越界异常
- ………
- 非运行时异常(检查):是必须进行处理的异常,如果不处理,程序就不能编译通过。
- IOException :io异常
- SQLException :sql语句异常
- 用户自定义异常
- ………
- RuntimeException(不检查):运行时异常,是程序缺陷所引起的异常,java虚拟机不会去检查此错误,程序应该从逻辑角度尽可能避免这类异常的发生。
Java处理异常的方法为:抛出异常(throw+throws)和捕捉异常(try…catch…finally)。
Throwable的二个子类Exception和Error的区别
1、Error类及其子类用来描述Java运行系统中内部错误以及资源耗尽的错误,如内存溢出等,基本很难恢复,属于系统级别。 2、Exception为非致命错误,表示程序存在设计或实现问题,可通过捕捉异常使程序继续执行。
ClassNotFoundException 和 NoClassDefFoundError
1、ClassNotFoundException:是一个运行时异常,从从Exception继承的,当动态加载Class的时候找不到类会抛出该异常,一般在执行Class.forName()、ClassLoader.loadClass()或ClassLoader.findSystemClass()的时候抛出 2、NoClassDefFoundError :后缀是一个Error,是从Error继承的,当编译成功以后执行过程中Class找不到导致抛 出该错误,由JVM的运行时系统抛出
6.2、异常处理机制
在Java中,异常处理采用的是"抓抛模型"(Catch and Throw Model),也称为"抛出-捕获"模型。
程序在执行过程中,如果出现异常,会生成一个异常类对象(虚拟机自动创建、程序员手动创建),然后将该对象提交给Java的运行时系统(抛出异常),如果一个方法内抛出了一个异常,那么这个异常会抛给调用者去处理,如果调用者也没有处理该异常,那么会继续向上抛出,直到异常被处理(捕捉异常)。如果最终异常还是没有处理,则JVM会打印异常信息,然后终止程序运行。
这个模型是通过try
、catch
、throw
、throws
和finally
等关键字来实现的。
6.2.1、捕捉异常
在Java中,try-catch-finally
结构是异常处理的基本构造,它允许程序在运行时捕获并处理异常,以防止程序因异常而意外终止。try-catch-finally
结构不仅帮助处理已知的潜在错误,还可以处理运行时意外发生的异常情况。
-
try: 用来捕捉可能发生异常的Java语句。将可能出现异常的代码放在try语句块中,当程序发生错误,try内的语句停止执行,跳转到第一个匹配该异常类型的catch块处理。
-
catch:用来捕获并处理try块中抛出的特定类型的异常。
程序不发生异常不执行
,可以写多个catch 语句,catch中传递异常类,一个catch可以接收一个异常类(从Java 7开始,你可以在单个catch块中捕获多个异常类型,这称为多异常捕获)。catch中的异常类如果没有子父类关系,则谁声明在上,谁声明在下无所谓,如果异常类满足子父类的关系,则要求子类一定要声明在父类的上面,否则报错。
-
finally 是无论 try 中代码异常与否都会执行的语句。经常用于关闭文件、释放资源或其他清理活动。
但以下情况发生时语句不会执行:
- cpu关闭
- 程序所在线程死亡
- 使用了System.exit()退出程序
- finally语句块发生异常
try{
//可能产生错误的代码块
}catch(Exception1 e){ //将捕捉到的异常存放到Exception相关对象中,可以跟多个catch
//对Exception进行处理
}catch(Exception2 e){
//对Exception进行处理
}......
finally{
//无论是否异常都会执行的代码块
}
try-catch主要处理的是编译时的异常,让程序在编译期间不出现异常,让有可能产生的异常延迟到运行期间出现。而一般的运行时异常,因为比较常见,所以一般不会主动使用 try-catch 处理,但是编译时异常必须要进行处理才能通过编译。
finally语句的几种执行情况:
- 当try或catch中存在return语句时:在执行到return时,会先执行finally内的语句。
- 当finally存在return语句:会把原有的return值给覆盖掉。
- 在finally中修改return值:无法修改
==注意:finally语句会在return前执行,但是不能改变return的返回值。==不要在finally语句中使用return语句,会导致覆盖和丢失异常
6.2.2、try的新写法
1、自动资源管理
很多时候使用 finally 去关闭流等操作时,代码会很臃肿:
public static void main(String[] args) {
//创建文件对象
File file1 = new File("d:/**/a.jpg"); //源数据
File file2 = new File("d:/**/copya.jpg"); //目的数据,file("",true)为追加,不加为false,默认覆盖
//创建输入和输出流
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream(file1);
out = new FileOutputStream(file2);
//使用流对文件进行读写
........
} catch (Exception e) {
e.printStackTrace();
}finally {
//关闭流
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
为了解决这种情况,JDK7 后新增加了一种写法,能自动关闭文件,被称为自动资源管理。这种方法在try后能自动释放资源,不需要专门在finally中去调用close关闭。
try (声明或初始化资源语句) {
// 可能会生成异常语句
} catch(Throwable e1){
// 处理异常e1
} catch(Throwable e2){
// 处理异常e1
}.......
//使用自动资源管理改进,减少大量的代码
public static void main(String[] args){
//创建文件对象
File file1 = new File("d:/myData/a.jpg");
File file2 = new File("d:/myData/a1.jpg");
try (FileInputStream in = new FileInputStream(file1);
FileOutputStream out = new FileOutputStream(file2) ){
//使用流对文件进行读写
.......
} catch (Exception e) {
e.printStackTrace();
}
}
这种写法在 JDK9 中又做了进一步的改进,不要求在 try 后的圆括号内声明并创建资源,它可以在tr语句前进行创建(可以用final修饰,也可以不用,不用final时要保证后面不重新赋值),然后括号内写变量名即可
public static void main(String[] args) throws FileNotFoundException {
//创建文件对象
File file1 = new File("d:/myData/a.jpg");
File file2 = new File("d:/myData/a1.jpg");
FileInputStream in = new FileInputStream(file1);
FileOutputStream out = new FileOutputStream(file2);
try(in;out){
//使用流对文件进行读写
..........
} catch (Exception e) {
e.printStackTrace();
}
}
2、多异常捕获
try、catch可以写多个 catch 代码块,在某些时候会导致程序代码量大大增加。所以JDK7 推出了多异常捕获技术,可以把这些异常合并处理。
- 捕获多种类型的异常时,多种异常类型之间用竖线
|
隔开。 - 捕获多种类型的异常时,异常变量有隐式的 final 修饰,因此程序不能对异常变量重新赋值。
try{
} catch (IOException e) {
} catch (ParseException e) {
}
可修改为:
try{
// 可能会发生异常的语句
} catch (IOException | ParseException e) {
// 调用方法methodA处理
}
6.2.3、抛出异常
在Java中,异常可以通过throw
语句显式地抛出。任何继承自Throwable
类的对象都可以被抛出。这包括Exception
类和Error
类的所有子类。开发者可以根据需要抛出已有的异常或自定义异常。
当某个方法出现异常时,自己不确定怎么处理异常,则将异常抛出,由该方法的调用者去处理。如果其上级比如 main 也不知道怎么处理,则可以上抛给JVM 处理,JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行。
throw和throws只是将异常抛给了调用者,并没有去解决异常,此时调用者有两种处理方案:使用try捕捉异常进行处理、继续向上抛出(总要有一个去解决,就算不处理,最后也会由JVM打印异常的跟踪栈信息)
1、throw
throw用在方法体内,后面跟具体的异常对象,代码一旦执行了throw则代表一定抛出了异常,代码会在此终止(如果有finally语句的话,会执行到finally语句后再结束)。
throw是自行抛出异常,运行时异常可以直接抛出,但是如果异常对象是非运行时异常或自定义的异常则需要加上 throws 语句 或者在方法体内 try catch 处理该异常,否则编译报错。
-
运行时异常:throw 语句可以单独使用,抛出一个异常实例
-
非运行时异常:throw不能单独使用,需要在try中使用,或者throws向上抛出
2、throws
当一个方法可能抛出异常,且不希望自己处理时,可以在方法的声明中使用throws
关键字来声明这种异常。
throws是声明在方法后的,多个异常可用 , 分割,或者直接写父类。其子类重写方法时也会有一些限制,子类重写方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类重写方法声明抛出的异常不允许比父类方法声明抛出的异常多。
- 向方法的调用者明确传达风险:通过在方法签名中使用
throws
,可以通知调用这个方法的代码,这个方法可能会抛出某些异常,调用者需要处理这些异常,否则程序可能会因未捕获的异常而终止。 - 提供更好的API文档:使用
throws
声明也有助于自动生成的API文档(如Javadoc),在文档中清楚地标示出各种可能的异常,这对于使用该API的开发者来说非常有用。
public void readFile(String filePath) throws IOException, SecurityException {
// 代码尝试打开文件,可能抛出IOException或SecurityException
FileInputStream fileInput = new FileInputStream(filePath);
// 其他处理代码
}
当一个方法声明使用throws
时,任何直接调用该方法的代码都必须采取措施来处理声明的异常,要么是通过try-catch
结构捕获并处理这些异常,要么是在其自身的方法声明中继续使用throws
声明这些异常。
1、编译时异常必须用try catch 进行处理
2、运行时异常一般不用异常处理,而是测试后直接修改代码逻辑。
3、使用try-catch处理异常后,后续代码还可以继续执行。
4、如果方法之间层层调用,可以用throws将异常向上层层抛出,然后在最后使用一个try-catch进行处理。
6.3、自定义异常
Java中的异常类需要需要在 Throwable 的异常体系结构中,所以自定义异常类需要使用到继承,继承 Exception(非运行时异常继承这个)
或 RuntimeException(运行时异常类继承这个)
。
自己创建异常类,需要至少提供两个构造方法:一个是无参数的构造器;另一个是带详细错误信息字符串的构造器,这个字符串将作为该异常对象的描述信息。除此外,还可以自行添加一些其他提示信息。
-
创建一个异常类:该类可以在用户借书失败后提示失败的用户id
public class BorrowingRestrictionException extends Exception { private String userId; public BorrowingRestrictionException(String message, String userId) { super(message); this.userId = userId; } public BorrowingRestrictionException() { } public String getUserId() { return userId; } }
-
创建一个用户类
public class User { private String userId; //用户id private boolean isAccountActive; //账户是否激活 private int booksCurrentlyBorrowed; //当前借阅的书籍数量 public User(String userId, boolean isAccountActive, int booksCurrentlyBorrowed) { this.userId = userId; this.isAccountActive = isAccountActive; this.booksCurrentlyBorrowed = booksCurrentlyBorrowed; } //用于判断用户是否能够再借阅更多书籍,条件是账户必须激活且当前借阅的书籍数量小于5本 public boolean canBorrowMoreBooks() { return isAccountActive && booksCurrentlyBorrowed < 5; } public String getUserId() { return userId; } }
-
创建图书馆管理类
public class LibraryManager { //用于借出图书给用户 public void checkOutBook(User user) throws BorrowingRestrictionException { if (!user.canBorrowMoreBooks()) { throw new BorrowingRestrictionException("用户无法借阅更多书籍", user.getUserId()); } System.out.println("书已成功借出:" + user.getUserId()); } }
-
测试
public static void main(String[] args) { User user = new User("user123", true, 5); // 用户已借了5本书 LibraryManager libraryManager = new LibraryManager(); try { libraryManager.checkOutBook(user); } catch (BorrowingRestrictionException e) { System.out.println("借书失败: " + e.getMessage() + " 用户ID: " + e.getUserId()); } }
6.4、常用方法
-
printStackTrace():打印异常及其回溯栈到标准错误流(System.err)。异常调试时最常用的方法之一,它提供了异常发生点的详细调用路径。
-
getMessage():返回异常的详细消息字符串,即在抛出异常时设置的消息。这个消息通常提供了关于异常原因的更具体描述。
-
toString():返回异常的短描述,通常包括异常的类名和通过getMessage()方法获取的描述
-
getStackTrace():返回一个由栈轨迹元素组成的数组,这些元素表示构成异常发生时栈轨迹的连续调用。可以用来进一步分析异常,或者用于自定义异常的详细报告。
-
getCause():返回一个Throwable对象,表示原始异常,即当前异常的原因(cause)。这在异常链处理中非常有用,可以追踪异常的根源。