一、异常的分类
Java编译器会对检查异常进行检查,而对运行时异常不会检查。也就是说对于编译时异常要么通过throws进行声明抛出,要么通过try…catch进行捕获处理,否则编译不通过。而运行时异常没有throws和try…catch依然可以编译通过。但是同样可以对其进行throws或try…catch。(可以把编译异常理解为炸弹,要么拆掉,要么抛出别人,否则炸死了程序)
1. 错误
如果应用程序出现了 Error,那么将无法恢复,只能重新启动应用程序,最典型的Error 的异常是:OutOfMemoryError(内存溢出)
2. 编译时异常(检查异常)
出现了这种异常必须显示的处理,不显示处理 java 程序将无法编译通过
3. 运行时异常(RuntimeException类及其子类都被称为运行时异常。)
此种异常可以不用显示的处理,例如被 0 除异常,java 没有要求我们一定要处理
二、异常的捕获和处理掉需要采用 try 和 catch
try {
}catch (OneException e){
}catch (TwoException e){
}finally {
}
- try 中包含了可能产生异常的代码
- try 后面是 catch,catch 可以有一个或多个,catch 中是需要捕获的异常
- 当 try 中的代码出现异常时,出现异常下面的代码不会执行,马上会跳转到相应的catch 语句块中,如果没有异常不会跳转到 catch 中
- 如果try中有return,则执行完return后的子句(还是同一行语句return i++,执行了i++后不会返回,如果从这里返回则是返回i的中间缓存值,这要研究JVM),在返回前先执行finally中的语句,如果finally中也有return语句,则返回的入口不再是try中的return,而是直接从finally中的return返回
- finally 表示,不管是出现异常,还是没有出现异常,finally 里的代码都执行,finally 和 catch可以分开使用,但 finally 必须和 try 一块使用
三、getMessage 和 printStackTrace()
- 取得异常描述信息:getMessage()
- 取得异常的堆栈信息(比较适合于程序调试阶段):printStackTrace();
public class Test {
public static void main(String[] args) {
int i1 = 100;
int i2 = 0;
try {
int i3 = i1 / i2;
System.out.println(i3);
} catch (ArithmeticException e) {
// e 是一个引用,它指向了堆中的 ArithmeticException
// 通过 getMessage 可以得到异常的描述信息
System.out.println(e.getMessage());
}
}
}
四、声明异常(抛出异常)
在方法定义处采用 throws 声明异常,如果声明的异常为编译异常,那么调用方法必须处理此异常
private static void readFile()
throws FileNotFoundException, IOException { //声明异常,声明后调用者必须处理
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
} finally {
fis.close();
}
}
捕获和处理
import java.io.*;
public class ExceptionTest {
public static void main(String[] args) {
// throws FileNotFoundException, IOException { //可以在此声明异常,这样就交给 java 虚拟机处理了,不建议这样使用
// throws Exception { //可以采用此种方式声明异常,因为 Exception 是两个异常的父类
try {
readFile();
}catch (FileNotFoundException e){ //不捕获异常就抛出(方法处声明)
System.out.println(e.getMessage());
}catch (IOException e){
System.out.println(e.getMessage());
}
}
}
将 IOException 放到前面,会出现编译问题,因为 IOException 是 FileNotFoundException 的父类,所以截获了 IOException 异常后,IOException 的子异常都不会执行到,所以再次截获 FileNotFoundException 没有任何意义异常的截获一般按照由小到大的顺序,也就是先截获子异常,再截获父异常。(为了更精确提示)
五、手动抛出异常
public class ExceptionTest {
public static void main(String[] args) {
try {
int ret = method1(1000, 0);
System.out.println(ret);
} catch (Exception iae) {
System.out.println(iae.getMessage());
}
}
private static int method1(int value1, int value2) {
try {
if (value2 == 0) { 手动抛出异常
throw new IllegalArgumentException("除数为 0");
//加入如下语句编译出错,throw相当于 return 语句
}
if (!(value1 > 0 && value1 <= 100)) {
//手动抛出异常
throw new IllegalArgumentException("被除数必须为 1~100 之间 的数据");
}
int value3 = value1 / value2;
return value3;
} finally {
//throw 虽然类似 return 语句,但 finally 会执行的
System.out.println("-----------finally------------");
}
}
}
运行结果:
-----------finally------------
除数为 0
异常处理,完全依赖于程序的返回
另外异常处理和程序逻辑混在一起,不好管理
异常是非常,程序语句应该具有一套完成的异常处理体系
六、方法覆盖与异常
方法覆盖的条件:子类方法不能抛出比父类方法更多的异常,但可以抛出父类方法异常的子异常
七、自定义异常
自定义异常通常继承于 Exception 或 RuntimeException,到底继承哪个应该看具体情况来定
//自定义的非受控异常类
public class MyException extends RuntimeException{
public MyException() {
//调用父类的默认构造函数
super();
}
public MyException(String message) {
//手动调用父类的构造方法
super(message);
}
}
public class Test {
public static void main(String[] args) {
try {
method1(10, 0);
} catch (MyException e) {
//必须拦截,拦截后必须给出处理,如果不给出处理,就属于吃掉了该异常
//系统将不给出任何提示,使程序的调试非常困难
System.out.println(e.getMessage());
}
}
private static void method1(int value1, int value2)
throws MyException { //如果是受控异常必须声明(抛出)
if (value2 == 0) {
throw new MyException("除数为 0");
}
int value3 = value1 / value2;
System.out.println(value3);
}
}
在项目中往往官方没有具体的异常类去定义项目中的错误,所以要自定义异常类
八、结论(在软件开发中怎么解决异常让编译通过)
原则:到界面层(controller层)进行try…catch进行信息错误的提示(返回错误信息给前端,不必再一次抛出),其实在业务层或者数据库层也会try…catch,进行事务的回滚和业务需求,但同时会再一次抛出,由调用层判断处理和抛出。