Java异常机制.xmind

1.Error&Exception

1.什么是异常?

  • 实际工作中,遇到的情况不可能是非常完美的。比如:你写的某个模块,用户输入不一定符合你的要求、你的程序要打开某个文件,这个文件可能不存在或者文件格式不对,你要读取数据库的数据,数据可能是空的等。我们的程序再跑着,内存或硬盘可能满了。等等。
  • 软件程序在运行过程中,非常可能遇到刚刚提到的这些异常问题,我们叫异常,英文是:Exception,意思是例外。这些,例外情况,或者叫异常,怎么让我们写的程序做出合理的处理。而不至于程序崩溃。
  • 异常指程序运行中出现的不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。
  • 异常发生在程序运行期间,它影响了正常的程序执行流程。

    2.异常分类

    要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。

  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

    3.异常体系结构

  • Java把异常当作对象来处理,并定义一个基类java.lang. Throwable作为所有异常的超类

  • 在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception

06 Java异常 - 图1
20171010162418484.png

1.Java内置异常类

Java 语言定义了一些异常类在 java.lang 标准包中。
标准运行时异常类的子类是最常见的异常类。由于 java.lang 包是默认加载到所有的 Java 程序的,所以大部分从运行时异常类继承而来的异常都可以直接使用
Java 根据各个类库也定义了一些其他的异常,下面的表中列出了 Java 的非检查性异常
image.png
下面的表中列出了 Java 定义在 java.lang 包中的检查性异常类
image.png

2.异常方法

下面的列表是 Throwable 类的主要方法:
image.png

4.Error

Error:Error类对象由 Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。例如,Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止;还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在Java中,错误通常是使用Error的子类描述。

5.Exception

Exception:在Exception分支中有一个重要的子类RuntimeException(运行时异常),该类型的异常自动为你所编写的程序定义ArrayIndexOutOfBoundsException(数组下标越界)NullPointerException(空指针异常)ArithmeticException(算术异常)MissingResourceException(丢失资源)ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;而RuntimeException之外的异常我们统称为非运行时异常,类型上属于Exception类及其子类,从程序语法角度讲是必须进行处理的异常如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
20171010184746692.png

2.捕获与抛出异常

1.异常处理机制

1.抛出异常

throw,throws关键字

2.捕获异常

try,catch,finally关键字

2.异常处理的五个关键字

1.捕获异常(try&catch)

1.单一捕获块

使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方。
try/catch代码块中的代码称为保护代码,使用 try/catch 的语法如下:

  1. try
  2. {
  3. // 程序代码
  4. }catch(ExceptionName e1)
  5. {
  6. //Catch 块
  7. }

Catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。
如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方法是一样。
实例
下面的例子中声明有两个元素的一个数组,当代码试图访问数组的第四个元素的时候就会抛出一个异常。

  1. // 文件名 : ExcepTest.java
  2. import java.io.*;
  3. public class ExcepTest{
  4. public static void main(String args[]){
  5. try{
  6. int a[] = new int[2];
  7. System.out.println("Access element three :" + a[3]);
  8. }catch(ArrayIndexOutOfBoundsException e){
  9. System.out.println("Exception thrown :" + e);
  10. }
  11. System.out.println("Out of the block");
  12. }
  13. }

以上代码编译运行输出结果如下:

  1. Exception thrown :java.lang.ArrayIndexOutOfBoundsException: 3
  2. Out of the block

2.多重捕获块

一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获。
多重捕获块的语法如下所示:

  1. try{
  2. // 程序代码
  3. }catch(异常类型1 异常的变量名1){
  4. // 程序代码
  5. }catch(异常类型2 异常的变量名2){
  6. // 程序代码
  7. }catch(异常类型3 异常的变量名3){
  8. // 程序代码
  9. }

上面的代码段包含了 3 个 catch块。
可以在 try 语句后面添加任意数量的 catch 块。
如果保护代码中发生异常,异常被抛给第一个 catch 块。
如果抛出异常的数据类型与 ExceptionType1 匹配,它在这里就会被捕获。
如果不匹配,它会被传递给第二个 catch 块。
如此,直到异常被捕获或者通过所有的 catch 块。

注: 在此多重捕获块中,异常类型需由小到大,程序才可正常运行,否则会出现报错情况。

20171010162418484.png
实例
该实例展示了怎么使用多重 try/catch。

  1. try {
  2. file = new FileInputStream(fileName);
  3. x = (byte) file.read();
  4. } catch(FileNotFoundException f) { // Not valid!
  5. f.printStackTrace();
  6. return -1;
  7. } catch(IOException i) {
  8. i.printStackTrace();
  9. return -1;
  10. }

2.抛出异常(throw&throws)

1.throws

用来声明一个方法可能产生的所有异常,不做任何处理而是将异常往上传,谁调用我我就抛给谁。
用在方法声明后面,跟的是异常类名
可以跟多个异常类名,用逗号隔开
表示抛出异常,由该方法的调用者来处理
throws表示出现异常的一种可能性,并不一定会发生这些异常。
throws在方法后边声明异常,其实就是自己不想对异常做出任何的处理,告诉别人自己可能出现的异常,交给别人处理。

  1. package com.xinkaipu.Exception;
  2. class Math{
  3. public int div(int i,int j) throws Exception{
  4. int t=i/j;
  5. return t;
  6. }
  7. }
  8. public class ThrowsDemo {
  9. public static void main(String args[]) throws Exception{
  10. Math m=new Math();
  11. }
  12. }

2.throw

throw:则是用来抛出一个具体的异常类型。
用在方法体内,跟的是异常对象名
只能抛出一个异常对象名
表示抛出异常,由方法体内的语句处理
throw则是抛出了异常,执行throw则一定抛出了某种异常 。
throw:就是自己处理一个异常,有两种方式要么是自己捕获异常try…catch代码块,要么是抛出一个异常(throws 异常)

  1. package com.xinkaipu.Exception;
  2. public class TestThrow
  3. {
  4. public static void main(String[] args)
  5. {
  6. try
  7. {
  8. //调用带throws声明的方法,必须显式捕获该异常
  9. //否则,必须在main方法中再次声明抛出
  10. throwChecked(-3);
  11. }
  12. catch (Exception e)
  13. {
  14. System.out.println(e.getMessage());
  15. }
  16. //调用抛出Runtime异常的方法既可以显式捕获该异常,
  17. //也可不理会该异常
  18. throwRuntime(3);
  19. }
  20. public static void throwChecked(int a)throws Exception
  21. {
  22. if (a > 0)
  23. {
  24. //自行抛出Exception异常
  25. //该代码必须处于try块里,或处于带throws声明的方法中
  26. throw new Exception("a的值大于0,不符合要求");
  27. }
  28. }
  29. public static void throwRuntime(int a)
  30. {
  31. if (a > 0)
  32. {
  33. //自行抛出RuntimeException异常,既可以显式捕获该异常
  34. //也可完全不理会该异常,把该异常交给该方法调用者处理
  35. throw new RuntimeException("a的值大于0,不符合要求");
  36. }
  37. }
  38. }

3.finally

finally 关键字用来创建在 try 代码块后面执行的代码块。
无论是否发生异常,finally 代码块中的代码总会被执行。
在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。
finally 代码块出现在 catch 代码块最后,语法如下:

  1. try{
  2. // 程序代码
  3. }catch(异常类型1 异常的变量名1){
  4. // 程序代码
  5. }catch(异常类型2 异常的变量名2){
  6. // 程序代码
  7. }finally{
  8. // 程序代码
  9. }

实例

  1. public class ExcepTest{
  2. public static void main(String args[]){
  3. int a[] = new int[2];
  4. try{
  5. System.out.println("Access element three :" + a[3]);
  6. }catch(ArrayIndexOutOfBoundsException e){
  7. System.out.println("Exception thrown :" + e);
  8. }
  9. finally{
  10. a[0] = 6;
  11. System.out.println("First element value: " +a[0]);
  12. System.out.println("The finally statement is executed");
  13. }
  14. }
  15. }

以上实例编译运行结果如下:

  1. Exception thrown :java.lang.ArrayIndexOutOfBoundsException: 3
  2. First element value: 6
  3. The finally statement is executed

4.使用注意

  • catch 不能独立于 try 存在。
  • 在 try/catch 后面添加 finally 块并非强制性要求的。
  • try 代码后不能既没 catch 块也没 finally 块。
  • try, catch, finally 块之间不能添加任何代码。

    5.some good Q&A


    1、try{} 里有一个 return 语句,那么紧跟在这个 try 后的 finally{} 里的 code 会不会被执行,什么时候被执行,在 return 前还是后?
    答案:会执行,在方法返回调用者前执行。

2、Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?

答:Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个异常都是一个对象,它是Throwable类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java的异常处理是通过5个关键词来实现的:try、catch、throw、throws和finally。一般情况下是用try来执行一段程序,如果系统会抛出(throw)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理;try用来指定一块预防所有异常的程序;catch子句紧跟在try块后面,用来指定你想要捕获的异常的类型;throw语句用来明确地抛出一个异常;throws用来声明一个方法可能抛出的各种异常(当然声明异常时允许无病呻吟);finally为确保一段代码不管发生什么异常状况都要被执行;try语句可以嵌套,每当遇到一个try语句,异常的结构就会被放入异常栈中,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,异常栈就会执行出栈操作,直到遇到有处理这种异常的try语句或者最终将异常抛给JVM。

3、运行时异常与受检异常有何异同?

答:异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,在Effective Java中对异常的使用给出了以下指导原则:
- 不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)
- 对可以恢复的情况使用受检异常,对编程错误使用运行时异常
- 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)
- 优先使用标准的异常
- 每个方法抛出的异常都要有文档
- 保持异常的原子性
- 不要在catch中忽略掉捕获到的异常

4.异常结构快捷键

选中某行代码后使用 ctrl+alt+t 自动生成异常处理结构

3.自定义异常

1.引入

使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。

2.自定义异常类的步骤

在程序中使用自定义异常类,大体可分为以下几个步骤:

  1. 创建自定义异常类。
  2. 在方法中通过throw关键字抛出异常对象。
  3. 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
  4. 在出现异常方法的调用者中捕获并处理异常。

    3.异常类的定义

    可以像下面这样定义自己的异常类:
    1. class MyException extends Exception{
    2. }
    只继承Exception 类来创建的异常类是检查性异常类。
    下面的 InsufficientFundsException 类是用户定义的异常类,它继承自 Exception。
    一个异常类和其它任何类一样,包含有变量和方法。
    实例
    以下实例是一个银行账户的模拟,通过银行卡的号码完成识别,可以进行存钱和取钱的操作。 ```java // 文件名InsufficientFundsException.java import java.io.*;

//自定义异常类,继承Exception类 public class InsufficientFundsException extends Exception { //此处的amount用来储存当出现异常(取出钱多于余额时)所缺乏的钱 private double amount; public InsufficientFundsException(double amount) { this.amount = amount; } public double getAmount() { return amount; } }

  1. 为了展示如何使用我们自定义的异常类,<br />在下面的 CheckingAccount 类中包含一个 withdraw() 方法抛出一个 InsufficientFundsException 异常。
  2. ```java
  3. // 文件名称 CheckingAccount.java
  4. import java.io.*;
  5. //此类模拟银行账户
  6. public class CheckingAccount
  7. {
  8. //balance为余额,number为卡号
  9. private double balance;
  10. private int number;
  11. public CheckingAccount(int number)
  12. {
  13. this.number = number;
  14. }
  15. //方法:存钱
  16. public void deposit(double amount)
  17. {
  18. balance += amount;
  19. }
  20. //方法:取钱
  21. public void withdraw(double amount) throws
  22. InsufficientFundsException
  23. {
  24. if(amount <= balance)
  25. {
  26. balance -= amount;
  27. }
  28. else
  29. {
  30. double needs = amount - balance;
  31. throw new InsufficientFundsException(needs);
  32. }
  33. }
  34. //方法:返回余额
  35. public double getBalance()
  36. {
  37. return balance;
  38. }
  39. //方法:返回卡号
  40. public int getNumber()
  41. {
  42. return number;
  43. }
  44. }

下面的 BankDemo 程序示范了如何调用 CheckingAccount 类的 deposit() 和 withdraw() 方法。

  1. //文件名称 BankDemo.java
  2. public class BankDemo
  3. {
  4. public static void main(String [] args)
  5. {
  6. CheckingAccount c = new CheckingAccount(101);
  7. System.out.println("Depositing $500...");
  8. c.deposit(500.00);
  9. try
  10. {
  11. System.out.println("\nWithdrawing $100...");
  12. c.withdraw(100.00);
  13. System.out.println("\nWithdrawing $600...");
  14. c.withdraw(600.00);
  15. }catch(InsufficientFundsException e)
  16. {
  17. System.out.println("Sorry, but you are short $"
  18. + e.getAmount());
  19. e.printStackTrace();
  20. }
  21. }
  22. }

编译上面三个文件,并运行程序 BankDemo,得到结果如下所示:

  1. Depositing $500...
  2. Withdrawing $100...
  3. Withdrawing $600...
  4. Sorry, but you are short $200.0
  5. InsufficientFundsException
  6. at CheckingAccount.withdraw(CheckingAccount.java:25)
  7. at BankDemo.main(BankDemo.java:13)


4.实际应用

  • 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理。
  • 在多重catch块后面,可以加一个catch (Exception) 来处理可能会被遗漏的异常。
  • 对于不确定的代码,也可以加上try-catch,处理潜在的异常。
  • 尽量去处理异常,切忌只是简单地调用printStackTrace()去打印输出。
  • 具体如何处理异常,要根据不同的业务需求和异常类型去决定。
  • 尽量添加finally语句块去释放占用的资源。