概念
java异常:程序执行过程中,出现的非正常的情况,最终导致JVM的正常停止。它是Java提供的一种识别及响应错误的一致性机制,异常本身是一个类,产生异常就是创建一个异常对象,并抛出该异常对象,java处理异常的方式就是中断处理。

分类
异常的根类:java.lang.Throwable, 它有两个子类,分别是java.lang.Error(无法避免)与java.lang.Exception(可以避免)
Error:程序本身无法克服和恢复,没救了。一般是指虚拟机相关的问题,例如系统崩溃,内存空间不足,方法调用栈溢出,遇到该类问题,建议让让程序终止。
Exception:还能够解决,可捕获,且可能恢复,其中异常分为系统异常和普通异常。
系统异常(unchecked Exception,也叫(Runtime Exception,运行时异常):软件本身缺陷导致的,例如数组脚本越界,空指针异常,软件使用者无法克服和恢复,但是该类问题可以让软件系统继续运行或者软件中断运行。(可处理,也可以不处理,不强制用ry..catch 或者throws声明)
普通异常(checked Exception,也叫编译期异常): 普通异常在源代码中必须显式的进行捕获处理,这是编译检查的一部分。(必须用try..catch 或者throws声明继续抛给上层调用方法处理) checked Exception 是是不兼容functional 编程的(函数式编程)你写过 Lambda/Stream 代码,相信深有体会。

分析
//主方法
public static void main(String[] args) {
int[] arr0 = {1, 2, 3};
System.out.println(getElement(arr0, 3));
}
/**
*
* @param arr 数组名
* @param index 索引
* @return 获取指定索引的数组值
*/
public static int getElement(int[] arr, int index) {
return arr[index];
}
结果:
* Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
* at day01.demo.throwerror.getElement(throwerror.java:11)
* at day01.demo.throwerror.main(throwerror.java:7)
分析:通过上述可知,此处异常是,索引越界异常,数组索引最大为2
* 过程:
* 1.jvm根据异常,产生异常对象,这个异常对象,该对象包含了异常产生的内容,原因,位置
* 2.getElement()方法中没有处理异常的方法,jvm把有异常对象抛给方法调用者,让main方法处理这个异常
*
* 3.main()方法接收异常对象,它也没有对异常进行处理,继续将对象抛出给main方法的调用者JVM 处理
* ☟
* 4.JVM接收到这个异常对象,做了两个事情
* ① 把异常对象的内容,原因、位置以红色部分打印在控制台
* ② JVM 终止当前正在执行的java程序(中断处理)
处理
throw 关键字:
使用格式: throw new XXXException("异常产生的原因");
注意: throw关键字必须写在方法内部
throw关键字后面new 的对象必须是Exception或者 Exception的子类对象
throw关键字抛出指定的异常对象,必须处理该异常对象
RuntimeException (运行期异常,unchecked),交给jvm处理,也就是中断处理。直接用throw关键字抛出即可
public static int getElement1(int[] arr, int index) {
// 空指针异常,进行判断
if(arr==null){
// throw关键字必须写在方法内部
throw new NullPointerException("数组为空");
}
// Objects可以进行非空判断,可简化上述步骤
Objects.requireNonNull(arr);
Objects.requireNonNull(arr,"数组为空");
// 判断索引是否越界
if(index<0||index>arr.length-1){
throw new ArrayIndexOutOfBoundsException("索引越界异常");
}
return arr[index];
}
编译期异常(checked异常),必须处理,用throws关键字 和try.. catch
throws 关键字
把异常交给调用者处理(自己不处理,交给别人去处理),最终交给JVM处理,也就是中断处理
注意:
1.throws 关键字必须写在方法声明处
2.throws 关键字后面声明异常必须是Exception或者Exception子类
3.方法内部出现了多个异常,throws后面必须声明多个异常,多个异常中存下父子关系,声明父类异常即可
4.调用了一个声明抛出异常的方法,我们必须处理声明的异常,继续使用throws声明抛出,或try catch
public class throwsdemo {
//调用了一个声明抛出异常的方法,我们必须处理声明的异常,继续使用throws声明抛出
public static void main(String[] args) throws FileNotFoundException, IOException{
read("a.txt");
}
// throws 关键字必须写在方法声明处
// 方法内部出现了多个异常,throws后面必须声明多个异常
//FileNotFoundException,IOException都属于编译期异常
//IOException是 FileNotFoundException的父类,FileNotFoundException可省略
public static void read(String path) throws FileNotFoundException, IOException {
if(!path.equals("a.txt")){
throw new FileNotFoundException("文件不存在");
}
if(!path.endsWith("txt")){
throw new IOException("文件后缀名错误");
}
}
}
上述解决异常,只是将异常抛出了,最后JVM 执行中断处理,导致程序无法执行
try...catch 捕获异常:
*捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。
*捕获异常语法如下:
try {
编写可能会出现异常的代码
} catch (异常类型 e) {
进行异常的捕获,对捕获到的异常进行处理
记录日志/打印异常信息/继续抛出异常
} finally {
代码块中存放的代码都是一定会被执行的
}
*注意:try和catch都不能单独使用,必须连用。
*try中产生多个异常,可以使用多个catch处理异常对象,catch中异常变量如果有子父类关系,子类异常写在上面
*try中无异常,catch代码块不执行
*注意:finally不能单独使用
public static void main(String[] args) {
try {
read("a.txt");
} catch (FileNotFoundException e) {
//抓取到的是编译期异常 抛出去的是运行期
throw new RuntimeException(e);
} finally {
System.out.println("不管程序怎样,这里都将会被执行。");
}
System.out.println("over");
}
public static void read(String path) throws FileNotFoundException {
if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
// 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw
throw new FileNotFoundException("文件不存在");
}
}
Throwable类中定义了一些查看方法:
public String getMessage():获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。
public String toString():获取异常的类型和异常描述信息(不用)。
public void printStackTrace():打印异常的跟踪栈信息并输出到控制台。
public static void main(String[] args) {
try {
read("a.tx");
} catch (FileNotFoundException e) {
System.out.println( e.getMessage());//简短描述,结果:文件不存在
System.out.println(e.toString());//返回此throwable的详细消息字符串,结果:java.io.FileNotFoundException: 文件不存在
System.out.println(e);//结果:java.io.FileNotFoundException: 文件不存在
} finally {
System.out.println("不管程序怎样,这里都将会被执行。");
}
System.out.println("over");
}
public static void read(String path) throws FileNotFoundException {
if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
// 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw
throw new FileNotFoundException("文件不存在");
}
}
异常注意事项
finally中有return语句,永远返回finally中的结果,finally中最好没有return语句
/*
finally中有return语句,永远返回finally中的结果,finally中最好没有return语句
*/
public static void main(String[] args){
System.out.println(get());//运行结果为100
}
public static int get() {
int a = 10;
try {
return a;
}catch (Exception e){
System.out.println(e);
}
finally {
a=100;
return a;
}
}
父类抛出多个异常,子类重写父类方法,抛出和父类相同的异常,或父类异常的子类,或不抛出异常
父类方法没有抛出异常,子类重写父类方法时,不可抛出异常。如果产生异常,只能捕获处理,不能声明抛出。
public class Fu {
public void show01() throws NullPointerException,ClassCastException{}
public void show02() throws IndexOutOfBoundsException{}
public void show03() throws IndexOutOfBoundsException{}
public void show04() {}
}
class zi extends Fu{
@Override
//子类重写父类方法时,抛出和父类相同异常
public void show01() throws NullPointerException, ClassCastException {
super.show01();
}
/*
子类重写父类方法时,抛出和父类异常的子类
ArrayIndexOutOfBoundsException是 IndexOutOfBoundsException的子类
*/
@Override
public void show02() throws ArrayIndexOutOfBoundsException {
super.show02();
}
/*
子类重写父类方法时,不抛出异常
*/
@Override
public void show03() throws IndexOutOfBoundsException {
super.show03();
}
/*
父类没有抛出异常,子类不可以抛出异常,只能捕获处理不能声明抛出
*/
@Override
public void show04(){
try {
}catch (Exception e){
System.out.println(e);
}
}
}
随堂小练
问题,throw异常和throws异常的区别:
*throw异常
1)throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。
2)throw是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行throw一定是抛出了某种异常。
*throws异常:
1)throws语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。
2)throws主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。
3)throws表示出现异常的一种可能性,并不一定会发生这种异常。
几个常见的RuntimeException异常
1)java.lang.NullPointerException 空指针异常;原因:调用了未经初始化的对象或者是不存在的对象。
2)java.lang.ClassNotFoundException 指定的类找不到;原因:类的名称和路径加载错误;
通常是程序试图通过字符串来加载某个类时可能引发异常。
3)java.lang.NumberFormatException 字符串转换为数字异常;原因:字符型数据中包含非数字型字符。
4)java.lang.IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对象时发生。
5)java.lang.IllegalArgumentException 方法传递参数错误。
6)java.lang.ClassCastException 数据类型转换异常
final、finally、finalize的区别
1)final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。
2)finally:异常处理语句结构的一部分,表示总是执行。
3)finalize:Object类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法
提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法
被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对
象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用。
延伸:
1. 尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常,如果程序中有运行期异常,我们更希望的RuntimeException 被扩散来,而不是被捕获。进一步而言,不要捕获Error 或者Throwable 异常。
2. 不要生吞(swallow)异常,这种异常需要特别的注意,生吞异常往往是基于假设这段代码可能不会发生或者忽略也无所谓,产品研发上面千万不要做这种假设,如果异常不被跑出来,或者打印到日志当中,很难且无法预料报异常的原因。生吞异常无非就是掩盖问题。
3. throw early 和 catch late :fileName 如果为空,程序抛出 NullPointerException,所以我们要 throw elary ,也就意味着进行提前进行判空处理,可以通过if条件语句判空处理等。
catch late 就比较苦恼了,异常捕获了如何处理呢,上面我们讲到了生吞异常是万万不可以的,如果不知道如何处理,保留原有的cause 信息,或者一般业务中会自定义异常,直接返回 0 或者 -1 等一些状态码,指定某种特定的问题。
public void readPreferences(String fileName){
//...perform operations...
InputStream in = new FileInputStream(fileName);
//...read the preferences file...
}
// 第一时间进行抛出
public void readPreferences(String fileName){
//...perform operations...
InputStream in = new FileInputStream(fileName);
//...read the preferences file...
}
有一个不错的例子分享给大家
假如你开车上山,车坏了,你拿出工具箱修一修,修好继续上路(Exception被捕获,从异常中恢复,继续程序的运行),车坏了,你不知道怎么修,打电话告诉修车行,告诉你是什么问题,要车行过来修。(在当前的逻辑背景下,你不知道是怎么样的处理逻辑,把异常抛出去到更高的业务层来处理)。你打电话的时候,要尽量具体,不能只说我车动不了了。那修车行很难定位你的问题。(要捕获特定的异常,不能捕获类似Exception的通用异常)
本文围绕Java异常展开,介绍其概念为程序执行中的非正常情况。阐述了异常分类,包括Error和Exception,后者又分系统异常和普通异常。还说明了不同异常的处理方式,如RuntimeException交JVM,编译期异常用throws和try..catch。最后给出异常处理注意事项和示例。

被折叠的 条评论
为什么被折叠?



