异常的捕获与处理
1、认识异常
在程序开发中,程序的编译与运行是两个不同的阶段,编译主要针对的是语法检测,而在程序运行时却有可能出现各种各样的错误,导致程序中断执行,那么这些错误在Java中统一称为异常。在Java中对异常的处理提供了非常方便的操作。那么何为异常呢?
异常是指在程序执行时由于程序处理逻辑上的错误导致程序中断的一种指令流。
范例:初始异常
package edu.blog.test19.exception01;
public class ExceptionDemo01 {
public static void main(String[] args) {
System.out.println("程序开始");
System.out.println("10/2:" + (10 / 2));
System.out.println("程序结束");
System.out.println("===========================");
System.out.println("程序开始");
System.out.println("10/0:" + (10 / 0));
System.out.println("程序结束");
}
}
/*
执行结果:
程序开始
10/2:5
程序结束
===========================
程序开始
Exception in thread "main" java.lang.ArithmeticException: / by zero
at edu.blog.test19.exception01.ExceptionDemo01.main(ExceptionDemo01.java:11)
*/
程序上半部分没有异常产生,程序会按照逻辑顺序执行完毕。但是,程序后半部分产生了异常(ArithmeticException异常),由于程序没有对异常进行任何的处理,所以默认情况下会进行异常信息的打印,同时终止执行对异常产生之后的代码(也就是说第二个“程序结束”不会打印)。
2、异常处理
由上我们发现,当程序产生异常后,若没有对其进行处理,程序则会出现中断执行的情况。
为了使程序在出现异常后,剩余代码依旧能正常执行,Java针对异常的处理提供有3个核心的关键词:try、catch、finally,可以组成以下的异常处理格式。
try{
//有可能出现异常的语句
} catch {
//异常处理1
} catch {
//异常处理2
} ..... {
//异常处理n
} finally {
//异常的统一出口,无论是否产生异常,finally语句必执行
}
try语句中捕获可能出现的异常。若产生异常,程序自动跳转到catch语句中寻找到匹配的异常类进行相应的处理。最后不管程序有没有产生异常,肯定会执行到finally语句。
注意:finally()块是可以省略的。
若省略了finally()块,则程序在执行完catch()块结束后,程序将继续向下执行。
Tips:catch()块和finally()块都是可选的,但是不能同时省略。
所以异常的处理组合有以下的结构形式:try…catch、try…catch…finally、try…finally。
范例:异常处理
package edu.blog.test19.exception01;
public class ExceptionDemo02 {
public static void main(String[] args) {
System.out.println("程序开始");
try {
int result = 10 / 0;
System.out.println("数字计算:10 / 0 = " + result);
System.out.println("【try】---这里会执行吗?");
} catch (ArithmeticException e) {
System.out.println("【catch1】---这里会执行吗?");
e.printStackTrace();
System.out.println("【catch2】---这里会执行吗?");
} finally {
System.out.println("【finally】---这里会执行吗?");
}
System.out.println("程序结束");
}
}
/*
执行结果:
程序开始
【catch1】---这里会执行吗?
【catch2】---这里会执行吗?
【finally】---这里会执行吗?
程序结束
java.lang.ArithmeticException: / by zero
at edu.blog.test19.exception01.ExceptionDemo02.main(ExceptionDemo02.java:7)
*/
本程序使用了try…catch…finally的异常处理格式,当程序中产生异常后,异常会被try语句捕获,而后交给catch处理,通过异常类提供的printStackTrace()方法打印出异常信息,明确告诉用户异常在哪,为何有异常,最后将finally代码块作为异常处理代码的出口。
由上程序范例我们可以发现:
- try语句块中,当出现异常后,异常后面的代码语句并不会执行(如,System.out.println("【try】—这里会执行吗?");)。
- catch语句块中,只要有匹配的异常处理,则里面所有的语句都会执行的。
- finally语句块中的语句都会执行,而且整个try…catch…finally结构后面的语句也都会执行。
Tips:finally语句的作用多余吗?
上述列子我们发现不管程序是否出现异常,异常处理后面的代码语句(如,System.out.println(“程序结束”);)都会执行,那么finally语句的作用是不是有点多余?
答:当然不会啦,因为两者的执行机制本就不相同。因为此程序只是针对ArithmeticException异常,并不能对其他异常进行处理。倘若产生了其他的异常,程序依旧会中断执行,进而后面的代码也就不能正常执行,但是finally语句块中的语句依然会执行。
我们对上面范例进行小修改,将ArithmeticException改为NullPointerException后再次测试。
package edu.blog.test19.exception01;
public class ExceptionDemo03 {
public static void main(String[] args) {
System.out.println("程序开始");
try {
int result = 10 / 0;
System.out.println("数字计算:10 / 0 = " + result);
System.out.println("【try】---这里会执行吗?");
} catch (NullPointerException e) {
System.out.println("【catch1】---这里会执行吗?");
e.printStackTrace();
System.out.println("【catch2】---这里会执行吗?");
} finally {
System.out.println("【finally】---这里会执行吗?");
}
System.out.println("程序结束");
}
}
/*
执行结果:
程序开始
【finally】---这里会执行吗?
Exception in thread "main" java.lang.ArithmeticException: / by zero
at edu.blog.test19.exception01.ExceptionDemo03.main(ExceptionDemo03.java:7)
*/
通过两个范例的对比,是不是更能体会到两者的不同。
3、异常的处理流程
Java中的异常处理流程下图1所示。
- Java中只有在运行中产生的异常是可以处理的,当出现异常时,JVM会判断此异常的类型,并自动实例化相应的异常类对象;
- 若程序中没有提供异常的处理方式,则会采用JVM的默认处理方式(打印异常信息),之后直接退出当前程序(即程序中断执行);
- try块语句会捕获产生异常的异常类的实例化对象,若是程序中存在异常处理,则将其与catch块中的异常类型进行对比;
- 若类型相同,则使用此catch块语句对其进行异常处理;
- 若类型不相同,则继续匹配后续的catch类型,若是没有catch块匹配成功,则表示该异常无法进行处理。
- 不管程序有没有产生异常(或者说产生的异常是否被处理)都要执行finally语句;
- 执行完finally块后,程序会再次判断当前的异常是否已经处理过了;
- 若处理过了,则继续执行后面的其他代码;
- 若没有处理过,则交由JVM进行默认处理。
Tips:一个小细节——处理多个异常时,捕获范围小的异常要放在捕获异常范围大的异常之前处理。
4、异常类
Throwable这个Java类被用来表示任何可以作为异常被抛出的类。
Throwable对象可分为两种类型(指从 Throwable继承而得到的类型):
- Error用来表示编译时和系统错误(除特殊情况外,一般不用你关心);
- Exception是可以被抛出的基本类型,在Java类库、用户方法以及运行时故障中都可能抛出 Exception型异常;
- RuntimeException:在编译期是不检查的,出现问题后,需要需要我们回来修改代码;
- 非RuntimeException:在编译期就必须处理的,否则程序不能通过编译。
所以我们一般关心的基类型通常是Exception。
5、throws和throw关键字
在Java中,抛出异常有三种形式,throws、throw以及系统自动抛异常。
- throws:方法可能抛出异常的声明
定义一个方法的时候可以使用throws关键字声明。使用throws关键字声明的方法表示此方法不处理异常,而交给方法调用处进行处理。
package edu.blog.test19.exception02;
public class ThrowsTestDemo {
public static void main(String[] args) {
try {
Test.fun();
} catch (ArithmeticException e) {
e.printStackTrace();
}
}
}
class Test {
public static void fun() throws ArithmeticException {
System.out.println(5 / 0);
}
}
/*
java.lang.ArithmeticException: / by zero
at edu.blog.test19.exception02.Test.fun(ThrowsTestDemo.java:15)
at edu.blog.test19.exception02.ThrowsTestDemo.main(ThrowsTestDemo.java:6)
*/
本程序在主方法中调用了Test.fun()方法,由于此方法上使用了throws抛出了异常,这样在调用此方法时就必须明确使用异常处理语句处理该语句可能发生的异常。
另外主方法本身也是方法,所以如果在主方法上使用了throws,则表示在主方法中可以不用强制进行异常处理。如果出现了异常,将交给JVM进行默认处理,则此时会导致程序中断执行。
package edu.blog.test19.exception02;
public class ThrowsTestDemo {
public static void main(String[] args) throws ArithmeticException {
Test.fun();
System.out.println("我会执行吗??");
}
}
class Test {
public static void fun() throws ArithmeticException {
System.out.println(5 / 0);
}
}
/*
执行结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at edu.blog.test19.exception02.Test.fun(ThrowsTestDemo.java:12)
at edu.blog.test19.exception02.ThrowsTestDemo.main(ThrowsTestDemo.java:5)
*/
- throw:语句抛出一个异常
在默认情况下,所有的异常类的实例化对象都会由JVM默认实例化并且自动抛出。但是Java提供了一个throw关键字,作用是抛出一个异常,抛出的时候是抛出的是一个异常类的实例化对象。在异常处理中,try语句要捕获的是一个异常对象,那么此异常对象也可以自己抛出。
package edu.blog.test19.exception02;
public class ThrowTestDemo {
public static void main(String[] args) {
try {
throw new Exception("我就是自己抛异常,就是玩~");
} catch (Exception e) {
System.out.println("就是玩~");
e.printStackTrace();
}
}
}
/*
执行结果:
就是玩~
java.lang.Exception: 我就是自己抛异常,就是玩~
at edu.blog.test19.exception02.ThrowTestDemo.main(ThrowTestDemo.java:6)
*/
小结:throw与throws的区别。
- throw:是在代码块中使用的,主要是手动进行异常对象的抛出;
- throws:是在方法中定义使用的,表示将此方法中可能产生的异常明确告诉调用处,由调用处进行处理。
6、自定义异常类
Java本身已经提供了大量的异常类,但是这些异常类也许并不够用,这时你就需要自定义异常类了。自定义异常类只需要继承Exception(强制性处理异常)或者RuntimeException(选择性异常处理)父类即可。
范例:实现自定义异常
package edu.blog.test19.exception03;
public class ScoreException extends Exception{
public ScoreException(){}
public ScoreException(String msg){
super(msg);
}
}
package edu.blog.test19.exception03;
public class Teacher {
public void checkSore(int score) throws ScoreException {
if (score < 0 || score > 100) {
throw new ScoreException("你给的分数有误,分数应该在0~100之间");
} else {
System.out.println("成绩正常");
}
}
}
package edu.blog.test19.exception03;
public class CustomExceptionTestDemo {
public static void main(String[] args) {
int score = 120;
Teacher teacher = new Teacher();
try {
teacher.checkSore(score);
} catch (ScoreException e) {
e.printStackTrace();
}
}
}
/*
执行结果:
edu.blog.test19.exception03.ScoreException: 你给的分数有误,分数应该在0~100之间
at edu.blog.test19.exception03.Teacher.checkSore(Teacher.java:6)
at edu.blog.test19.exception03.CustomExceptionTestDemo.main(CustomExceptionTestDemo.java:8)
*/
7、assert关键字
断言(assert)是一种常见的软件功能,assert关键字是在JDK 1.4的时候引入的,其主要的功能是进行断言。断言是执行到某行之后,其结果一定是预期的结果。
范例:断言的使用
package edu.blog.test19.exception04;
public class AssertTestDemo {
public static void main(String[] args) {
int x = 10;
//经过某些操作使x的值发生改变
assert x == 100 : "x的值并不是100";
System.out.println(x);
}
}
/*
Exception in thread "main" java.lang.AssertionError: x的值并不是100
at edu.blog.test19.exception04.AssertTestDemo.main(AssertTestDemo.java:7)
*/
注意:Java默认情况下是不开启断言的。
//增加“-ea”参数即可
java -ea edu.blog.test19.exception04.AssertTestDemo
Tips:IDEA开发工具专门增加参数呢?
- 第一步
-
第二步
8、补充:try语句块中有return语句
之前我偶尔看到一道题,此题如下:
package edu.blog.test19.exception05;
public class TestDemo {
public static void main(String[] args) {
System.out.println("return value of getValue():" + getValue());
}
public static int getValue() {
try {
return 0;
} finally {
return 1;
}
}
}
/*
return value of getValue():1
*/
再来举个例子:
package edu.blog.test19.exception05;
public class TestDemo {
public static void main(String[] args) {
System.out.println("return value of getValue():" + getValue());
}
public static int getValue() {
int i = 0;
try {
return ++i;
} finally {
System.out.println("我还是会执行的");
}
}
}
/*
执行结果:
我还是会执行的
return value of getValue():1
*/
小结:
- 如果try语句块中有return语句(finally语句块中没有),返回的是try语句块中的变量值。执行过程大致如下:
- 若try语句块中有返回值,先把返回值保存到局部变量中(可以理解为打了断点进行标记);
- 执行jsr指令跳到finally语句中执行;
- 执行完finally语句块后,返回之前保存在局部变量中的值(可以理解为回到标记的断点处)。
- 若try语句块、finally语句块中都有return语句,则忽略try的return,而使用finally的return。
- 从1、2我们也看出,finally语句块中的语句是一定要执行的。
注:此文章为个人学习笔记,如有错误,敬请指正。