Java:异常及相关内容和通过异常处理问题

声明

1)该文章部分内容整理自网上的资料,如不小心侵犯了大家的权益,还望海涵,并联系博主删除。

2)博主是萌新上路,文中如有不当之处,请各位大佬指出,共同进步,谢谢。

概念

在《Java编程思想》P248页有这样一段话:

“ 异常 ”这个词有 “ 我对此感到意外 ”的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理;你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在这里将作出正确的决定。

因此,使用异常一般能够降低错误处理代码的复杂度,而且能把 ”描述在正常执行过程中做什么事“ 的代码和 ”出了问题怎么办“ 的代码相分离。

异常发生的原因有很多,通常包含以下几大类:

用户输入了非法数据。

要打开的文件不存在。

网络通信时连接中断,或者JVM内存溢出。

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

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

运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。

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

基本异常

异常情形(exceptional condition) 是指阻止当前方法或作用域继续执行的问题。在当前环境下无法获得必要的信息来解决问题,你所能做的就是从当前环境跳出,并且把问题提交给上一级环境,这就是抛出异常时所发生的事情。

if(t == null)
    throw new NullPointerException();

这就抛出了异常,于是在当前环境下就不必再为这个问题操心了,它将在别的地方得到处理。

当抛出异常后,有几件事会随之发生。首先,同 Java 中其他对象的创建一样,将使用 new 在堆上创建异常对象。然后,当前的执行路径(它不能继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。

我们还可以将异常看作是一种内建的恢复(undo)系统,因为(在细心使用的情况下)我们在程序中可以拥有各种不同的恢复点。如果程序的某部分失败了,异常将“恢复”到程序中某个已知的稳定点上。

下面为一个实例代码:

import java.util.Random;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Random random = new Random();
        try{
    
    
            System.out.println("Before exception.");
            if(random.nextInt(1)>-1)                        //随机生成 0-1 的数
                throw new Exception();                      //由于编译器太聪明,故加入if语句避免报错
            System.out.println("After exception.");         //如果不使用条件限制,这条语句将会报错
        }catch (Exception e){
    
    
            e.printStackTrace();
        }

        System.out.println("Out of try.");
    }
}

运行结果

Before exception.
Out of try.
java.lang.Exception
	at Test.main(Test.java:9)

由以上运行结果可知,当异常Exception()出现的时候,在异常后面的语句System.out.println("After exception.");就不再执行,直到异常被捕获catch(){}后,程序才正常执行,即语句System.out.println("Out of try.");被编译。

运行时异常

在Java中运行时异常都是RuntimeException类及其子类异常,常见的子类有以下几种:

名称 作用
ClassCastException 多态中,可以使用Instanceof 判断,进行规避
ArithmeticException 进行if判断,如果除数为0,进行return
NullPointerException 进行if判断,是否为null
ArrayIndexOutOfBoundsException 使用数组length属性,避免越界

下面将对其中一个子类进行举例分析:

import java.util.Random;

public class Practice {
    
    
    public static void main(String[] args) {
    
    
        Random random = new Random();
        int i = 10,x;
        if(random.nextInt(1)<2){
    
    
            i = 0;
        }
        x=10/i;
    }
}

运行结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Practice.main(Practice.java:10)

由上述代码及运行结果可知,程序在运行前,编译器不会报错,只是再运行时,编译器发现了错误并进行了报错,因此对于运行时异常,一般只修改源代码,而不像检查性异常一般,对抛出的异常进行修改。

异常参数

throw关键字

所有标准异常类都有两个构造器:一个是无参构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:

throw new NullPointerException("t = null");

关键字throw能够抛出任意类型的 Throwable 对象,它是异常类型的根类。通常,对于不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示。(通常,唯一的信息只有异常的类型名,而在异常对象内部没有任何有意义的信息。)

Exception 类的层次

所有的异常类是从 java.lang.Exception 类继承的子类。

Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。

Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。

Error 用来指示运行时环境发生的错误,例如,JVM 内存溢出。

异常类有两个主要的子类:IOException 类和 RuntimeException 类。
图例

捕获异常

try块

如果在方法内部抛出了异常(或者在方法内部调用的其他方法抛出了异常),可以在方法内设置一个特殊的块来捕获异常。因为在这个块里“尝试”各种(可能产生异常的)方法调用,所以称为 try 块。它是跟在 try 关键字之后的普通程序块:

try {
    
    
    // Code that might generate exceptions
}

catch关键字

抛出的异常必须在某处得到处理,这的地方就是异常处理程序,而且针对每个要捕获的异常,得准备相应的处理程序。异常处理程序紧跟在 try 块之后,以关键字 catch 表示:

try {
    
    
    // Code that might generate exceptions
} catch(Type1 id1) {
    
    
    // Handle exceptions of Type1
} catch(Type2 id2) {
    
    
    // Handle exceptions of Type2
} catch(Type3 id3) {
    
    
    // Handle exceptions of Type3
}
// etc.

需要注意的是catch 不能独立于 try 存在,catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。

如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方法是一样。

finally关键字

finally,英译为”最后地,最终地“,顾名思义,就是结束异常处理程序。finally 关键字用来创建在 try 代码块后面执行的代码块。无论是否发生异常,finally 代码块中的代码总会被执行(除JVM退出)。在 try/catch 后面添加 finally 块并非强制性要求的。在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。

try {
    
    
    // Code that might generate exceptions
} catch(Type1 id1) {
    
    
    // Handle exceptions of Type1
} catch(Type2 id2) {
    
    
    // Handle exceptions of Type2
} finally{
    
    
    //释放资源代码
}

异常处理程序的执行图

在这里插入图片描述

异常继承

一个类继承另一个类后,如果父类出现异常,则子类也会随着异常。

import java.util.Random;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Random random = new Random();
        try{
    
    
            System.out.println("Before exception.");
            if(random.nextInt(1)>-1)                         //随机生成0-1的数
                throw new SonException();                    //由于编译器太聪明,故加入if语句避免报错
            System.out.println("After exception.");          //如果不使用条件限制,这条语句将会报错
        }catch (Exception e){
    
    
            e.printStackTrace();
        }

        System.out.println("Out of try.");
    }
}

class FatherException extends Exception{
    
    
    FatherException(){
    
    
        super();
    }
    FatherException(String s){
    
    
        super(s);
    }
}

class SonException extends FatherException{
    
    
    SonException(){
    
    
        super();
    }
    SonException(String s){
    
    
        super(s);
    }
}

运行结果:

Before exception.
Out of try.
SonException
	at Test.main(Test.java:9)

由上述代码以及运行结果清晰可见,类FatherException继承了Exception的异常,而SonException又继承了FatherException的异常。

如果我们一开始捕获了父类的异常,则不需要接下去捕获子类的异常了,因为已经从源头将异常捕获了,如果继续捕获子类异常,编译器将会进行报错:
在这里插入图片描述

但我们先捕获子类异常,再捕获父类异常的行为是被编译器所允许的:
在这里插入图片描述

在一个方法中调用另一个方法也是如此!

异常方法

throws关键字

throws关键字用在方法签名中,用于声明该方法可能抛出的异常。 主方法上也可以使用throws抛出。 如果在主方法上使用了throws抛出,就表示在主方法里面可以不用强制性进行异常处理,如果出现了异常,就交给JVM进行默认处理,则此时会导致程序中断执行。

//接着前面的代码
class Basic{
    
    
    public void f() throws FatherException{
    
    }
    }
}

尽管类Basic里所创建的f()方法里啥都没有,但当我们调用这个方法时,编译器还是会要求我们对这个异常方法进行处理。

我们也可以在f()方法里抛出异常,例如:

    public void f() throws FatherException{
    
    
        throw new FatherException();
    }

此时,我们可以在f()方法里处理异常,这样我们在调用f()方法时,就不需要再对异常进行处理了。

    public void f(){
    
                            //由于异常已经在内部解决,没必要再抛出了
        try{
    
    
            throw new FatherException();
        }catch (Exception e){
    
    }
    }

同时,一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。

    public void f() throws FatherException,Exception{
    
    
        throw new FatherException();
    }

猜你喜欢

转载自blog.csdn.net/weixin_46263782/article/details/107654341