Java异常
1.什么是异常?
异常很好理解,就是程序运行中出现的不正常的情况。
我们知道除数是不能为0的,现在来模拟一个除数为0的异常:
package com.dh.exception;
public class Exception01 {
public static void main(String[] args) {
int i = 1/0;
System.out.println(i);
}
}
结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.dh.exception.Exception01.main(Exception01.java:7)
Process finished with exit code 1
上述代码在编译时是不会报错的,但是在运行时就会报一个java.lang.ArithmeticException: / by zero,并且可以看到,程序并没有输出i,所以发生异常的语句之后的代码都不会被执行。
Java提供了异常处理机制,会将异常信息输出到控制台,方便写代码的人去查找错误原因,更高效的调试处理,不仅输出了是什么异常,并且还告诉了你导致异常出现的代码是在第几行,非常方便程序员去找错误,使程序更安全、更健壮。
试想一下,要是没有这些输出信息。。。
此刻可以大喊一声,Java牛逼!
2.异常的分类
我们尝试着能不能在api文档中查一下ArithmeticException是个什么东西:
通过查api文档,我们可以看到ArithmeticException是个类,所以,Java语言中,异常是以异常类的形式存在,在发生异常的时候,虚拟机会new一个异常对象,并且将改异常对象的信息打印到控制台上。
我们也可以看到,ArithmeticException的鼻祖的确是Object,而Throwable直接继承了Object,ArithmeticException间接的继承了Throwable。我们再去api文档中查看一下Throwable是什么?
文档解释为:Throwable的直接子类有Error和Exception。(因为文档翻译的不太好,故不在此放文档解释的截图)
刚刚的是个异常信息,并且在Exception下还有个子类为RuntimeException,但是错误信息我们之前也见过,在递归调用的时候,如果自己不停的调用自己,就会出现StackOverFlowError。
所以,Java的异常大概可以分为如下:
Throwable就是可抛出的意思,Error和Exception直接继承Throwable了,不难得出,Error和Exception都可抛出。
但是,错误是不能处理的,所有的Error,只要发生了,就会终止程序的运行,退出JVM。
异常是可处理的。异常又分为运行时异常和编译时异常:Exception中所有直接子类都为编译时异常,RuntimeException下的所有子类都为运行时异常。
-
编译时异常:也称为受控性异常。因为在编写代码时,就会直接红色波浪线报错,这种异常是有程序员造成的,发生的概率一般比较高(这...?),且必须进行预处理,否则会一直红色波浪线,虽然你也可以强制运行,但纯属浪费时间而已并没有什么意义。
-
运行时异常:也称为非受控时异常。在编写代码的时候,没有红色波浪线报错提示,但是一运行就会报异常,如文中开头就提到的除数为0,这是由虚拟机造成的,所以运行时异常一般发生的概率比较低,可以预处理也可以不必预处理。预处理的话像上述例子,就是先判断除数是否为0,不为0时再进行除法运算,否则给出除数不能为0的提示。
注意:
运行时异常和编译时异常都是在程序运行过程中发生的。道理很简单,只有在程序运行的时候才可以new异常类的对象,不要因为编译时异常这个名字就觉得编译时异常是在编译时发生的。
3.异常处理的方式
如果你在工作的时候,不小心出现了一个失误,比如说让公司损失了1000块钱,那么现在你有两种解决方式,第一种是你自己垫钱补上,第二种是上报你的领导。
异常就相当于上述的失误,而异常处理的方式,原理也是类似的,一般有两种:
-
使用try...catch,在catch中抓住异常。
-
在方法声明的最后,使用throws+异常类,抛出该方法的调用者,调用者也可以使用这两种方式处理,直到最后的main()。
但是这种方式不一定能解决掉异常,如果你一个不小心,让公司损失了1000w,那么此时上报你的直接领导,解决不了,再上报给领导的直接领导...一直到公司的boss,他也解决不了,那就只能大家一起完蛋了。
我们知道所有的方法都是在main中直接或间接调用的,所以最后一个方法的调用一定是main,所以如果main也处理不了,就会抛给JVM,JVM就会结束程序的运行,退出JVM了。
(1)运行时异常的处理
1.try...catch:
try中写可能会出现异常的代码
catch紧跟的的()中接收异常对象,该异常对象必须是实际发生异常的对象或者父类,不能是无关的异常对象和子类对象接收,写代码出现异常时的操作,所以catch只有出现异常时才执行。
catch可多个,大的异常要放在下面,(不然大的放上面,下面那个catch就没有意义了),否则会报错
或者单个,在()中使用|,如(AclNotFoundException | ActivationException e)
package com.dh.exception;
public class Exception01 {
public static void main(String[] args) {
try {
int i = 1/0;
System.out.println(i);
}catch (ArithmeticException e){ //接收程序运行过程中new的异常对象
System.out.println("除数不能为0");
}
}
}
此时的结果为:程序输出
除数不能为0
将上述式子改为1时,此时没有发生异常,程序就不会执行catch子句,就不会输出除数不能为0这句话 。
2.throws:
package com.dh.exception;
public class Exception02 {
//在方法的声明的最后加上throws 异常类的名称
public static void main(String[] args) throws ArithmeticException{
int i = 1/0;
System.out.println(i);
}
}
结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.dh.exception.Exception02.main(Exception02.java:7)
Process finished with exit code 1
可以看到此时仍然抛出了异常,这是因为:在main方法中发生了异常,却没有处理异常,此时将异常对象向上抛抛给了JVM,JVM就结束程序的运行,输出异常信息,退出JVM。
如果程序没有发生错误,就没有异常对象,main也不需要向上抛,程序就会继续运行,正常结束。
(2)编译时异常处理
我们先模拟一个编译时异常:
package com.dh.exception;
public class Exception03 {
//查api文档,找一个Exception的直接子类:CloneNotSupportedException
public static void show() throws CloneNotSupportedException{
System.out.println("show");
}
public static void main(String[] args) {
show();//红色波浪线报错
}
}
此时程序会报错,这是因为show方法中throws的CloneNotSupportedException为编译时异常,而它的调用者没有预处理这个异常,所以会报错,此时提示也给出了两种解决方法。
1.try...catch:
2.throws:
这两种方式程序都不会报错了,但是throws是不能解决问题的,我们还是得使用try...catch来解决。
4.异常对象常用方法
自动生成的try...catch中,有一句e.printStackTrace();
printStackTrace()是异常对象的方法,打印异常输出的堆栈信息;(最常用!也推荐使用)
常用的方法还有:getMessage(),获取对象的描述信息。
5.finally子句
finally{}可直接和try使用,也可以和try...catch一起使用。
finally子句中的代码,无论是否发生异常,都一定会执行!!(充钱了吧?)
(除非来了,除非System.exit(0)),这是会退出JVM,它就没机会执行了!
finally语句常用于关闭一些东西,因为如文件流、数据库连接等不关闭会很消耗资源,所以在使用完之后必须关闭。
例:
FileInputStream用于读取诸如图像数据的原始字节流,我们点进去源码可以看到:
public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
}
其构造方法抛出了FileNotFoundException,一直点击异常,可以发现其间接的继承了Exception子类,所以是编译时异常,必须要预先处理,否则会报错,在main中我们就最好try...catch处理了。
package com.dh.exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Exception04 {
public static void main(String[] args) {
FileInputStream file = null;
try {
//输入错误路径引发异常
file = new FileInputStream("aaa");
} catch (IOException e) {
e.printStackTrace();
}finally {
System.out.println("我执行啦!");
}
}
}
结果:
可以看到,输入程序出现了异常,但是finally中的语句还是执行了。
也可以去掉catch,但是如果去掉了之后,就没有异常信息输出了,程序出现了异常你都不知道,还会傻乎乎的以为一切ok,所以最好不要去掉catch,当出现异常时,它对我们特别有帮助!
相关面试题(坑!):
package com.dh.exception;
public class Exception05 {
public static void main(String[] args) {
try{
System.out.println("try......");
return;
}finally {
System.out.println("catch......");
}
}
}
结果:
try......
catch......
我们之前说过,遇到return会直接返回,不会再运行下面的代码了,但是在这里finally是比retrn先执行的。
再来:
package com.dh.exception;
public class Exception05 {
public static void main(String[] args) {
System.out.println(show());
}
public static int show(){
int i = 100;
try {
return i;
}finally {
i++;
}
}
}
看看结果:
100
结果居然还是100!并不是101。
这是以为Java一开始定下了一些语法,不可变的,否则就是打脸了:
-
方法体{}中的代码必须按照自上而下的顺序逐行执行。
-
return语句一旦执行,方法必须结束。
那程序到底是怎么执行的呢?我们可以去看看.class文件:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.dh.exception;
public class Exception05 {
public Exception05() {
}
public static void main(String[] args) {
System.out.println(show());
}
public static int show() {
byte i = 100;
byte var1;
try {
var1 = i;
} finally {
int var5 = i + 1;
}
return var1;
}
}
原来是先把i赋值给了一个临时变量,如何执行i++,return语句的确是最后执行的,并且返回的是临时变量。
6.final、finally、finalize的区别
-
final是关键字,状态修饰符。修饰的变量为常量,修饰的方法不能被重写,修饰的类不能被继承;
-
finally也是一个关键字,是try的一个子句,一定会执行里面的代码,除非系统退出;
-
finalize是一个标识符,是Object中的一个方法,finalize(),是一个有关垃圾回收的方法。