异常

异常

常见的一些潜在风险

  • 用户不存在?
  • 用户名口令错误?
  • 余额不足?
  • 网络不通?
  • 磁盘空间不足?

异常

异常处理基本模式

Method1

  • 在Java编程语言中,用try和catch语句来处理异常
  • 格式如下
1
2
3
4
5
6
//ch06.ExceptionDemo
try{
//可能发生异常的语句
}catch(Exception e){
//发生异常了 执行这里的代码
}

Method2

  • 与方法1大致相同,增加finally语句
  • 格式如下
1
2
3
4
5
6
7
8
9
10
//ch06.ExceptionDemo2
try{
//可能发生异常的语句
//如果前面发生了异常 就不会执行这里
}catch(Exception e){
//发生异常了 执行这里的代码
//如果没有发生异常 不会执行这里
}finally{
//不管是否发生异常都会执行这里
}
  • 注意finally:不管如何(是否发生异常),都会执行这里的代码。

异常的产生

所有的异常都是通过throw创建出来的

  • 创建方法
1
2
throw new 异常的构造函数()...
//例如:throw new Exception("用户不存在!");
  • 需要注意的是,不可以随便抛出一个东西
  • 例如如下代码就是错误的
1
2
throw new String("用户不存在!");
//ch06.ExceptionDemo3
  • 抛出的内容必须是Throwable的子类。
  • 只要是Throwable的子类,都可以使用throw/catch进行操作;
  • 反之,不是Throwable的子类,都不能使用throw/catch进行操作。
  • 可抛出Throwable的几个类型
    • Error
    • Exception
    • RuntimeException

异常类型

Error及其子类

  • Error类表示Java运行时产生的系统内部错误或资源耗尽等严重错误;
  • 这种错误通常是程序无法控制和解决的,如果发生这种错误,通常的做法是通知用户并中止程序的执行;
  • 这种异常超出了程序员能力范围,没解决办法,建议程序直接退出。编译器检查、必须处理。
1
//ch06.ErrorDemo

例:NoClassDefFoundError, OutOfMemoryError, VirtualMachineError, InternalError and so on.

Exception及其子类

  • Exception又称为”可检异常“,”非运行时异常“;
  • 程序能处理的异常;
  • 这种错误的出现完全在程序员意料之中,有对应的解决办法。编译器检查、必须处理。
1
//ch06.ExceptionDemo2

例:IOException, SQLException and so on.

此外,还有很多用户自定义的:口令错误、余额不足…

RuntimeException及其子类

  • RuntimeException类及其子类被称为”运行时异常“
    • 一般发生在JRE内部;
    • 也称”非必检异常“;
    • 如NullPointerException。
  • RuntimeException类及其子类都成为运行时异常,这种异常的特点是Java编译器不会检查它。也就是说,当程序中可能出现这类异常,即使没有用try-catch语句,出现异常时也会抛出这个错误;
  • RuntimeException表示无法让程序恢复运行的异常,导致这种异常的原因通常是由于执行力错误操作,建议直接显示错误,因此Java编译器不检查这种异常;
  • 也有一种说法认为Exception之所以不检测,是因为不应该替别人背黑锅!
1
//ch06.RuntimeExceptionDemo

Example

  • ArithmaticException
1
2
int a=12/0;//Wrong
//Exception
  • NullPointerException
1
2
3
Date d=null;
System.out.println(d.toString());//Wrong
//RuntimeException
  • ArrayIndexOutOfBoundException
1
2
3
4
int [] array=new int [4];
array[0]=1;
array[4]=1;//Wrong
//RuntimeException
  • ClassCastException
1
2
3
4
5
6
class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}
Animal animal=new Dog();
Cat cat=(Cat)animal;//Wrong
//RuntimeException

创建用户自定义异常类

步骤

1
//ch06.UserDefineException

创建用户自定义异常时,一般需要完成如下工作

  1. 声明一个新的异常类,使之以Exception类或其他某个已经存在的系统异常类或用户异常类为父类;
  2. 为新的异常类定义属性和方法,或重载父类的属性和方法,使这些属性和方法能够体现该类所对应的错误的信息。

异常的处理

自己处理

1
2
//ch06.ExceptionDemo4
//自己处理

自己不处理 继续throw

1
2
//ch06.ExceptionDemo5
//等着别人处理

分门类得处理异常

定义多种异常类(Throwable子类),不同情况下,抛出不同异常类

1
//ch06.ExceptionDemo6

Questions

  1. 如果抛出了可以捕捉得东西,一定要在方法后面加throws吗?
  • 有时候需要
  • 如果抛出的是Exception的子类,且非RuntimeException的子类,必须抛出或者捕捉;
  • 有时候不需要
  • 如果抛出的是RuntimeException的子类或者Error的子类,则不需要显式得抛出。
  1. 可不可以在不抛出内容的情况下catch?
  • 有时候不行

  • //ch06.ExceptionDemo9
    <!--code15-->
    
    
  • RuntimeException的子类或者Error的子类,可以在不抛出的情况下捕捉。

  1. catch Exception能捕捉到所有抛出的“东西”吗?
  • 不能,要写捕捉所有抛出的“东西”,必须catch Throwable

  • //ch06.ExceptionDemo101
    <!--code16-->
    
    
  1. 异常的顺序问题

异常会首先被符合条件的catch捕捉

1
2
3
4
5
6
7
8
9
10
try{
throw new java.io.IOException("ie");
//IOException是Exception的子类
}catch (IOException ie){
//异常在这里被捕捉了
System.out.println("IOException");
}catch (Exception e){
//这段就不会执行了
System.out.println("Exception");
}
  • 如果将上程序中两个catch语句块交换,则会导致编译错误,为什么?
1
2
3
4
5
6
7
8
9
10
try{
throw new java.io.IOException("ie");
}
catch(Exception e){
System.out.println("Exception");
}
catch(IOException ie){
//永远接收不到exception
System.out.println("IOException");
}
  • 上面这段代码就会出现编译报错,原因是下面的catch永远不会接收到异常,这是由于Exception是最大的异常类,所有的异常都属于Exception类,所以第一个catch就会优先接收到所有满足条件的异常,即使是IOException的异常,由于IOException也是Exception的一个子类,因此也会再第一个catch处被捕获,而第二个catch也就永远不会有作用了。因此我们必须是按照从小范围到大范围的顺序对catch语句进行排列。
  1. 返回问题,下面函数为什么会报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//ch06.ExceptionDemo20.java
//buy
public int buy(String username, int amount) throws Exception {
try {
int orgBalance = this.getBalance(username);
if (orgBalance < amount) {
throw new BalanceException("余额不足!");
}
return orgBalance - amount;
} catch (Exception e) {
System.out.println(e.getMessage());
// 这里没有返回值,也就意味着如果发生异常,函数就没用返回,所以会报错。
}
}
  1. 初始化问题,下面函数为什么报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//ch06.ExceptionDemo20.java
//buy2
public void buy2(String username, int amount) throws Exception {
int orgBalance;
try {
orgBalance = this.getBalance(username);
if (orgBalance < amount) {
throw new BalanceException("余额不足!");
}
} catch (Exception e) {
System.out.println("余额不足:" + orgBalance);
// 系统编译的时候认为try里面代码完全有可能一行都不被执行,所以必须要在try前面给变量赋值
}
}
  1. 初始化问题,下面函数为什么报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//ch06.ExceptionDemo20.java
//readFromFile
public void readFromFile(String fileName) {
InputStream in;
// 正确的写法是InputStream in=null;
try {
in = new FileInputStream(fileName);
// ....以后还有代码,此处省略
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (in != null) {
// in没有初始化
in.close();
// in close的时候可能再次发生异常
}
}
}
  1. Exception引发的override问题
1
//ch06.ExceptionSon
  • 一个方法必须通过throws语句在方法的声明部分说明它可能抛出而并未捕获的所有的“必检异常”,如果没有这么做,将不能通过编译;
  • 如果在子类中覆盖了父类的某一方法,那么该子类方法不可以比被其覆盖的父类方法抛出更多的异常(但可以更少)。
  1. 异常到底应该捕捉还是抛出?
  • 捕获并处理那些你知道并且应该负责处理的异常;
  • 对那些你不知道方法的调用者会如何处理的的异常,最好将它们留给方法的调用者进行处理。

异常的优点与用途

优点

  • 极大的降低了程序调试难度,使得错误定位准确;
  • 强制让用户在编译阶段发现一些未解决的问题,提高程序健壮性。

用途

异常的最大用途——查看错误堆栈

1
2
3
4
5
6
7
8
9
10
11
//ch06.ExceptionDemo31
public static void main(String[] args) {
try {
new ExceptionDemo31();
}catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
//编译结果
java.lang.ArrayIndexOutOfBoundsException:10 at ch06.ExceptionDemo31.main(Exception31.java:14)

Points

  • try中代码是有可能一行都不执行的;

  • 处理异常的过程中,可能再次发生异常;

  • Exception包含在java.lang包中,所以使用Exception不需要import;

  • 可以在不存在潜在风险的情况下throws;

  • 不可以在不存在潜在风险的情况下catch;

  • override的时候要考虑Exception;

  • return的时候要考虑Exception;

  • 异常的常见处理方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //ch06.ExceptionDemo31
    public static void main(String[] args) {
    try {
    new ExceptionDemo31();
    }catch(Exception e) {
    System.out.println(e.getMessage());
    e.printStackTrace();
    }
    }
  • throw与throws用处的区别

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //ch06.ThrowsDemo
    public class ThrowsDemo {

    public ThrowsDemo() {
    // TODO Auto-generated constructor stub
    }
    public void method()
    throws SQLException, IOException {
    if(1>2) {
    throw new IOException("IOException");
    }
    if(2>3) {
    throw new SQLException("SQLException");
    }
    }
    }
    • throws:跟在方法声明后面,后面跟着的是异常类名;
    • throw:用在方法体内,后面跟着的是异常类对象名。
  • 处理异常的过程中,可能再次发生异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //ch06.ExceptionInException
    package ch06;
    import java.io.FileInputStream;
    import java.io.IOException;
    /**
    * 这个例子演示了 异常处理中 仍然可能发生异常
    */
    public class ExceptionInException {
    public ExceptionInException() {
    // TODO Auto-generated constructor stub
    }
    public static void main(String[] args) {
    FileInputStream in=null;
    try {
    in=new FileInputStream("D:/temp.txt");
    //..其他操作
    }catch (IOException e) {
    System.out.println(e.getMessage());
    e.printStackTrace();
    if(in!=null) {
    //这行导致的异常处理中的异常
    in.close();
    }
    }
    }
    }

文章源码

文章源码 备用仓库