- 异常的定义
在java中,我们程序除了正常运行外还有一种情况,那就是异常。
Java语言将程序运行过程中所发生的不正常严重错误称为异常,对异常的处理称为异常处理。
它会中断正在运行的程序,正因为如此异常处理是程序设计中一个非常重要的方面,也是程序设计的一大难点。
众所周知,0不能做分母,而这段代码中的“1/0”便犯了这个错误,我们看到执行后输出了123,但是错误代码行以后的不再输出,所以异常的结果是中断正在运行中的程序。
- 异常的结构
异常的分类图
throwable有两个直接子类,Error类和Exception类。
Error : 指合理的应用程序在执行过程中发生的严重问题。当程序发生这种严重错误时,通常的做法是通知用户并中止程序的执行。
而这实际上是编译器的问题,所以我们不进行过多探究。
- Exception:异常可分为运行时异常(RuntimeException)和检查时异常(CheckedException)两种:
①1.RuntimeException:运行时异常,即程序运行时抛出的异常。这种异常在写代码时不进行处理,Java源文件也能编译通过。 RuntimeException异常类及其下面的子类均为运行时异常。
public class Unusual {
public static void main(String[] args) {
System.out.println(123);
System.out.println(1/0); //运行时异常,程序在编译时并未报错
System.out.println(456);
}
}
这段代码在java中我们编写时并不会报错,并且java源文件也能通过javac命令生成class文件,但一旦运行,便会报错。
由于这个特点,这种异常我们可以不显式处理。
②2.CheckedException:检查时异常,又称为非运行时异常,这样的异常必须在编程时进行处理,否则就会编译不通过。Exception异常类及其子类(除去RuntimeException异常类及其子类)都是检查时异常。
比如这段代码,创建对象时的类名并不存在,编译时就没有通过,程序报错,针对这种异常,我们必须做显式处理,使程序在编译时就能够通过。
- 异常的处理
•Java中对异常的处理有如下两种方式:
①通过try、catch和finally关键字捕获异常;
②通过throw或throws关键字抛出异常;
这篇博客我们注重讲捕获异常
- 捕获异常的语法结构
public class Unusual {
public static void main(String[] args) {
try{
System.out.println(1/0); //可能抛出异常的语句块
}
catch(ArithmeticException e){ //异常类型,这里是ArithmeticException
e.printStackTrace(); //当捕获到所预测异常时执行的语句块
}
finally{
System.out.println("程序执行完毕"); //无论是否发生异常都会执行的代码
}
}
}
因为1/0确实是数学类型的异常,所以程序执行的结果为
并且由于try catch finally语句捕捉了异常,那么编译器会认为这个异常得到了解决,因此之后的代码正常执行
- 注意事项
①一个try必须搭配catch或finally中的其中一个。
因为try的作用是提出可能抛出异常的语句块,而catch与finally决定了之后的操作,如果不搭配其中一个,这个try就失去了意义
仅含try,编译器警告我们插入finally完善结构
②一个try下可以有多个catch,但catch要按其捕获的异常类型由子类到父类的顺序编写
public class Unusual{
public static void main(String[] args){
try{
String name=null;
System.out.println(name.length());//可能出现异常的语句
}catch(NullPointerException e){ //首先catch NullPointerException异常
System.out.println("NUll"); //捕获成功后输出“NULL”
}catch(ArithmeticException e){ //再捕获ArithmeticException异常
System.out.println("0"); //捕获成功后输出“0”
}catch(Exception e){ //最后捕获Exception(一切异常)
System.out.println("other"+e); //捕获成功后输出“other”+e
}
}
}
虽然这些catch看起来是并列的,但实际上会从上往下执行,而try catch语句的执行逻辑为:
try语句块中的代码可能会引发多种类型的异常,当引发异常时,会按顺序查看每个 catch 语句,并执行第一个与异常类型匹配的catch语句,其后 catch 语句被忽略。
因此,如果我们先捕获“更大”的异常类型,那么如果语句符合这个异常类型,catch语句就会执行,其后对“更小”的异常类型的捕获就会被忽略,那么之后的语句其实已经没有了意义,并且由于这样子导致捕获不够精确,返回的方法也不一定是最适合这个异常的解决方法。
由于第一个catch已经生效,之后的catch失去作用,因此这段代码的执行结果为”NULL“,除了多写的catch做了无用功外,try中剩下的可能异常的语句没有得到捕捉,虽然程序运行没有问题,但是程序员无法判断它们到底是不是异常,可能导致之后编写代码出现问题。
③无论try中是否有异常发生,finlly中的代码总会执行,一般用于释放资源
public class Unusual {
public static void main(String[] args) {
try {
System.out.println(1/0); //0做分母,语句出现异常
}catch(Exception e) {
System.out.println("other"+e);
}finally{
System.out.println("zhixing");
}
try{
System.out.println(1/1); //实际上这个语句并没有异常
}catch(Exception e){
System.out.println("other"+e);
}finally{
System.out.println("zhixing");
}
}
}
执行结果为
我们可以看到,无论try后的语句是否异常,finally语句都执行了。
- 异常处理的弊端以及解决
在异常处理时,编译器会将异常信息打印控制台,而控制台会存入一段缓存,缓存有大小。当缓存满了,但是异常信息源源不断,则会将最先进入缓存的异常信息移除,意味着无法全面的发现程序运行期间出现的问题,为了全面收集系统运行期间出现的所有异常信息,log4j诞生了,与存入缓存不同,log4j将所有的异常信息输出到一个文件中,因为文件在硬盘上,可以认为无限大,因此异常信息可以完全得到保存。
- log4j的使用方法