异常处理使得程序可以处理非预期的情景,并且能够继续正常的操作
在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(但是在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");
}
}
为了介绍异常处理,重新再写一个方法来计算商。
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();三种方法来显示栈跟踪、异常信息、异常对象和信息。
运行结果:
finally子句
有时候,不论异常是否会出现或者被捕获,都希望执行某一些代码,这个时候可以使用finally子句来达到这个目的。
try{
statements;
}
catch(TheException ex){
handling ex;
}
finally{
finalStatements;
}
注意:使用finally子句时可以省略掉catch块。