# 概念

异常就是程序在运行时产生的错误,即 Java 在编译或运行或者运行过程中出现的错误

在 Java 中的解决办法:异常处理机制。

异常处理机制能让程序在发生异常的时候,按照程序员预先设计的异常处理逻辑,来针对的处理异常,使程序能够最大可能的恢复正常运行。

Java 中的异常可以是函数中的语句执行时引发的,也可以是程序员通过 throw 语句手动抛出的,只要在 Java 程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE 就会试图寻找异常处理程序来处理异常。


# 异常处理机制

JVM 的默认处理方式:把异常的名称,原因,位置等信息输出在控制台,同时会结束程序。

处理方式:(以数组下标越界为例)

  • 当索引越界时,JVM 能自动检测到问题,这个异常 Java 本身有描述:异常的名称、异常的内容、异常的产生位置,java 将这些信息直接封装到异常对象中。new ArrayIndexOutOfBoundsException (越界的值)。
  • 将异常的对象抛出,抛给方法的调用者(以 main 方法为例),main 接收到这个异常后,如果没有进行处理异常,就会继续把异常抛给 JVM
  • JVM 接收到这个异常后,首先将异常信息以红色字体输出在控制台,然后终止程序

常用的关键字:try、catch、finally、throw、throws。

try用于监听。将要被监听的代码 (可能抛出异常的代码) 放在 try 语句块之内,当 try 语句块内发生异常时,异常就被抛出。

catch对捕捉到的异常进行处理,处理后程序继续运行

finally特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而 finally 就是解决这个问题的,在 finally 代码块中存放的代码都是一定会被执行的。可以用于释放资源。

throw用来抛出一个具体的异常类型。

throws用来声明一个方法可能产生的所有异常,不做任何处理而是将异常往上传,谁调用我我就抛给谁。

注意
  1. catch 不能独立于 try 存在。
  2. 在 try/catch 后面添加 finally 块并非强制性要求的。
  3. try 代码后不能既没 catch 块也没 finally 块。
  4. try 里面越少越好。
  5. try, catch, finally 块之间不能添加任何代码。
  6. finally 里面的代码最终一定会执行(除了 JVM 退出)。
  7. 如果程序可能存在多个异常,需要多个 catch 进行捕获。
  8. 异常如果是同级关系,catch 谁前谁后没有关系。
  9. 如果异常之间存在上下级关系,上级需要放在后面
throw和throws说明
throws用在方法声明后面,跟的是异常类名
可以跟多个异常类名,用逗号隔开
表示抛出异常,由该方法的调用者来处理
throws 表示出现异常的一种可能性,并不一定会发生这些异常
throw用在方法体内,跟的是异常对象名
只能抛出一个异常对象名
表示抛出异常,由方法体内的语句处理
throw 则是抛出了异常,执行 throw 则一定抛出了某种异常
throws说明
//throws 在方法后面声,自己 (声明 throws 的方法) 不处理,告诉别人 (其他方法) 自己可能出现的异常,让别人处理。
// 
class abnormal{
    public void division(int x,int y) throws ArithmeticException{
        int i = x/y;
    }
    // 调用上面的方法,调用带 throws 声明的方法,必须显式捕获该异常
    public void usedivision(){
        try {
            this.division(1,0);
        } catch (ArithmeticException e) {
            System.out.println("/ by zero");
        }
    }
}

throw说明
class abnormal{
    int[] a = new int[5]; 
    public void ArrayStore(x){
        if(x>=5){
            // 自行抛出 ArrayIndexOutOfBoundsException 异常,既可以显式捕获该异常
            // 也可完全不理会该异常,把该异常交给该方法调用者处理
            throw new ArrayIndexOutOfBoundsException("数值大了");
        }
        for(int i=0;i<x;i==){
            this.a[i]=i;
        }
    }
    public void useArrayStore(){
        
        try {
            this.ArrayStore(6);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("大于数组长度");
            e.printStackTrace();
        }
    }
}
// 调用 useArrayStore 方法后程序运行结果:
// 大于数组长度
//java.lang.ArrayIndexOutOfBoundsException: 数值大了
关键字用法
class abnormal{
    public void static main(String[] args){
        try{
            // 用于监听。将要被监听的代码 (可能抛出异常的代码) 放在 try 语句块之内,当 try 语句块内发生异常时,异常就被抛出。
        }catch(/* 异常类型 异常变量名 例:Exception e*/){
            // 对捕捉到的异常进行处理,处理后程序继续运行
        }finally{
            // 特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而 finally 就是解决这个问题的,在 finally 代码块中存放的代码都是一定会被执行的。可以用于释放资源。
        }
    }
}

# 异常类型

异常分为两种,一种是运行异常,另一种是编译异常。运行异常:抛出的异常是 RuntimeException 类,或者是他的子类;编译异常:调用了抛出异常的方法,不处理编译失败

Throwable: 它是所有错误与异常的超类(父类)
    |- Error 错误,错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。
    |- Exception 编译期异常,进行编译JAVA程序时出现的问题
       |- RuntimeException 运行期异常, JAVA程序运行过程中出现的问题

运行时异常

特点:

  • 方法内部抛出的异常就是运行异常,new XXXException
  • 方法的声明上不需要 throws 语句,调用者也无需处理此异常
  • 运行异常,一旦发生,不要处理,而是修改源码,后面的代码没有执行的意义
异常描述
ArithmeticException当出现异常的运算条件时,抛出此异常。例如,一个整数 "除以零" 时,抛出此类的一个实例。
ArrayIndexOutOfBoundsException用非法索引访问数组时抛出的异常]{.red}。如果索引为负或大于等于数组大小,则该索引为非法索引。
ArrayStoreException试图将错误类型的对象存储到一个对象数组时抛出的异常。
ClassCastException当试图将对象强制转换为不是实例的子类时,抛出该异常。
IllegalArgumentException抛出的异常表明向方法传递了一个不合法或不正确的参数。
IllegalMonitorStateException抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。
IllegalStateException在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。
IllegalThreadStateException线程没有处于请求操作所要求的适当状态时抛出的异常
IndexOutOfBoundsException指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
NegativeArraySizeException如果应用程序试图创建大小为负的数组,则抛出该异常。
NullPointerException当应用程序试图在需要对象的地方使用 null 时,抛出该异常
NumberFormatException当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
SecurityException由安全管理器抛出的异常,指示存在安全侵犯
StringIndexOutOfBoundsException此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。
UnsupportedOperationException当不支持请求的操作时,抛出该异常。

编译异常

异常描述
ClassNotFoundException应用程序试图加载类时,找不到相应的类,抛出该异常。
CloneNotSupportedException当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。
IllegalAccessException拒绝访问一个类的时候,抛出该异常。
InstantiationException当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。
InterruptedException一个线程被另一个线程中断,抛出该异常。
NoSuchFieldException请求的变量不存在
NoSuchMethodException请求的方法不存在

# 在继承和方法重写中

1、父类的方法抛出异常,子类重写后

  • 可以不抛出异常
  • 也可以抛出异常,但抛出的异常不能大于父类的异常(继承关系)

2、父类的方法没有抛出异常,子类重写后

  • 不能抛出异常
  • 如果子类中调用了抛出异常的方法,只能 try...catch... 来处理异常

# 异常方法

Throwable 类:

序号方法及说明
1public String getMessage()
返回关于发生的异常的详细信息。这个消息在 Throwable 类的构造函数中初始化了。
2public Throwable getCause()
返回一个 Throwable 对象代表异常原因。
3public String toString()
使用 getMessage () 的结果返回类的串级名字。
4public void printStackTrace()
打印 toString () 结果和栈层次到 System.err,即错误输出流。
5public StackTraceElement [] getStackTrace()
返回一个包含堆栈层次的数组。下标为 0 的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。
6public Throwable fillInStackTrace()
用当前的调用栈层次填充 Throwable 对象栈层次,添加到栈层次任何先前信息中。

# 自定义异常

如果要自定义异常类,则扩展 Exception 类即可,因此这样的自定义异常都属于检查异常(checked exception)。如果要自定义非检查异常,则扩展自 RuntimeException。

自定义的异常应该总是包含如下的构造函数:

  • 一个无参构造函数
  • 一个带有 String 参数的构造函数,并传递给父类的构造函数。
  • 一个带有 String 参数和 Throwable 参数,并都传递给父类构造函数
  • 一个带有 Throwable 参数的构造函数,并传递给父类的构造函数。

# 一些问题

finally 块和 return

  • 首先一个不容易理解的事实:在 try 块中即便有 return,break,continue 等改变执行流的语句,finally 也会执行
  • finally 中的 return 会覆盖 try 或者 catch 中的返回值
  • finally 中的 return 或异常会抑制(消灭)前面 try 或者 catch 块中的异常。
class test{
    public void A() {
        System.out.println(this.aaa(10));
    }
    public String aaa(int i) {
        try{
            for (i = 0 ;i < 10 ; i++){
                if(i>1){
                    continue;
                }
                if (i > 3){
                    int w = i / 0;
                    break;
                }
                if (i>8){
                    return "10";
                }
            }
        }catch (Exception e){
            return "11";
        }finally {
            return "01";
        }
    }
	public static void main(String[] args){
        this.A();
    }
}
// 控制台输出的是:01