Java 异常基础详解

1. Java 中的异常

前言:Java 中的异常处理是处理程序运行错误时的强大机制之一,它可以保证应用程序的正常流程。

首先我们将了解java异常、异常的类型以及受查和非受查异常之间的区别。

1.1 什么是异常?

字面意义:异常是一种不正常的情况。

在 java 中,异常是扰乱程序正常流程的事件,它是在程序运行时抛出的对象。

1.2 什么是异常处理?

异常处理一种在运行时解决程序错误的机制,例如 ClassNotFound、IO、SQL、Remote 等。

1.2.1 异常处理的优势

异常通常会干扰程序的正常流程,而异常处理的核心优势是维护程序的正常流程。现在让我们假设一下:

statement 1;  
statement 2;  
statement 3;  
statement 4;  
statement 5;//发生异常
statement 6;  
statement 7;  
statement 8;  
statement 9;  
statement 10;  

假设你的程序中有10条语句,如果在第5条中出现了一个异常,那么语句6-10将不会继续执行。如果你使用了异常处理,那么语句6-10的部分将正常执行,这就是我们为什么需要在程序中使用异常处理的原因。

你知道吗?

  • 受查和非受查异常之间的区别是什么?
  • 代码int data=50/0;后面发生了什么?
  • 为什么需要使用多个catch块?
  • finally块是否有可能不执行?
  • 什么是异常传递?
  • throwthrows关键字之间的区别?
  • 对方法重写使用异常处理的4条规则是什么?

现在让我们带着以上问题继续下面的学习。

1.3 Java 异常类的层次结构

throwable

1.4 异常类型

主要有两种类型的异常:受查和非受查异常,Error被视为非受查异常。Sun公司认为有三种异常类型:

  • 受查异常(Checked Exception)
  • 非受查异常(UnChecked Exception)
  • 错误(Error)

1.5 受查和非受查异常之间的区别

1)受查异常

除了RuntimeExceptionError外,继承自Throwable类的类称为受查异常,例如:IOException、SQLException 等。受查异常在编译时进行检查。

常见的有以下几个方面:

  • 试图在文件尾部后面读取数据
  • 试图打开一个不存在的文件
  • 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在

2)非受查异常

继承自RuntimeException类的异常被称为非受查异常,例如:ArithmeticException、 NullPointerException、 ArrayIndexOutOfBoundsException 等。非受查异常不会在编译时检查,而是在运行时进行检查。

常见的有以下几个方面:

  • 错误的类型转换
  • 数组访问越界
  • 访问null指针

“如果出现了RuntimeException异常,那么一定是你自身的问题”,是一条相当有道理的规则。

3)错误(Error)

错误是一种无法恢复的异常类型,通常是在java运行时系统的内部错误和资源耗尽错误。应用程序不应该抛出这种类型的对象。如果出现了这样的内部错误,除了通告给用户,并尽力的使得程序安全的终止之外,再也无能为力了。这种情况很少出现。

1.6 可能出现异常的常见场景

在某些情况下,可能出现未检查的异常,它们如下:

1)发生ArithmeticException的场景

如果我们将任何数字除以0,就会出现一个 ArithmeticException 异常。

int a = 50/0;//ArithmeticException  

2)发生NullPointerException的场景

如果变量的值为null,那么调用此变量将会出现 NullPointerException 异常。

String s = null;  
System.out.println(s.length());//NullPointerException  

3)发生NumberFormatException的场景

任何值的格式错误,都有肯能发生 NumberFormatException 异常。假设一个字符串变量,其中包含了字符,若将此变量转换为数字类型,将会发生 NumberFormatException 异常。

String s = "abc";  
int i = Integer.parseInt(s);//NumberFormatException

4)发生ArrayIndexOutOfBoundsException的场景

如果你在一个不存在的的数组索引中插入任何值,则会导致 ArrayIndexOutOfBoundsException 异常。

int a[] = new int[5];  
a[10] = 50; //ArrayIndexOutOfBoundsException  

1.7 Java 异常处理关键字

下面是 Java 异常处理中的5个关键字:

trycatchfinallythrowthrows

1.8 创建自定义异常类

在程序中,可能会遇到任何标准异常类都没有能够充分地描述清楚的问题。在这种情况下,创建自己的异常类就是一件顺理成章的事情了。我们需要做的只是定义一个派生于 Exception 的类,或者派生于 Exception 子类的类。例如,定义一个派生于 IOException 的类。

习惯上,定义的类应该包含两个构造器,一个是默认构造器,一个是描述详细信息的的构造器(超类 Throwable 的 toString 方法将会打印出这些详细信息,这在调试中非常有用。)

示例如下:

class FileFormatException extends IOException {
    public FileFormatException() {}
    public FileFormatException(String gripe) {
        super(gripe);
    }
}

现在,就可以抛出自己定义的异常类型了。

String readData(BufferedReader in) throws FileFormatException {
    ...
    while (...) {
        // EOF encountered
        if (ch == -1) {
            if (n < len)
                throw new FileFormatException();
        }
        ...
    }
    return s;
}


2. Java try-catch

将可能发生异常的代码放在try块中,且必须在方法中才能使用。try 块后必须使用catch块或finally块。

2.1 Java try 块

1)try-catch 语法

try{  
// 可能抛出异常的代码
}catch(Exception_class_Name ref){}  

2)try-finally 语法

try{  
// 可能抛出异常的代码
}finally{}  

2.2 Java catch 块

Java catch块被用于处理异常,必须在try块后使用。

你可以在一个try块后使用多个catch

2.3 未使用异常处理的问题

如果我们不使用try-catch处理异常,看看会发生什么。

public class Testtrycatch1 {  
    public static void main(String args[]) {  
        int data=50/0;// 可能抛出异常
        System.out.println("代码的其余部分...");  
    }  
}  

输出:

Exception in thread main java.lang.ArithmeticException:/ by zero

如上面的示例所示,代码的其余部分并没有执行。("代码的其余部分..."未打印)

2.4 使用异常处理解决问题

让我们通过try-catch块来查看上述问题的解决方案。

public class Testtrycatch2 {  
    public static void main(String args[]) {  

        try {  
            int data = 50/0;  
        }
        catch(ArithmeticException e) {
            System.out.println(e);
        }  

        System.out.println("代码的其余部分...");  
    }  
}  

输出:

Exception in thread main java.lang.ArithmeticException:/ by zero
代码的其余部分...

现在,正如上面的示例所示,代码的其余部分执行了.(也就是"代码的其余部分..."被打印)

2.5 Java try-catch 内部工作原理

exceptionobject

Java 虚拟机首先检查异常是否被处理,如果异常未处理,则执行的一个默认的异常处理程序:

  • 打印异常描述
  • 打印堆栈跟踪(异常发生方法的层次结构)
  • 终止程序

如果程序员处理了异常,则应用程序按照正常流程执行。


3. 使用多个 catch 块

如果需要在发生不同异常时执行不同的任务,则需要使用多个 catch 块。

查看下面一个简单的多 catch 块示例。

public class TestMultipleCatchBlock{  
    public static void main(String args[]) {  

        try{  
            int a[] = new int[5];  
            a[5] = 30/0;  
        }  
        catch(ArithmeticException e) {
            System.out.println("任务1已完成");
        }  
        catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("任务2已完成");
        }  
        catch(Exception e) {
            System.out.println("已完成通用任务");
        }

        System.out.println("代码的其余部分...");  

    }  
}  

输出:

任务1已完成
代码的其余部分...

规则:一次只有一个异常发生,并且一次只执行一个catch块。

规则: 所有异常必须从最具体到最通用的顺序排序,即捕获ArithmeticException必须在捕获Exception之前发生。

class TestMultipleCatchBlock1 {  
public static void main(String args[]) {  

        try{  
            int a[]=new int[5];  
            a[5]=30/0;  
        }
        catch(Exception e) {
            System.out.println("已完成通用任务");
        }
        catch(ArithmeticException e) {
            System.out.println("任务1已完成");
        }
        catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("任务2已完成");
        }  

        System.out.println("代码的其余部分...");  

    }  
}  

输出:

Compile-time error


4. Java 嵌套 try 块

Java try块中的try块被称为try嵌套块。

4.1 为什么使用 try 嵌套块?

有时可能会出现一种情况,一个块的某个部分可能导致一个错误,而整个块的本身可能会导致另一个错误。在这种情况下,必须使用嵌套异常处理程序。

语法:

....  
try  
{  
    statement 1;  
    statement 2;  
    try  
    {  
        statement 1;  
        statement 2;  
    }  
    catch(Exception e)  
    {  
        ...
    }  
}  
catch(Exception e) {...}  
....  

4.2 Java try 嵌套块示例

class Excep6 {
    public static void main(String args[]) {
        try {
            // try 嵌套块1
            try {
                System.out.println("try 嵌套块1");
                int b = 39 / 0;
            }
            catch(ArithmeticException e) {
                System.out.println(e);
            }
            // try 嵌套块2
            try {
                int a[] = new int[5];
                a[5] = 4;
            }
            catch(ArrayIndexOutOfBoundsException e) {
                System.out.println(e);
            }
            System.out.println("try外部块其他语句...");
        }
        catch(Exception e) {
            System.out.println("handeled");
        }
        System.out.println("正常流...");
    }
}

输出:

try 嵌套块1
java.lang.ArithmeticException: / by zero
java.lang.ArrayIndexOutOfBoundsException: 5
try外部块其他语句...
正常流...


5. Java finally 块

Java finally 块是用来执行重要代码的块(如关闭连接、流等)。

无论是否处理异常,最终都会执行 finally 块。

finally 块紧跟 try 或 catch 块后:

finally

注意:无论你是否处理异常,在终止程序之前,JVM都将执行finally块(如果存在的话)

5.1 为什么要使用 finally 块

finally 块可以用于放置"clear"代码,例如关闭文件,关闭连接等。

5.2 使用 finally 块案例

接下来让我们来看看在不同情况下使用 finally 块。

1)案例1

当前没有发生异常:

class TestFinallyBlock {
    public static void main(String[] args) {
        try {
            int data = 25 / 5;
            System.out.println(data);
        }
        catch (NullPointerException e) {
            System.out.println(e);
        }
        finally {
            System.out.println("finally 块总是执行");
        }
        System.out.println("代码的其余部分...");
    }
}

输出:

5
finally 块总是执行
代码的其余部分...

2)案例2

发生异常但未处理:

class TestFinallyBlock1 {
    public static void main(String[] args) {
        try {
            int data = 25 / 0;
            System.out.println(data);
        }
        catch (NullPointerException e) {
            System.out.println(e);
        }
        finally {
            System.out.println("finally 块总是执行");
        }
        System.out.println("代码的其余部分...");
    }
}

输出:

finally 块总是执行
Exception in thread main java.lang.ArithmeticException:/ by zero

3)案例3

发生异常并处理异常:

public class TestFinallyBlock2 {
    public static void main(String args[]) {
        try {
            int data = 25 / 0;
            System.out.println(data);
        }
        catch(ArithmeticException e) {
            System.out.println(e);
        }
        finally {
            System.out.println("finally 块总是执行");
        }
        System.out.println("代码的其余部分...");
    }
}

输出:

Exception in thread main java.lang.ArithmeticException:/ by zero
finally 块总是执行
代码的其余部分...

规则:对于 try 块可以有0个或多个 catch 块,但仅仅只能有一个 finally 块。

规则:如果程序退出(通过调用 System.exit() 或通过导致进程中止的致命错误),finally块将不会被执行。


6. Java 抛出异常

6.1 Java throw 关键字

Java throw 关键字用于显示的抛出异常。

我们可以使用 throw 关键字在 Java 中抛出检查(Checked)或未检查(UnChecked)异常。throw 关键字主要用于抛出自定义异常。

Java throw 语法如下:

throw exception;  

抛出IOException异常的例子:

throw new IOException("sorry device error");  

6.2 Java throw 示例

在本例中,我们创建了一个将整数值作为参数的 validate 方法。如果年龄小于18岁,我们将抛出一个ArithmeticException异常,否则打印一条消息"欢迎投票"。

public class TestThrow1 {
    static void validate(int age) {
        if(age < 18)  
            throw new ArithmeticException("无效");
        else  
            System.out.println("欢迎投票");
    }

    public static void main(String args[]) {
        validate(13);
        System.out.println("代码的其余部分...");
    }
}

输出:

Exception in thread main java.lang.ArithmeticException:无效


7. Java 异常传递

异常首先从堆栈顶部抛出,如果未捕获,则将调用堆栈下降到前一个方法,如果没有捕获,则将异常再次下降到先前的方法,以此类推,知道它们被捕获或到达调用堆栈底部为止。以上称为异常传递。

规则:默认情况下,非受查异常在调用链中(传递)转发。

异常传递示例:

class TestExceptionPropagation1 {
    void m(){
        int data = 50 / 0;
    }
    void n() {
        m();
    }
    void p() {
        try{
            n();
        }
        catch(Exception e) {
            System.out.println("异常处理器");
        }
    }

    public static void main(String args[]) {
        TestExceptionPropagation1 obj = new TestExceptionPropagation1();
        obj.p();
        System.out.println("正常流...");
    }
}

输出:

异常处理器
正常流...

propagation

在上面的示例中。异常发生在 m() 方法中,如果未对其进行处理,则将其传递到未处理它的前 n() 方法,再次将其传递到处理异常的 p() 方法。

可以在 main()、p()、n()、p()、 m() 中的任何方法中处理异常。

规则:默认情况下,受查异常不会在调用链中(传递)转发。

用于描述受查异常不会在程序中传递的示例:

class TestExceptionPropagation2{
    void m(){
        throw new java.io.IOException("设备异常"); // 受查异常
    }
    void n(){
        m();
    }
    void p(){
        try{
            n();
        }
        catch(Exception e){
            System.out.println("异常处理器");
        }
    }
    public static void main(String args[]){
        TestExceptionPropagation2 obj=new TestExceptionPropagation2();
        obj.p();
        System.out.println("正常流...");
    }
}

输出:

Compile Time Error

编译时发生一个错误,证明受查异常并不会在程序中进行传递。


8. Java throws 关键字

Java throws 关键字被用于声明一个异常。它给程序员提供了一个信息,说明可能会发生异常,所以程序员最好提供异常处理代码,以保证程序正常的流程。

异常处理主要用于处理受查异常,如果出现任何非受查异常,如"NullPointerException",都是程序员自身的错误,请认真检查你的代码。

8.1 Java throws 语法

return_type method_name() throws exception_class_name {  
    // method code  
}  

8.2 应该声明哪个异常?

仅仅声明受查异常,因为:

  • 非受查异常:程序员应该更正代码以确保代码正确无误。
  • Error:无法控制,如果出现了 VirtualMachineErrorStackOverflowError等异常,将无法进行任何操作。

8.3 Java throws 优势

使用 throws 声明受查异常后,使得受查异常可以在调用堆栈中进行(传递)转发。它向处理该异常的方法提供异常信息。

8.4 Java throws 示例

下面的示例描述了受查异常可以通过throws关键字进行传递:

import java.io.IOException;
class Testthrows1{
    void m() throws IOException{
        throw new IOException("设备异常"); // 受查异常
    }
    void n()throws IOException{
        m();
    }
    void p(){
        try{
            n();
        }
        catch(Exception e){
            System.out.println("异常处理器");
        }
    }
    public static void main(String args[]){
        Testthrows1 obj=new Testthrows1();
        obj.p();
        System.out.println("正常流...");
    }
}

输出:

异常处理器
正常流...

规则:如果你正在调用一个声明了异常的方法,则必须捕获或声明异常。

现在有两种情况:

  • 情况1:你遇到了一个异常,使用 try-catch 处理了异常。
  • 情况2:你声明了异常,使用方法指定抛出。

1) 情况1:处理了异常

  • 在这种情况下,如果你处理了异常,则不管程序是否出现了异常,程序都将继续执行。
import java.io.*;
class M{
    void method() throws IOException{
        throw new IOException("设备异常");
    }
}
public class Testthrows2{
    public static void main(String args[]){
        try{
            M m = new M();
            m.method();
        }
        catch(Exception e){
            System.out.println("异常处理器");
        }
        System.out.println("正常流...");
    }
}

输出:

异常处理器
正常流...

2) 情况2:声明了异常

  • A)如果声明了异常,但代码未出现异常,程序将正常执行。
  • B)如果声明了异常且发生了异常,则在运行时抛出异常,因为程序会抛出不处理的异常。

A)声明了异常但未发生异常:

import java.io.*;
class M{
    void method()throws IOException{
        System.out.println("执行设备操作");
    }
}
class Testthrows3{
    public static void main(String args[])throws IOException{
        // 声明了异常
        M m=new M();
        m.method();
        System.out.println("正常流...");
    }
}

输出:

执行设备操作
正常流...

B)声明了异常且发生了异常:

import java.io.*;
class M{
    void method()throws IOException{
        throw new IOException("设备错误");
    }
}
class Testthrows4{
    public static void main(String args[])throws IOException{
        // 声明了异常  
        M m=new M();
        m.method();
        System.out.println("正常流...");
    }
}

输出:

Runtime Exception

程序编译时将直接出现了一个编译错误。

8.5 throw 与 throws 区别

No.throwthrows
1)Java throw 关键字用于显示的抛出异常Java throws 关键字用于声明一个异常
2)受查异常不能只使用 throw 进行传递受查异常可以通过 throws 进行传递
3)Throw 后面跟着一个异常实例Throws 后面跟着一个异常类
4)在方法中使用 ThrowThrows 与方法签名一起使用
5)你不能抛出多个异常你可以声明多个异常,例如public void method() throws IOException,SQLException

1)Java throw 示例:

void m(){  
    throw new ArithmeticException("sorry");  
}  

2)Java throws 示例:

void m()throws ArithmeticException{  
    // method code  
}  

3)Java throw 和 throws 示例:

void m()throws ArithmeticException{  
    throw new ArithmeticException("sorry");  
}  

8.6 思考:可以重新抛出一个异常吗?

答案当然是可以的,可以在 catch 块中抛出相同的异常。这种方法通常用于只想记录一个异常,但不做任何改变。

代码示例:

try {
    // access the database
}
catch (Exceptiom e) {
    logger.log(level, message, e);
    throw e;
}

在 Java SE7 之前,这种方法存在一个问题,假设这段代码在以下方法中:

public void updateRecord() throws SQLException

Java 编译器查看 catch 块中的 throw 语句,然后查看 e 的类型,会指出这个方法可以抛出任何 Exception 而不仅仅是 SQLException。现在这个问题已经有所改进,编译器会追踪到 e 来自 try 块。假设这个 try 块仅有的受查异常是 SQLException 实例,另外,假设 e 在 catch 块中未改变,将外围方法声明为 throws SQLException 是合法的。


9. Final 和 Finally 和 Finalize 对比

Final 和 Finally 和 Finalize 三者之间的差异如下:

No.finalfinallyfinalize
1)final 用于对类、方法和变量加以限制,final 类不能被继承,final 方法不能被重写,final 变量不能被更改finally 用于放置重要的代码,无论异常是否被处理它都会执行finalize 用于在对象被垃圾回收之前执行清理操作
2)final 是一个关键字finally 是一个块finalize 是一个方法

1)Java final 示例:

class FinalExample{
    public static void main(String[] args){
        final int x = 100;
        x = 200; // final 修饰的变量不能被更改
        // 编译时将出错
    }
}

2)Java finally 示例:

class FinallyExample{
    public static void main(String[] args){
        try{
            int x = 300;
        }
        catch(Exception e){
            System.out.println(e);
        }
        finally{
            System.out.println("finally 块始终被执行");
        }
    }
}

3)Java finalize 示例:

class FinalizeExample{
    public void finalize(){
        System.out.println("finalize called");
    }
    public static void main(String[] args){
        FinalizeExample f1 = new FinalizeExample();
        FinalizeExample f2 = new FinalizeExample();
        f1 = null;
        f2 = null;
        System.gc();
    }
}

10. 异常处理方法的重写

关于重写异常处理方法的规则如下:

  • 超类方法没有声明异常:
    如果超类方法没有声明异常,则子类重写方法不能声明受查异常,但可以声明非受查异常。
  • 超类方法声明了异常:
    如果超类方法声明了异常,则子类重写方法可以声明与超类方法相同的异常,也可以不声明异常。若父类方法声明父类异常,子类重写方法声明子类异常也可以,反之不可以。

1)如果超类方法没有声明异常

超类方法未声明异常,子类重写方法声明受查异常的示例:

import java.io.*;
class Parent{
    void msg(){
        System.out.println("parent");
    }
}
class TestExceptionChild extends Parent{
    void msg() throws IOException{
        System.out.println("Child");
    }
    public static void main(String args[]){
        Parent p = new TestExceptionChild();
        p.msg();
    }
}

输出:

Compile Time Error

超类方法未声明异常,子类重写方法声明非受查异常的示例:

import java.io.*;
class Parent{
    void msg(){
        System.out.println("parent");
    }
}
class TestExceptionChild1 extends Parent{
    void msg() throws ArithmeticException{
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p = new TestExceptionChild1();
        p.msg();
    }
}

输出:

child

2)如果超类方法声明了异常

A)超类方法声明了异常,子类重写方法声明不相同父类异常的示例:

import java.io.*;
class Parent{
    // 声明了子类异常
    void msg() throws ArithmeticException{
        System.out.println("parent");
    }
}
class TestExceptionChild2 extends Parent{
    // 声明了父类异常
    void msg() throws Exception{
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p = new TestExceptionChild2();
        try{
            p.msg();
        }
        catch(Exception e){
        }
    }
}

输出:

Compile Time Error

B)超类方法声明了异常,子类重写方法声明相同异常的示例:

import java.io.*;
class Parent{
    void msg()throws Exception{
        System.out.println("parent");
    }
}
class TestExceptionChild3 extends Parent{
    void msg()throws Exception{
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p=new TestExceptionChild3();
        try{
            p.msg();
        }
        catch(Exception e){
        }
    }
}

输出:

child

C)超类方法声明了异常,子类重写方法声明不相同子类异常的示例:

import java.io.*;
class Parent{
    // 声明了父类异常
    void msg()throws Exception{
        System.out.println("parent");
    }
}
class TestExceptionChild4 extends Parent{
    // 声明了子类异常
    void msg()throws ArithmeticException{
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p=new TestExceptionChild4();
        try{
            p.msg();
        }
        catch(Exception e){
        }
    }
}

输出:

child

D)超类方法声明了异常,子类重写方法未声明异常的示例:

import java.io.*;
class Parent{
    void msg()throws Exception{
        System.out.println("parent");
    }
}
class TestExceptionChild5 extends Parent{
    void msg(){
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p=new TestExceptionChild5();
        try{
            p.msg();
        }
        catch(Exception e){
        }
    }
}

输出:

child

参考文章:https://www.javatpoint.com/exception-handling-in-java
参考书籍:《Java核心技术 卷1》

转载于:https://www.cnblogs.com/nwgdk/p/8862353.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值