Java-异常处理

1、异常处理的目的

异常是程序之中导致程序中断的一种指令流,异常一旦出现并且没有进行合理处理的话,那么程序就将中断执行。
下面,通过两个程序来进行异常产生问题的对比。
(1)不产生异常的程序:

public class Test {
    public static void main(String args[]) {
        System.out.println("1、除法计算开始。");
        int result = 10 / 2;
        System.out.println("2、除法计算结果:" + result);
        System.out.println("3、除法计算结束。");
    }
}

运行结果:

除法计算开始。
除法计算结果:5
除法计算结束。

(2)产生异常的程序

public class Test {
    public static void main(String args[]) {
        System.out.println("1、除法计算开始。");
        int result = 10 / 0; // 会出现错误
        System.out.println("2、除法计算结果:" + result);
        System.out.println("3、除法计算结束。");
    }
}

运行结果:

1、除法计算开始。Exception in thread "main" 
java.lang.ArithmeticException: / by zero
    at Test.main(Test.java:4)

一旦产生异常,产生异常的语句以及之后的语句将不再执行,默认情况下是进行异常信息的输出,而后自动结束程序的执行。
现在,我们要做的是:即使程序出现了异常,也要让程序正确的执行完毕。


2、异常的分类处理

如果希望程序出现异常之后程序依然可以正常的完成的话,那么就可以使用如下的格式进行异常的处理:

try {
         可能出现异常的语句 ;
} [ catch (异常类型 异常对象) {
         处理异常 ;
} catch (异常类型 异常对象) {
         处理异常 ;
} ... ] [finally {
         不管是否出现异常,都执行此代码 ;
}]

现在,使用以上的操作处理异常处理前面除法于是出现的异常:

public class Test {
    public static void main(String args[]) {
        System.out.println("1、除法计算开始。");
        try {
            int result = 10 / 0; // 异常
            System.out.println("2、除法计算结果:" + result); // 之前语句有异常,此语句不再执行
        } catch (ArithmeticException e) {
            System.out.println(e); // 异常处理:输出错误信息,java.lang.ArithmeticException:/ by zero
        }
        System.out.println("3、除法计算结束。");
    }
}

运行结果:

1、除法计算开始。
java.lang.ArithmeticException: / by zero
3、除法计算结束。

可以发现,加入了异常处理之后,程序中即使有了异常,程序也可以正常的执行完毕,但是异常处理时的错误输出信息和之前相比,出错的信息不明确了,那么为了让错误的信息更加的完整,一般都会调用printStackTrace()方法进行异常信息的打印,这个方法打印的异常信息是最完整的:

public class Test {
    public static void main(String args[]) {
        System.out.println("1、除法计算开始。");
        try {
            int result = 10 / 0; // 异常
            System.out.println("2、除法计算结果:" + result); // 之前语句有异常,此语句不再执行
        } catch (ArithmeticException e) {
            e.printStackTrace(); // 异常处理:输出错误信息
        }
        System.out.println("3、除法计算结束。");
    }
}

运行结果:

1、除法计算开始。
java.lang.ArithmeticException: / by zero
    at Test.main(Test.java:5)
3、除法计算结束。

此时发现,打印的信息是很完整的。

除了try…catch格式处理异常外,还可以使用try…catch..finally:

public class Test {
    public static void main(String args[]) {
        System.out.println("1、除法计算开始。");
        try {
            int result = 10 / 1;
            System.out.println("2、除法计算结果:" + result);
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } finally {
            System.out.println("不管是否出现异常都执行");
        }
        System.out.println("3、除法计算结束。");
    }
}

运行结果:

1、除法计算开始。
2、除法计算结果:10
不管是否出现异常都执行
3、除法计算结束。

但是,对于之前的程序又有了问题:现在执行数学计算的两个参数,都是由程序默认提供,那么如果说现在两个计算的参数通过初始化参数传递呢?

public class Test {
    public static void main(String args[]) {
        System.out.println("1、除法计算开始。");
        try {
            int x = Integer.parseInt(args[0]); // 接收参数
            int y = Integer.parseInt(args[1]); // 接收参数
            int result = x / y;
            System.out.println("2、除法计算结果:" + result);
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } finally {
            System.out.println("不管是否出现异常都执行");
        }
        System.out.println("3、除法计算结束。");
    }
}

这个时候,数据由外部传送,那么就有可能出现以下几类问题:
(1)执行时不输入参数(java TestDemo),ArrayIndexOutOfBoundsException,未处理;
(2)输入的参数不是数字(java TestDemo a b),NumberFormatException,未处理;
(3)被除数为0(java TestDemo 10 0),ArithmeticException,已处理。
可以发现,以上的程序实际上是存在三种异常,而程序之中只能够处理一种,而对于不能处理的异常,发现程序依然会直接中断执行。
加入多个catch:

public class Test {
    public static void main(String args[]) {
        System.out.println("1、除法计算开始。");
        try {
            int x = Integer.parseInt(args[0]);
            int y = Integer.parseInt(args[1]);
            int result = x / y;
            System.out.println("2、除法计算结果:" + result);
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } finally {
            System.out.println("不管是否出现异常都执行");
        }
        System.out.println("3、除法计算结束。");
    }
}

现在,程序比之前更健壮了。


3、异常的简化处理

以上已经完成了异常的基本处理,但是所有的异常都像之前那样一条条的判断似乎是一件不可能完成的任务,因为日后肯定会接触到一些不常见的异常信息,那么下面就必须首先研究异常的流程和结构。

先查看两个异常类的继承结构:
(1)ArithmeticException:

java.lang.Object
    |- java.lang.Throwable
         |- java.lang.Exception
              |- java.lang.RuntimeException
                 |- java.lang.ArithmeticException

(2)ArrayIndexOutOfBoundsException:

java.lang.Object
   |- java.lang.Throwable
      |- java.lang.Exception
         |- java.lang.RuntimeException
             |- java.lang.IndexOutOfBoundsException
                |-java.lang.ArrayIndexOutOfBoundsException

可以发现,所有的异常类型最高的继承类是Throwable,Throwable下有两个子类:
(1)Error:指的是JVM错误,这个时候的程序并没有执行,无法处理;
(2)Exception:指的是程序之中出现的错误信息,可以进行异常处理。

通过继承关系可以发现,在进行日后异常处理的时候是以Exception为主,并且可以形成以下的异常处理流程:
这里写图片描述
(1)如果程序中产生了异常,那么JVM根据异常的类型,实例化一个指定异常类的对象;

(2)如果这时程序中没有任何的异常处理操作,则这个异常类的实例化对象将交给JVM进行处理,而JVM的默认处理方式就是进行异常信息的输出,而后中断程序执行;

(3)如果程序中存在了异常处理,则会由try语句捕获产生的异常类对象;

(4)与try之后的每一个catch进行匹配,如果匹配成功,则使用指定的catch进行处理,如果没有匹配成功,则向后面的catch继续匹配,如果没有任何的catch匹配成功,则这个时候将交给JVM执行默认处理;

(5)不管是否有异常都会执行finally程序,如果此时没有异常,执行完finally,则会继续执行程序之中的其他代码,如果此时有异常没有能够处理(没有一个catch可以满足),那么也会执行finally,但是执行完finally之后,将默认交给JVM进行异常的信息输出,并且程序中断。

通过以上的分析可以发现,实际上catch捕获异常类型的操作,就和方法接收参数是一样的,那么按照之前所学习过的对象多态性来讲,所有的异常类都是Exception的子类,那么这个时候,实际上所有的异常都可以使用Exception进行接收:

public class Test {
    public static void main(String args[]) {
        System.out.println("1、除法计算开始。");
        try {
            int x = Integer.parseInt(args[0]);
            int y = Integer.parseInt(args[1]);
            int result = x / y;
            System.out.println("2、除法计算结果:" + result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("不管是否出现异常都执行");
        }
        System.out.println("3、除法计算结束。");
    }
}

这时应该可以感受到异常处理所带来的好处了。但是这种操作也存在一种问题:如果在一些异常处理要求严格的项目之中,异常必须分别处理,如果现在异常的处理要求不是很严格,直接编写Exception就足够了。


4、throws关键字

thrwos关键字主要是在方法定义上使用的,表示的是此方法之中不进行异常的处理,而交给被调用处处理。

例如:

class MyMath {
    public int div(int x, int y) throws Exception {
        return x / y;
    }
}

现在的div()方法之中抛了一个异常出来,表示所有的异常交给被调用处进行处理。

class MyMath {
    public int div(int x, int y) throws Exception {
        return x / y;
    }
}

public class Test {
    public static void main(String args[]) {
        try {
            System.out.println(new MyMath().div(10, 0));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

java.lang.ArithmeticException: / by zero
    at MyMath.div(Test.java:3)
    at Test.main(Test.java:10)

注意: 在调用throws声明方法的时候,一定要使用异常处理操作进行异常的处理,这属于强制性的处理。

而主方法本身也属于方法,那么在主方法上也可以继续使用throws进行异常的抛出:

class MyMath {
    public int div(int x, int y) throws Exception {
        return x / y;
    }
}

public class Test {
    public static void main(String args[]) throws Exception {
        System.out.println(new MyMath().div(10, 0));
    }
}

运行结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at MyMath.div(Test.java:3)
    at Test.main(Test.java:9)

这时主方法将异常继续向上抛,交给JVM进行异常的处理,也就是采用默认的方式,输出异常信息,而后结束程序执行。
注意:在实际开发中,主方法不要加throws,因为程序如果有异常,我们也希望可以正常的结束。


5、throw关键字

之前所有异常类对象都是由JVM自动进行实例化操作的,用户也可以自己手工的抛出一个异常类实例化对象,就通过throw完成了。

public class Test {
    public static void main(String args[]) {
        try {
            throw new Exception("自定义的异常");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

java.lang.Exception: 自定义的异常
    at Test.main(Test.java:4)

小结:throw和throws的区别?

(1)throw:在方法体内使用,表示人为的抛出一个异常类对象(这个对象可可以是自己实例化的,也可以是已存在的);
(2)throws:在方法的声明上使用,表示在此方法中不进行异常的处理,而交给被调用处处理。


6、RuntimeException类

先来观察一段代码

public class Test {
    public static void main(String args[]) {
        String str = "123";
        int num = Integer.parseInt(str);
        System.out.println(num * num);
    }
}

运行结果:

15129

这个程序就是将一个字符串变为了基本数据类型,而后执行乘法操作,但是下面来看一下parseInt()方法定义:

public static int parseInt(String s) throws NumberFormatException

发现这个方法上抛出了一个NumberFormatException的异常,按照之前所讲,如果存在了throws,则必须使用try…catch进行处理,可是现在却没有强制要求处理,这是为什么呢?

来观察一下NumberFormatException的继承结构:

java.lang.Object
   |- java.lang.Throwable
      |- java.lang.Exception
          |- java.lang.RuntimeException
              |- java.lang.IllegalArgumentException
                  |- java.lang.NumberFormatException

发现NumberFormatException属于RuntimeException的子类,而在Java之中明确规定:

对于RuntimeException的异常类型,在编译的时候不会强制性的要求用户处理,用户可以根据需要有选择性的来进行处理,在开发之中,如果没有处理,那么出现异常之后将交给JVM默认进行处理。也就是说,RuntimeException的子异常类,可以由用户根据需要有选择性的来进行处理。

小结:RuntimeException和Exception的区别?请列举出几个常见的RuntimeException.

(1)RuntimeException是Exception的子类;
(2)Exception定义了必须处理的异常,而RuntimeException定义的异常可以选择性的进行处理。

常见的RuntimeException:
NumberFormatException、ClassCastException、NullPointerException、ArithmeticException、ArrayIndexOutOfBoundsException。


7、自定义异常

在Java之中本身已经提供了大量的异常类型,但是在开发之中,这些异常类型还不能满足于开发的需要。所以在一些系统架构之中往往会提供一些新的异常类型,来表示一些特殊的错误,而这种操作就称为自定义异常类,

而要想实现这种自定义异常类,那么可以让一个类继承Exception或RuntimeException。

class MyException extends Exception { // 自定义异常类
    public MyException(String msg) {
        super(msg);
    }
}

public class Test {
    public static void main(String args[]) throws Exception {
        throw new MyException("自己的异常类");
    }
}

运行结果:

Exception in thread "main" MyException: 自己的异常类
    at Test.main(Test.java:9)

猜你喜欢

转载自blog.csdn.net/weixin_39190897/article/details/81871826