×
广告

异常处理

96
承思
2017.10.11 01:09* 字数 1838

抛出异常

异常处理使得程序可以处理非预期的情景,并且能够继续正常的操作

在java中,运行时错误会作为异常抛出。异常就是一种对象,表示阻止正常进行程序执行的错误或者情况。

异常是从方法抛出的。方法的调用者可以捕获以及处理该异常。
我们可以先从简单的算术异常错误来了解抛出异常的作用

import java.util.Scanner;
public class Quotient {
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        System.out.println("Enter two integers: ");
        int number1=input.nextInt();
        int number2=input.nextInt();
        System.out.println(number1+" / "+number2+" is "+(number1/number2));
    }
}
第二个整数为0时,产生错误

当第二个数字输入的是数字0的时候,就会产生一个运行时错误,因为不能用一个整数除以一个0(但是在java中浮点型除以0将不会抛出异常)
要想解决这个错误,一个简单的方法就是添加一个if语句来测试第二个数字。

import java.util.Scanner;
public class Quotient {
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        System.out.println("Enter two integers: ");
        int number1=input.nextInt();
        int number2=input.nextInt();
        if(number2!=0)
        System.out.println(number1+" / "+number2+" is "+(number1/number2));
        else
            System.out.println("Divisor cannot be zero");
    }
}
第二个整数为0时,没有错误了

为了介绍异常处理,重新再写一个方法来计算商。

import java.util.Scanner;
public class QuotienWithMethod {
    public static int quotient(int number1,int number2){
        if(number2==0){
            System.out.println("Divisor cannot be zero");
            System.exit(1);//表示程序非正常退出
        }
        return number1/number2;
    }
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        int number1=input.nextInt();
        int number2=input.nextInt();
        
        int result=quotient(number1, number2);
        System.out.println(number1+" / "+number2+" is "+result);
    }
}
没有弹出错误

但是这种方法是强行结束掉整个程序,而且不应该是被调用的方法来结束整个程序,而是应该由调用者来决定是否终止程序。所以要用到异常处理。

import java.util.Scanner;
public class QuotientWithException {
    public static int quotient(int number1,int number2){
        if(number2==0)
            throw new ArithmeticException("Divisor cannot be zero");
        return number1/number2;
    }
    public static void main(String[] args) {
        Scanner input=new Scanner(System.in);
        System.out.println("Enter two integers: ");
        int number1=input.nextInt();
        int number2=input.nextInt();
        try{
            int result=quotient(number1,number2);
            System.out.println(number1+" / "+number2+" is "+result);
        }
        catch(ArithmeticException ex){
            System.out.println("Exception: an integer"+"cannot be divided by zero");
        }
        System.out.println("Execution continues...");
    }
}
抛出异常之后程序未终止

如果number2为0,方法就会通过执行下面的语句来抛出异常

throw new ArithmeticException("Divisor cannot be zero");

在这种情况下,抛出的值为new ArithmeticException("Divisor cannot be zero"),就成为一个异常(exception)。throw语句的执行称为抛出一个异常(throwing an exception)。异常就是一个从异常类创建的对象。在这种情况下,异常类就是java.lang.ArithmeticException.构造方法ArithmeticException(str)被调用来构建一个异常,其中str就是用来描述异常的信息。
当异常被抛出的时候后,正常的执行流程会被中断。异常会从一个地方传递到另一个地方。调用方法的语句包含在一个try块和一个catch块中。try块包含了正常情况之下执行的代码。异常会被catch块捕获。catch块中的语句用来处理异常,然后执行catch块之后的语句(相当于是throw语句调用了catch块,但是catch块执行完毕之后不会返回到throw语句之后,而是直接运行catch块之后的语句)。

catch块的头部:

catch (ArithmeticException ex)

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

try{
    Code to run;
    A statement or a method that may throw an exception;
    More code to run;
}
catch(type ex){
    Code to process the exception;
} 

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

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

异常是对象,而对象都采用类来定义。异常的根类是java.lang.Throwable。

Throwable类是所有异常类的根。所有的java异常类都直接或者间接地继承自Throwable类。我们可以通过继承Exception或者Exception的子类来创建自己的异常类。
异常类可以分为三种类型:系统错误、 异常、 运行时异常

  • 系统错误(system error):是由java虚拟机抛出的,用error类表示。Error类描述的是系统内部错误。
  • 异常(Exception):是用Exception类表示的,他描述的是由程序和外部环境所引起的错误,这些错误可以能被程序捕获和处理。
  • 运行时错误(runtime exception)是用RuntimeException类表示的,它描述的是程序设计错误。
    RuntimeException、Error以及它们的子类都称为免检异常(unchecked exception),而其他所有异常都称为必检异常(checked exception)因为免检异常可能出现在程序的任意一个地方,为了避免过多地使用try-catch块,所以免检异常不作强制要求。

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

声明异常:在Java中,当前执行的语句必属于某个方法。Java解释器调用main方法开始执行一个程序。每个方法都必须声明它可能抛出的必检异常类型。

public void myMethod() throws IOException

抛出异常:检测到错误的程序可以创建一个合适的异常类型的实例并抛出它,这就成为抛出一个异常。

IllegalArgumentException ex=new IllegalArgumentException("Wrong Argument");
throw ex;

或者用下面这种方法

throw new IllegalArgumentException("Wrong Argument");

每个异常类至少有两个构造方法:一个无参构造方法和一个带可描述这个异常的String参数的构造函数。这个String参数称为异常消息,可以用getMessage();获取。
声明异常的关键字是throws,抛出异常的关键字是throw。
捕获异常:当抛出一个异常时,可以在try-catch块中捕获和处理它。

try{
    statements;
}
catch (Exception ex){
    handler for exception;
}

如果在执行try块中代码的过程中没有出现异常,则会跳过catch语句。如果try块中的某条语句抛出一个异常,java会跳过try块中剩下的语句。
从一个通用的父类可以派生出各种异常类。如果一个catch块可以捕获一个父类的异常对象,它就能捕获那个父类的所有子类的异常对象。而且如果父类的catch块出现在子类的catch块漆面,会导致编译错误。
Java要求程序员必须处理必检异常。如果方法声明了一个必检异常,就必须在try-catch块中调用它,或在调用方法中声明要抛出异常。
要是需要一个catch块捕获多个异常可以使用"|"来将每个异常类型隔开。
从异常中获取信息


public class TestException {
    public static void main(String[] args) {
        try{
            System.out.println(sum(new int[]{1,2,3,4,5}));
        }
            catch (Exception ex){
                ex.printStackTrace();
                System.out.println("\n"+ex.getMessage());
                System.out.println("\n"+ex.toString());
                
                System.out.println("\nTrace Info Obtained from getStackTrace");
                StackTraceElement[] traceElements=ex.getStackTrace();
                for(int i=0;i<traceElements.length;i++){
                    System.out.println("method"+traceElements[i].getMethodName());
                    System.out.println("("+traceElements[i].getClassName()+":");
                    System.out.println(traceElements[i].getLineNumber()+")");
                }
            }
        }
    private static int sum(int [] list){
        int result=0;
        for(int i=0;i<=list.length;i++)
            result+=list[i];
            return result;
    }
}

在代码中使用了printStackTrace();、getMessage();、toString();三种方法来显示栈跟踪、异常信息、异常对象和信息。

运行结果:

printStackTrace();显示栈跟踪
getMessage();显示异常信息
toString();显示异常对象和信息
getStackTrace();显示调用的方法

finally子句

有时候,不论异常是否会出现或者被捕获,都希望执行某一些代码,这个时候可以使用finally子句来达到这个目的。

try{
    statements;
}
catch(TheException ex){
    handling ex;
}
finally{
    finalStatements;
}

注意:使用finally子句时可以省略掉catch块。

读书笔记
Web note ad 1