java之异常(Exception)与错误(Error)的区别
概念
Error(错误):程序无法处理,通常指程序中出现的严重问题。
- 例如
java.lang.VirtualMachineError
(Java虚拟机运行错误):当 Java虚拟机崩溃或用尽了它继续操作所需的资源时,抛出该错误 - 例如
java.lang.StackOverflowError
(栈溢出错误):当应用程序递归太深而发生堆栈溢出时,抛出该错误。 - 例如
java.lang.OutOfMemoryError
(内存溢出):内存溢出或没有可用的内存提供给垃圾回收器时,产生这个错误。 - Error(错误)是不可查的,而且也常常在应用程序的控制和处理能力之外,因此当Error(错误)出现时,程序会立即奔溃,Java虚拟机立即停止运行,
Exception(异常):是指程序本身可以处理的异常(可以向上抛出或者捕获处理)。
Java处理异常的默认方式是中断处理
。
- 以
java.lang.NullPointerException
为例,当程序出现空指针异常时,会创建一个空指针异常对象,并向外抛出,并被虚拟机捕获,从而导致程序中断执行。
异常分类
Exception(异常)分为两大类:运行异常和编译异常。
- 运行异常:顾名思义,是程序运行时才会出现的异常。运行异常是
java.lang.RuntimeException
类及其子类的统称。如NullPointerException
(空指针异常)、IndexOutOfBoundsException
(数组下标越界异常)等。运行异常一般是由程序逻辑错误导致的,可以通过捕获处理或向上抛出。运行异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现运行异常,也会被编译通过。 - 编译异常:顾名思义,是程序编译时就会出现的异常。编译异常是
java.lang.RuntimeException
以外的异常。编译异常必须进行处理,如果不处理,程序就不能编译通过。
异常产生的原因
以一个简单的ArrayIndexOutOfBoundsException
举例
public class Demo01 {
public static void main(String[] args) {
int[] arr = new int[]{10,20,30};
int number = getNumber(arr,5);
System.out.println(number);
}
/**
* 获取指定索引位置的数据
*/
public static int getNumber(int[] arr,int index){
int number = arr[index];
return number;
}
}
------------------------------------------------------------------------------------
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at com.hanyxx.exception.Demo01.getNumber(Demo01.java:18)
at com.hanyxx.exception.Demo01.main(Demo01.java:10)
过程分析
异常的处理
Java异常处理的五个关键字:try、catch、finally、throw、throws
try…catch…finally : 捕获异常并处理
使用格式:
try{
编写可能会出现异常的代码
}catch(异常类型 e){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
简单实例:略
面试题: 如果try{} 里有一个 return 语句,那么finalfly{} 里的代码会不会被执行,什么时候被执行,在 return 前还是后?
- 会执行,在return前执行。当程序检测到try{}中的return语句时,它会在return语句执行前,检查是否finalfly{}语句,如果有,就执行finalfly{}语句,如果没有,直接返回。如果finalfly{}里也有return 语句,那么try{}中的return语句将失效。
简单的代码验证:
public class Demo01 {
public static void main(String[] args) {
int[] arr = new int[]{10,20,30};
int number = getNumber(arr,1);
System.out.println("获取值: " + number);
}
/**
* 获取指定索引位置的数据
*/
public static int getNumber(int[] arr,int index){
try {
int number = arr[index];
System.out.println("返回值:" + number);
return number;
} catch (Exception e) {
e.printStackTrace();
}finally {
return 88;
}
}
}
执行结果:
finally:无论异常是否产生,都需要执行的代码。
-
问:什么时候的代码必须最终执行?
-
答:当我们在try语句中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),无论是否产生异常,都必须将资源关闭。
throw:抛出异常
在编写程序时,我们必须要考虑程序出现问题的情况。比如,在定义方法时,方法需要接受参数,在进行业务处理前,应当先对参数数据进行合法判断,若数据不合法,则告知调用者传递合法的数据。
此事需要使用抛出异常的方式来告诉调用者。
使用格式:
throw new 异常类名(参数);
例如:
throw new NullPointerException("数据为空,请检查。");
throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在。");
简单代码实例:
public class Demo01 {
public static void main(String[] args) {
int[] arr = new int[]{10,20,30};
int number = getNumber(arr,5);
System.out.println("获取值: " + number);
}
/**
* 获取指定索引位置的数据
*/
public static int getNumber(int[] arr,int index){
if(index < 0 || index > arr.length - 1){
// 索引越界
throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在: " + index);
}else{
return arr[index];
}
}
}
执行结果:
从上面的代码中可以看出,throw关键字使用在方法内,可以抛出指定的异常对象,方式如下:
- 创建一个异常对象。封装一些提示信息(信息可以自己编写)。
- 代码出现异常时,该异常对象将会自动传递到调用者处,同时结束当前方法的执行。
throws:声明异常
声明异常:声明一个异常,并告知调用者。关键字throws运用于方法声明上,表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(捕获处理或者继续抛出)。
- 如果方法内部throw一个编译异常,而没有捕获处理,那么必须在方法体上通过throws进行声明,让调用者处理。
为什么?
因为运行异常有jvm处理,编译异常jvm是不管的,必须自定义处理逻辑(捕获处理或者上抛)。
声明异常格式:
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
简单演示:
import java.io.FileNotFoundException;
/**
* @author layman
*/
public class Demo01 {
public static void main(String[] args) throws FileNotFoundException {
study("学习日本知识.avi");
}
/**
* 学习知识,充实自己
*/
public static void study(String path) throws FileNotFoundException {
if (!path.equals("学习日本知识.avi")) {
throw new FileNotFoundException("对不起,文件损坏,无法深入学习,请注意身体!");
}
}
}
如果方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗号隔开:
import java.io.FileNotFoundException;
/**
* @author layman
*/
public class Demo01 {
public static void main(String[] args) throws FileNotFoundException, NoSuchFieldException {
study("学习日本知识.avi");
study("学习欧美知识.avi");
}
/**
* 学习知识,充实自己
*/
public static void study(String path) throws FileNotFoundException, NoSuchFieldException {
if(!path.equals("学习日本知识.avi")) {
throw new FileNotFoundException("对不起,文件损坏,无法深入学习,请注意身体!");
}
if(!path.equals("学习欧美知识.avi")){
throw new NoSuchFieldException("感觉身体被掏空...");
}
}
}
注意事项
多个异常使用捕获又该如何处理呢?
- 多个异常分别处理。
- 多个异常一次捕获,多次处理。
- 多个异常一次捕获一次处理。
一般我们是使用一次捕获多次处理方式,格式如下:
try{
编写可能会出现异常的代码
}catch(异常类型A e1){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型B e2){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
- 注意:这种异常处理方式,如果catch中的多个异常之间有子父类异常的关系,那么子类异常要在上面的catch语句块中处理,父类异常在下面的catch语句块中处理(多态)。
补充:
-
运行时异常被抛出,调用者可以不处理,那么就会由jvm处理。
-
如果finally{}语句块中有return语句,那么会永远返回finally中的结果。
-
如果父类方法抛出多个异常,那么子类重写父类方法时,可以选择抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
-
如果父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出。