java 异常机制分类_Java异常机制

前言:

写了两周的博客了,深的浅的都有,总有人问我写博客有什么用?能出书吗?写的太浅有人看吗?对找工作有什么帮助吗?不想争辩,也不愿把自己说的多么高大上,分享本身是一件简单的小事,但是能给我带来快乐,我享受别人看过我博客之后有种觉得有收获那种感觉,就这么简单。还有那些害怕写博客被别人质疑太浅的,其实大可不必,总有一些人掌握的知识没有你多,也许你的一句话就解决了他们的疑问。言归正传,今天给大家带来Java异常体系。

1、Java异常体系结构图

d82fe75ee01d

Java异常体系图

1.1、结构角度分类

Error类:系统错误,我们最熟悉的OutOfMemeroyError、StackOverFlowError、NoClassDefoundError等都是该类的子孙,这些错误主要与机器有关。值得注意的是Error我们也可以通过try catch捕获到,尽管捕获到意义不大,比如OutOfMemeroyError我们捕获到要进行处理是不可能的,因为系统已经不工作了。

Exception:异常类,一个Exception发生了那么证明程序员写的代码出问题了,而不是机器的问题。

1.2、Java对异常处理要求分类

Checked Exception类:

A:体系图中的红色部分,编译期间Java编译器会强制要求程序员对该类异常进行处理,要么try catch、要么throw抛出去给调用层处理,否则无法执行。Eclipse里面那些给我们提示的实际上都是这类异常。

B:为什么会存在这种异常呢?简单来说就是对客观上存在的潜在错误进行预处理,就像我们可能并不是每个人都会得天花,但是我们必须接种天花疫苗。Java号称平台无关,程序员写的代码可能会出现在各种环境中,拿IOException来说,假如代码运行的机器断网了,那么网络IO就有可能出现异常,如果不处理那么我们的程序就崩了,可检查异常一般都是大概率出现的异常,因此需要提前处理。

Unchecked Exception类:

A:体系图中蓝色部分,主要包括Error和RuntimeException,编译器不会检查该类型异常,也检查不到,比如我们最讨厌的NullPointerException,编译器不会强制程序员处理这种异常。当然,程序员可以主动try catch捕获这种异常然后进行处理,但是假如你写了除0的代码,与其花时间和代码量去做捕获处理,还不如从根本上杜绝这个问题,RuntimeException很大程度上能反应一个程序员代码的健壮性。

B:既然无法捕获,这种异常有什么存在的必要呢?这就不得不说异常的另一个重要属性,那就是传递信息的属性,一个异常本身就是一种信息,它会告诉程序员哪一行代码抛出了异常,调用链是什么样子的,异常是什么原因导致的,常见的异常如下。

//这一行冒号左边标注了异常类型,冒号右边标注了异常原因

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1

//这一行是异常抛出位置,在代码的第15行,c方法里

at com.huo.demos.test.Test.c(Test.java:15)

//以下三行是方法异常传递路径,即方法调用路径

at com.huo.demos.test.Test.b(Test.java:10)

at com.huo.demos.test.Test.a(Test.java:6)

at com.huo.demos.test.Test.main(Test.java:19)

2、自定义异常

2.1、什么时候需要自定义异常?

情况一:系统定义的异常信息和不够充分。拿常见的NullPointerException来说,该提示只会给出哪里出现了空指针异常,而如果我们自定义类似的异常我们就可以给出是哪个变量因为哪种原因在哪里抛出了该异常。

情况二:团队约定。比如团队开发一个API,要统一对外的异常展示。

情况三:程序可能出现错误但无法预知谁会调用该程序。比如入参要求是电话号码,调用方可能会输入英文字符,这个时候我们有两种选择,一种是做判断之后返回一个error code,此时的方法返回值就会被限制为int类型,而且需要给调用方出示一个error code含义表;另一种方法就是抛出异常,我不用考虑调用者是谁,也不用限定返回值,调用方只要调用了我的代码编译器就会提示他必须对这种可能的错误进行预处理。

d82fe75ee01d

image.png

2.2、如何自定义异常?

A:继承Exception:

Exception没有强制要求子类实现其方法,但最好还是实现一下。当然如果只为给一个简单的提示或者根据异常名称就可以快速知道发生了什么,也可以不用定义这些。

public class EException extends Exception {

public EException() {

super();

}

public EException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {

super(message, cause, enableSuppression, writableStackTrace);

}

public EException(String message, Throwable cause) {

super(message, cause);

}

public EException(String message) {

super(message);

}

}

B:继承RuntimeException

public class RException extends RuntimeException {

public RException() {

super();

}

public RException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {

super(message, cause, enableSuppression, writableStackTrace);

}

public RException(String message, Throwable cause) {

super(message, cause);

}

public RException(String message) {

super(message);

}

public RException(Throwable cause) {

super(cause);

}

}

C:两者区别:

首先:继承Exception的异常调用方必须抛出或者try catch处理,这是编译器强制的,而继承RuntimeException的异常不会这样。

d82fe75ee01d

继承Exception的异常

d82fe75ee01d

继承RuntimeException的异常

然后:继承Exception的异常应该出现在这种情况里,即我的代码极有可能出现这种异常,因为我无法考虑到所有环境,因此我抛出给调用方根据自己的环境进行对应处理。继承RuntimeException的异常应该出现在此种环境里,即我的代码出现这种异常的可能性不是特别大,但是还是有出现的可能,而在出现这种异常的时候我能够给调用方以足够的提示信息告知他发生了什么。

3、异常捕获机制

3.1、catch捕获范围

从第一个catch开始到最后一个catch形成捕获队列,按照队列的顺序谁先捕获到就归谁处理。这个过程要考虑多态的问题,由于Exception是许多异常类的父类,因而如果处在队列前面,后面的队列的声明就没有意义了。

public static int bMethod() {

try {

return cMethod(true);

} catch (EException e) { //第一个捕获

e.printStackTrace();

} catch (Exception e) { //第一个没捕获到,第二个来捕获

e.printStackTrace();

} finally {

return 1;

}

}

3.2、finally的执行问题

finally的中文意思是最终,也就是段try catch finally代码的try catch该执行的都执行完之后最终要执行finally里的代码。简单起见可以把try catch finally看成一个代码块。

为了更好的解释finally运行机制,我们先了解一下虚拟机栈帧。

d82fe75ee01d

虚拟机栈帧

我们知道,每个线程都有一个自己的栈空间,实际上,当方法执行的时候JVM会把方法制作成栈帧压入到操作栈中,每个方法都对应着一个栈帧,当前执行的栈帧总是位于栈顶。

栈帧包含局部变量表、操作数栈、动态连接、方法返回地址。这里的方法返回地址实际上是在方法遇到ret指令之后返回值存放的位置的地址,也就是说返回值并没有直接返回给方法的调用者,而是中间又多了一步将返回值存放到栈的某一个位置。在真正把返回值交给上层调用者之前,如果遇到break、continue、return、抛出异常等情况JVM会清除栈中的返回值

例子1,返回100

public static int method1() {

int k = 0;

try {

k = 1;

//返回的k=1首先存储到栈中的返回值位置,如果顺利完成就把该位置的值返回,如果遇到特殊指令如break、continue、return、抛异常就清除

return k;

} catch (Exception e) {

e.printStackTrace();

} finally {

//清除原来的返回值,把100放到返回值的位置上,如果顺利结束就返回,否则清除

return 100;

}

}

例子2,返回100

public static int method2() {

int k = 1;

while (true) {

try {

return k;

} catch (Exception e) {

e.printStackTrace();

} finally {

//遇到break,返回值位置的值被清除

break;

}

}

k=100;

return k;

}

例子3,说明finally不是一定会执行

public static int method1() {

int k = 1;

//此处抛异常或者try和catch里调用system.exit()方法finally就不会执行。

Integer.parseInt(null);

try {

return k;

} catch (Exception e) {

e.printStackTrace();

} finally {

return k;

}

}

总结

其实日常开发我们遇到最多的就是NPE问题,NPE问题即是小问题又是破坏健壮性的大问题,我们应该更好的防范一下。我刷leetcode和进行一些日常开发总结了一些经验,供大家参考下:

1、入参是对象的时候应该对入参进行判空操作

2、调用的其他人写的方法返回的是对象的时候应该对该对象进行判空操作

3、调用数据库或者远程调用的返回值应该进行判空操作

4、保证调用者为已知对象。str1.equals(str2)方法,str1应该是已知对象,str2可以为空;String.valueOf()和Integer/Long/Double.toString()返回值相同使用前者

5、自己写的方法尽量不要返回空值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值