欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

一篇文章带你理解并掌握Java的异常

程序员文章站 2022-06-19 16:54:35
文章目录前言一、异常处理二、异常类型三、异常处理的其它方面1.声明异常2.抛出异常3.捕获异常4.示例学习:声明、抛出和捕获异常四、finally子句五、何时使用异常六、重新抛出异常七、链式异常八、创建自定义异常类总结前言异常是运行时错误,异常处理使得程序可以处理运行时错误,并且继续通常的执行。异常机制提供了程序退出时的安全通道。当出现错误后,程序执行流程发生改变,程序的控制权转移到异常处理器。编译错误:程序没有遵循语法规则,编译程序能够自己发现并提示我们错误的原因和位置。运行时错误:程序在执行....


前言

异常是运行时错误,异常处理使得程序可以处理运行时错误,并且继续通常的执行。异常机制提供了程序退出时的安全通道。当出现错误后,程序执行流程发生改变,程序的控制权转移到异常处理器。

编译错误:程序没有遵循语法规则,编译程序能够自己发现并提示我们错误的原因和位置。

运行时错误:程序在执行时,运行环境发现了不能执行的操作。

逻辑错误:程序没有按照预期的逻辑顺序执行。

在程序运行过程中,如果JVM检测出一个不可能执行的操作,就会出现运行时错误。例如,如果使用一个越界的下标访问数组,程序就会产生一个ArrayIndexOutOfBoundsException的运行时错误;如果程序需要输入一个整数的时候用户输入了一个double值,会得到InputMismatchException的运行时错误。

在Java中,运行时错误会被作为异常抛出。异常就是一种对象,表示阻止正常运行的错误或者情况。如果异常没有处理,那么程序将会非正常终止。


一、异常处理

异常是从方法抛出的,方法的调用者可以捕获并处理该异常。

为了演示异常处理,包括异常是如何创建以及抛出的,我们从读取两个整数并显示它们商的例子开始。

import java.util.Scanner;

/**
 * @author MNH
 * @version 1.0
 * @project Name: JAVA基础
 * @file Name: Quotient
 * @desc 功能描述
 * @by IDE: IntelliJ IDEA
 */
public class Quotient {
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        System.out.println("Enter two integer:");
        int num1=input.nextInt();
        int num2=input.nextInt();
        System.out.println(num1+"/"+num2+"="+(num1/num2));
    }
}

一篇文章带你理解并掌握Java的异常
如果输入0赋值给第二个数字,那就会产生一个运行时错误,因为不能用一个整数除以0(注意,一个浮点数除以0是不会产生异常的)。解决这个错误的一个简单方法就是添加一个if语句来测试第二个数字。

import java.util.Scanner;

/**
 * @author MNH
 * @version 1.0
 * @project Name: JAVA基础
 * @file Name: QuotientWithIf
 * @desc 功能描述
 * @by IDE: IntelliJ IDEA
 */
public class QuotientWithIf {
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        System.out.println("请输入两个整数:");

        int num1=input.nextInt();
        int num2=input.nextInt();

        if(num2!=0){
            System.out.println(num1+"+"+num2+"="+(num1/num2));
        }else{
            System.out.println("被除数不能为0");
        }
    }
}

为了介绍异常处理,我们使用一个方法计算商,程序如下:

import java.util.Scanner;

/**
 * @author MNH
 * @version 1.0
 * @project Name: JAVA基础
 * @file Name: QuotientWithMethod
 * @desc 功能描述
 * @by IDE: IntelliJ IDEA
 */
public class QuotientWithMethod {
    public static int quotient(int num1,int num2){
        if(num2==0){
            System.out.println("被除数不能为0");
            System.exit(1);
        }
        return num1/num2;
    }

    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);

        System.out.println("请输入两个整数:");

        int num1=input.nextInt();
        int num2=input.nextInt();

        System.out.println(num1+"/"+num2+"="+quotient(num1,num2));
    }
}

一篇文章带你理解并掌握Java的异常
一篇文章带你理解并掌握Java的异常
方法quotient返回两个整数的商,如果num2为0,则不能返回一个值,程序在System.exit(1)处终止,这显然是一个问题。不应该让方法来终止程序—应该由调用者决定是否终止程序。

方法如何通知它的调用者一个异常产生了呢?Java可以让一个方法抛出一个异常,该异常可以被调用者捕获和处理。

import java.util.Scanner;

/**
 * @author MNH
 * @version 1.0
 * @project Name: JAVA基础
 * @file Name: QuotientWithException
 * @desc 功能描述
 * @by IDE: IntelliJ IDEA
 */
public class QuotientWithException {
    public static void quotient(int num1, int num2) {
        try {
            if (num2 == 0) {
                throw new ArithmeticException("被除数不能等于0");//抛出一个异常
            }
            System.out.println(num1 + "/" + num2 + "=" +(num1/num2));
        }catch (ArithmeticException e) {
            System.out.println("被除数不能为0");
        }
        System.out.println("程序继续......");
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.println("请输入两个整数:");

        int num1 = input.nextInt();
        int num2 = input.nextInt();

        quotient(num1,num2);
    }
}

//异常就是从一个异常类创建的对象

//本程序中异常就是Java.lang.ArithmeticException的构造方法ArithmeticException(str)被调用以创建异常类对象,str是描述异常的消息

一篇文章带你理解并掌握Java的异常
如果num2等于0,,方法通过执行以下语句抛出一个异常:throw new ArithmeticException(“被除数不能为0”)。在这种情况下,抛出的值为new ArithmeticException(“被除数不能为0”),称为一个异常。throw语句的执行称为抛出一个异常。异常就是从异常类创建的对象。在这种情况下,异常类就是java.lang.ArithmeticException。构造方法ArithmeticException(str)被调用以构建一个异常对象,其中str是描述异常的消息。

当异常被抛出时,正常的执行流程被中断。就像他的名字所提示的,“抛出异常”就是将异常从一个地方传递到另一个地方。调用方法的语句也包含在一个try块和一个catch块中。try块包含了正常情况下执行的代码,异常被catch块所捕获。catch块中的代码被执行以处理异常,之后,catch块之后的语句被执行。

throw语句类似方法的调用,但不同于调用方法的是,它调用的是catch块。从某种意义上讲,catch块就像带参数的方法定义,这些参数匹配抛出的值的类型。但是它不像方法,在执行完catch块之后,程序控制不返回到throw语句,而是执行catch块后的下一条语句。

catch(ArithmeticException ex)中的标识符ex的作用很像是方法中的参数,这个参数称为catch块中的参数。ex之前的类型(如,ArithmeticException)指定了catch块可以捕获的异常类型。一旦捕获该类型,就能从catch块体中的参数访问这个抛出的值。

一个异常可能是通过try块中的throw语句直接抛出,或者调用一个可能会抛出异常的方法而抛出。

异常处理的优点:它能使方法抛出一个异常给它的调用者,并由调用者处理该异常。如果没有这个能力,那么被调用的方法就必须自己处理异常或者终止该程序。被调用的方法通常不知道在出现错误时应该做些什么,只有调用者自己清楚出现错误时需要做些什么。异常处理最根本的优势就是将检测错误(由被调用的方法完成)从处理错误中分离出来。

import java.util.Scanner;

/**
 * @author MNH
 * @version 1.0
 * @project Name: JAVA基础
 * @file Name: InputMismatchException
 * @desc 功能描述
 * @by IDE: IntelliJ IDEA
 */
public class InputMismatchException {
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        boolean continueInput=true;

        while(continueInput){
            try{
                System.out.println("请输入一个整数:");
                input.nextInt();
                continueInput=false;
            }catch(java.util.InputMismatchException e){
                System.out.println("输入的不是整数,请重新输入");
                input.nextLine();
            }
        }
    }
}

//变量continueInput控制循环,它的初始值是true,当键入一个整数时,该值就变成false

一篇文章带你理解并掌握Java的异常
当执行input.nextInt()时,如果输入的不是一个整数,就会产生一个InputMismatchException异常。

二、异常类型

异常是对象,对象都采用类来定义。异常的根类是java.lang.Throwable。Throwable类是所有异常类的根,所有Java异常类都直接或间接地继承自Throwable,可以通过继承Exception或者Exception的子类来创建自己的异常类。

这些异常类可以分为三种主要类型:系统错误、异常或运行时异常。(1)系统错误:系统错误是由Java虚拟机抛出的,用Error类表示的。Error类描述的是内部系统错误,这样的错误很少发生。如果发生,除了通知用户以及尽量稳妥地终止程序外,几乎什么也做不了。(2)异常:异常是用Exception类表示的,它描述的是由你的程序和外部环境引起的错误,这些错误能被程序捕获和处理。(3)运行时异常:运行时异常是用RuntimeException类表示的,它描述的是程序设计错误,例如,错误的类型转换、访问一个越界数组或数值错误,运行时异常通常表明了编程错误。

RuntimeException、Error以及它们的子类都称为免检异常,在大多数情况下,免检异常都会反映出程序设计上不可恢复的逻辑错误。例如,如果通过一个引用变量访问一个对象之前并未将一个对象赋值给它,就会抛出NullPointerException异常;如果访问一个数组的越界元素,就会抛出IndexOutOfBoundsException异常。这些都是程序中必须纠正的逻辑错误。免检异常可能在程序的任何一个地方出现,为避免过多地使用try-catch块,Java语言不强制要求编写代码捕获或声明免检异常。所有其它异常称为必检异常,意味着编译器会强制程序员检查并通过try-catch块处理它们,或者在方法头中声明。

必检异常增加了代码的健壮性,但是为了对异常进行抛出、捕获和处理,需要增加较多代码,会降低代码的可读性。如果异常影响到系运行的安全性和正确性的时候,必须对必检异常进行处理,否则这些必检异常是可以转换成免检异常。

三、异常处理的其它方面

Java的异常处理模型基于三种基本操作:声明一个异常、抛出一个异常、捕获一个异常。

1.声明异常

Java解释器调用main方法开始执行一个程序,当前执行的语句必属于某个方法,每个方法都必须声明它可能抛出的必检异常的类型,称为声明异常。因为任何代码都可能发生系统错误和运行时错误,因此Java不要求在方法中显示地声明Error和RuntimeException。然而,方法要跑出的其它异常都必须在方法头中显示声明,这样,方法的调用者会被告知有异常。

为了在方法中声明一个异常,就要在方法头中使用关键字throws,如:public void method() throws IOException,关键字throws表明method()方法可能会抛出异常IOException。如果方法可能抛出多个异常,就可以在关键字throws后添加一个用逗号分隔的异常列表:public void method() throws Exception1,Exception2,…,ExceptionN。如果父类中的方法没有声明异常,那么就不能在子类中对其重写时声明异常。

2.抛出异常

检测到错误的程序可以创建一个合适的异常类型的实例并抛出它,这称为抛出一个异常,如throw new IllegalArgumentException(“Wrong argument”)。IllegalArgumentException是Java API中的一个异常类,通常,Java API中每个异常类都至少有两个构造方法:一个无参构造方法和一个带有可以描述这个异常的String参数的构造方法,该参数称为异常消息,它可以通过一个异常对象调用getMessage()获取。

声明异常的关键字是throws,抛出异常的关键字是throw。

3.捕获异常

当抛出一个异常时,可以在try-catch块中捕获和处理它。

try{

}catch(Exception1 ex1){

}catch(Exception2 ex2){

}
...
catch(ExceptionN exN){

}

如果在执行try块的过程中没有出现异常,则跳过catch子句。如果try块中的某条语句抛出一个异常,Java就会跳过try块中剩余的语句,然后开始查找处理这个异常的代码,处理异常的这个代码称为异常处理器。从第一个到最后一个逐个检查catch块,判断在catch块中的异常类实例是否是该异常对象的类型。如果是,就将该异常对象赋值给所声明的变量,然后执行catch块中的代码。如果没有发现异常处理器,Java会退出这个方法,把异常传递给这个方法的调用者,继续同样的过程来查找处理器。如果在调用的方法链中找不到处理器,程序就会终止并且在控制台上打印出错误信息。查找处理器的过程称为捕获一个异常。

假设main方法调用method1()方法,method1()方法调用method2()方法,method2()方法调用method3()方法,method3()方法抛出一个异常,考虑下面的情形:

main method{
	try{
		method1();
		statement1
	}catch(Exception1 ex1){

	}
	statement2
}

class method1{
	try{
		method2();
		statement3
	}catch(Exception2 ex2){

	}
	statement4
}

class method2{
	try{
		method3();//一个异常在method3()方法中抛出
		statement5
	}catch(Exception3 ex3){

	}
	statement6
}

如果异常类型是Exception3,它就会被method2()中处理异常ex3的catch块捕获,跳过statement5,然后执行statement6。

如果异常类型是Exception2,,则退出method2()方法,控制被返回给method1()方法,而这个异常就会被method1()中处理异常ex2的catch块捕获,跳过statement3,然后执行statement4。

如果异常类型是Exception1,则退出method1()方法,控制被返回给main()方法,而这个异常就会被main()方法中处理异常ex1的catch块捕获,跳过statement1,然后执行statement2。

如果异常类型没有在method2、method1、main方法中捕获,程序就会终止,不执行statement1和statement2。

各种异常类都可以从一个共同的父类中派生。如果一个catch块可以捕获一个父类的异常对象,那么它就可以捕获那个父类的所有子类的异常对象。

在catch块中异常被指定的顺序是非常重要的,如果父类的catch块出现在子类的catch块之前,就会导致编译错误。

try{

}catch(Exception e){

}catch(RuntimeException ex){

}

//错误的顺序
try{

}catch(RuntimeException e){

}catch(Exception ex){

}

//正确的顺序

对于使用同样的代码处理多种异常的情况,可以使用JDK7的新的多捕获特征简化异常的代码书写。语法是:

catch(Exception1 | Exception2 | ... |ExceptionN e){

}

每个异常使用竖线(|)与下一个分隔,如果其中一个异常被捕获,则执行处理的代码。

4.示例学习:声明、抛出和捕获异常

创建一个CircleWithException类的对象,如果setRadius(double radius)中radius是负数,则抛出一个IllegalArgumentException异常。

/**
 * @author MNH
 * @version 1.0
 * @project Name: JAVA基础
 * @file Name: CircleWithException
 * @desc 功能描述
 * @by IDE: IntelliJ IDEA
 */
public class CircleWithException {
    private double radius;
    private static int numberOfCircle=0;

    public CircleWithException(double radius){
        setRadius(radius);
        numberOfCircle++;
    }

    public CircleWithException(){
        this(0);
    }

    public double getRadius(){
        return radius;
    }

    public void setRadius(double radius){
        if(radius>=0) {
            this.radius = radius;
        }else{
            throw new IllegalArgumentException("半径不能为负数");
        }
    }

    public double area(){
        return radius*radius*3.14;
    }

    public static int getNumberOfCircle(){
        return numberOfCircle;
    }

    public static void main(String[] args) {
        try{
            CircleWithException circleWithException1=new CircleWithException(2.0);
            CircleWithException circleWithException2=new CircleWithException(-2.0);
            CircleWithException circleWithException3=new CircleWithException();
        }catch(IllegalArgumentException e){
            System.out.println(e.toString());
        }

        System.out.println("共有"+numberOfCircle+"个圆被创建");
    }
}

一篇文章带你理解并掌握Java的异常
IllegalArgumentException是异常类RuntimeException(免检异常)的子类,所以,如果不使用try语句,这个测试程序也能编译成功。

万一出现了异常,程序仍然会继续,而如果处理器没有捕获到这个异常,程序就会突然中断。

当没有异常发生时,try-catch的存在对系统性能的影响很小,可以说不会引起额外的系统开销。

异常发生后:(1)初始化异常对象(2)从调用栈返回(3)沿方法调用链来传播异常,以找到它的异常处理器。

四、finally子句

无论异常是否发生,finally子句总会被执行。

有时候,不论异常是否出现或是否被捕获,都希望执行某些代码。Java的finally子句可以用来实现这个目的,finally子句的语法如下:

try{
	statements;
}catch(Exception e){

}finally{
	finalStatements;
}

在任何情况下,finally块中的代码都会执行,不论try块中是否出现异常或者是否被捕获。考虑以下三种可能出现的情况:(1)如果try块中没有出现异常,执行fianlStatements,然后执行try语句的下一条语句;(2)如果try块中有一条语句引起了异常并被catch块捕获,会跳过try块的其它语句,执行catch块和finally子句,并执行try语句后的下一条语句;(3)如果try块中的一条语句引起异常,但是没有被任何catch块捕获,就会跳过try块中的其它语句,执行finally子句,并且将异常传递给这个方法的调用者。

即使在到达finally块之前有一个return语句,finally块还是会执行,同时使用finally子句时可以省略catch块。

五、何时使用异常

当错误需要被方法的调用者处理的时候,方法应该抛出一个异常。

try块包含正常情况下执行的代码,catch块包含异常情况下执行的代码。异常处理将错误处理代码从正常的编程中分离出来,这样,就可以使程序更易读、更易修改。由于异常处理需要初始化新的异常对象,需要从调用栈返回,而且还需要沿着方法调用链来传播异常以便找到它的异常处理器,所以异常处理需要更多的时间和资源。

异常发生在方法中,如果想让该方法的调用者处理异常,应该创建一个异常对象并将其抛出。如果能在发生异常的方法中处理异常,那么就不需要抛出或使用异常。一般来说,一个项目中多个类都会发生的共同异常应该考虑设计为一个异常类。对于发生在个别方法中的简单错误最好进行局部处理,无需抛出异常,这可以通过使用if语句来检测错误并实现。

六、重新抛出异常

如果异常处理器不能处理一个异常,或者只是简单地希望他的调用者注意到该异常,Java允许该异常处理器重新抛出异常。

try{
	statements;
}catch(Exception ex){
	throw ex;
}

语句throw ex重新抛出异常给调用者,以便调用者的其它处理器获得处理异常ex的机会。

七、链式异常

与另一个异常一起抛出一个异常,构成了链式异常。

有时候,可能需要同最初异常一起抛出一个新异常(带有附加信息),称为链式异常。

/**
 * @author MNH
 * @version 1.0
 * @project Name: JAVA基础
 * @file Name: ChainedExceptionDemo
 * @desc 功能描述
 * @by IDE: IntelliJ IDEA
 */
public class ChainedExceptionDemo {
    public static void method3() throws Exception{
        throw new Exception("异常来自method3()方法");
    }

    public static void method2() throws Exception{
        try{
            method3();
        }catch(Exception exc){
            throw new Exception("异常来自method2()方法",exc);
        }
    }

    public static void method1() throws Exception{
        try{
            method2();
        }catch(Exception ex){
            throw new Exception("异常来自method1()方法",ex);
        }
    }

    public static void main(String[] args) {
        try{
            method1();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

一篇文章带你理解并掌握Java的异常

八、创建自定义异常类

可以通过继承Java.lang.Exception类来定义一个自定义异常类。

Java提供相当多的异常类,尽量使用它们而不要创建自己的异常类。如果遇到一个不能用预定义异常类来充分描述的问题,那就可以通过继承Exception类或其子类来创建自己的异常类。

/**
 * @author MNH
 * @version 1.0
 * @project Name: JAVA基础
 * @file Name: InvalidRadiusException
 * @desc 功能描述
 * @by IDE: IntelliJ IDEA
 */
public class InvalidRadiusException extends Exception{
    private double radius;

    public InvalidRadiusException(double radius){
        super("Invalid radius:"+radius);
        this.radius=radius;
    }

    public double getRadius(){
        return radius;
    }
}

这个自定义异常类继承自java.lang.Exception,而Exception类继承自java.lang.Throwable,Exception类中的所有方法(例如,getMessage()、toString()、printStackTrace())都是从Throwable继承而来。

Exception类的构造方法:
Exception():构建一个没有信息的异常
Exception(String message):构建一个给定信息的异常
Exception(String message,Exception cause):构建一个具有指定信息和子句的异常,这形成了一个链式异常

/**
 * @author MNH
 * @version 1.0
 * @project Name: JAVA基础
 * @file Name: TestCircleWithCustomException
 * @desc 功能描述
 * @by IDE: IntelliJ IDEA
 */
public class TestCircleWithCustomException {
    public static void main(String[] args) {
        try {
            CircleWithCustomException circle1 = new CircleWithCustomException(2);
            CircleWithCustomException circle2 = new CircleWithCustomException(-2);
            CircleWithCustomException circle3 = new CircleWithCustomException();
        } catch (InvalidRadiusException ex) {
            System.out.println(ex);
        }
        System.out.println("创建圆的个数:" + CircleWithCustomException.getNumberOfCircle());
    }
}

class CircleWithCustomException {
    private double radius;

    private static int numberOfCircle = 0;

    public CircleWithCustomException(double radius) throws InvalidRadiusException {
        setRadius(radius);
        numberOfCircle++;
    }

    public CircleWithCustomException() throws InvalidRadiusException {
        this(0);
    }

    public void setRadius(double radius) throws InvalidRadiusException {
        if (radius >= 0) {
            this.radius = radius;
        } else {
            throw new InvalidRadiusException(radius);
        }
    }

    public double getRadius() {
        return radius;
    }

    public static int getNumberOfCircle() {
        return numberOfCircle;
    }

    public double area() {
        return 3.14 * radius * radius;
    }
}

一篇文章带你理解并掌握Java的异常
也可以继承RuntimeException声明一个自定义类异常,但这不是一个好方法,因为这会使自定义异常成为免检异常。最好使自定义异常是必检的,这样编译器就可以在程序中强制捕获这些异常。

本文地址:https://blog.csdn.net/julac/article/details/112607629

相关标签: Java