文章目录
1.什么是异常?
异常是程序在执行的过程中出现的错误。
2.异常体系
3.Error
系统级别异常,出现异常后无法通过程序本身解决的异常,只能重启应用程序。
4.Exception
异常出现后通过程序本身就能解决的异常。
4.1.编译时异常
除了RuntimeException外的异常都是编译异常,它是源码编译前需要处理的异常。
必须明确的一点,编译时异常不是指编译时出现的异常,而是指编译前需要处理的异常,它和所有异常一样都是程序在运行期间出现的错误,编译时异常不一定出现,它是解决用户输入错误导致异常的预留方案。
针对某些代码,如果在编译前不进行异常处理,那么就无法通过编译器生成字节码文件。这就是所谓的异常处理,它专门为编译时异常所生的,异常处理方式会在稍后进行讲解。
异常处理工作是程序员不得不做的,即使后来程序在运行时没有出现异常。当程序在JVM运行时真的出现这种异常,就会根据之前的异常处理方案去处理异常。
编译异常和程序员的开发水平和没有一点关系,程序员无法制造编译异常,它往往是因为用户的错误输入导致的异常,比如找不到文件,商品关联套餐不能下架(自定义异常)等等。
编译时异常是无法避免的,可遇不可求的,用户总有输入错误的时候。
常见的编译异常:
IOException:输入输出流异常
FileNotFoundException:文件找不到的异常
ClassNotFoundException:类找不到的异常
DataFormatException:数据格式化异常
NoSuchFieldException:没有匹配的属性异常
NoSuchMethodException:没有匹配的方法异常
SQLException:数据库操作异常
TimeoutException:执行超时异常
4.2.RuntimeException
运行时异常,它是源码编译前不需处理的异常,出现异常最后由JVM杀死程序。
如果出现了RuntimeException,那就表示代码设计的逻辑不够严谨,这就是程序员本身的问题了。
当然你也可以对RuntimeException做异常处理,但是你既然知道一定会出RuntimeException的,为什么不做逻辑处理,而去做异常处理呢?所以异常处理对于RuntimeException是鸡肋般的存在。
常见的运行时异常:
NullPointerException:空指针异常
ArrayIndexOutofBoundsException:数组越界异
ClassCastException:类型转换异常
IllegalArgumentException:非法的参数异常
InputMismatchException:输入不匹配
5.编译时异常的处理机制
异常处理机制有三种,分别是:
- throws:将方法出现的异常抛给方法调用者,层层往外抛,最终抛给JVM,由JVM出手杀死程序。异常处之前的代码都会执行,异常处之后的代码不会执行。
- try…catch:将方法出现的异常进行捕捉处理,异常处之前和之后的代码正常执行。
- throws和try…catch相结合:将需要处理的异常的方法都用throws抛给调用者,层层往外抛,在最外层的调用者进行try…catch统一捕捉异常。
下面通过代码运行实践验证结论:
5.1.throws
代码演示:
//将异常抛给JVM
public static void main(String[] args) throws FileNotFoundException {
f2();
}
//将异常抛给方法调用者main()
public static void f2() throws FileNotFoundException {
f1();
System.out.println("f2...");
}
//将异常抛给方法调用者f2()
public static void f1() throws FileNotFoundException {
System.out.println("test throws");
InputStream is = new FileInputStream("e:\\2\\abc.txt");
System.out.println("f1...");
}
测试结果:
由测试结果可以看出,异常出现处即InputStream is = new FileInputStream(“e:\2\abc.txt”),在它之前的代码都运行了,并立即杀死程序,打印异常的日志信息,在异常处之后的代码明显没有执行。
5.2.try…catch
代码演示:
public static void main(String[] args) {
f2();
}
//自己捕捉异常
public static void f2() {
f1();
try {
InputStream is = new FileInputStream("e:\\2\\abc.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("f2...");
}
//自己捕捉异常
public static void f1() {
System.out.println("test throws");
try {
InputStream is = new FileInputStream("e:\\2\\abc.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("f1...");
}
测试结果:
可以从输出内容看出,异常处之前的代码和之后的代码都被执行了。
关于这个异常处的定义,可要仔细说说了,在throws异常处理机制中,异常处就是出现异常的那一行代码。
可是它在try…catch里的定义可完全和throws的不一样:
在try…catch中异常处是try里面出现异常的那一行及其往后的内容。
try这个关键字可不简单,它会让JVM将出现异常的那一行及其往后的内容看作是一个整体异常处。
请往下看:
public static void main(String[] args) {
f2();
}
//自己捕捉异常
public static void f2() {
f1();
try {
InputStream is = new FileInputStream("e:\\2\\abc.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("f2...");
}
//自己捕捉异常
public static void f1() {
System.out.println("test throws");
try {
int a = 10,b = 20;
System.out.println("a:" + a);
System.out.println("b:" + b);
InputStream is = new FileInputStream("e:\\2\\abc.txt");
System.out.println(sum(a, b));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("f1...");
}
public static int sum(int a, int b) {
return a + b;
}
测试结果:
从输出的内容可以看出,a和b被输出了,但并没有执行sum(),这是为何?
因为a和b在异常出现的那一行(即InputStream这行)之前,而sum()这一行在InputStream这行之后,JVM认为InputStream这行和及其往后的行是一个整体异常处,所以就不往下执行sum()方法了。
所以我们不要盲目地像别人那样把将所有代码都放在try里面,要谨慎考虑啊。
那什么时候把代码都放进try里面?什么时候又不全放里面了?
答:如果出现异常的那一行代码会影响其在try里它往后的代码,那就都代码放进try里面,因为要保持程序的一致性嘛。相反,出现异常的那一行代码不会影响它往后的代码,那就不要都放进try里,都放进去,你的异常不就连累我其他代码的正常运行吗?
5.3.throws和try…catch结合
代码演示:
public static void main(String[] args) {
try {
f2();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
//自己捕捉异常
public static void f2() throws FileNotFoundException {
f1();
InputStream is = new FileInputStream("e:\\2\\abc.txt");
System.out.println("f2...");
}
//自己捕捉异常
public static void f1() throws FileNotFoundException {
System.out.println("test throws");
InputStream is = new FileInputStream("e:\\2\\abc.txt");
System.out.println("f1...");
}
throws和try…catch相结合,可以省去重复的try…catch工作,将所有异常集中处理,让程序员开发起来变得十分方便。
6.异常处理使代码更稳健案例
不进行异常处理会使得程序遇到异常立即死亡,而进行了异常处理,程序则不会死亡,异常处理会使得代码更稳健。
需求:
键盘录入一个合理的价格为止(必须是数值,值必须大于0)。
分析:
定义一个死循环,让用户不断的输入价格。
代码:
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (true) {
try {
System.out.println("请您输入合法的价格:");
String priceStr = sc.nextLine();
// 转换成double类型的价格
double price = Double.valueOf(priceStr);
// 判断价格是否大于 0
if(price > 0) {
System.out.println("定价:" + price);
break;
}else {
System.out.println("价格必须是正数~~~");
}
} catch (Exception e) {
System.out.println("用户输入的数据有毛病,请您输入合法的数值,建议为正数~~");
}
}
}
测试:
7.自定义异常
7.1.自定义异常的必要
Java无法为这个世界上全部的问题提供异常类,因为Java不知道你的业务逻辑所定义的异常是什么,如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了。
比如,企业系统认为员工年龄超过200岁就是异常,或者性别是女的就是异常,对于这些问题,明显Java不会提供异常类,需要我们自己自定义异常。
7.2.自定义异常的好处
可以使用异常的机制管理业务问题,如提醒程序员注意(这个位置可能会出现异常)。
同时一旦出现bug,可以用异常的形式清晰的指出出错的地方(在第几行)。
7.3.自定义异常的分类
1、自定义编译时异常
- 定义一个异常类继承Exception
- 重写构造器,用于自定义异常信息。
- 在认为是异常的地方用throw new 自定义对象抛出
编译前就需要处理,throws或try…catch。
自定义编译时异常类:
public class ItheimaAgeIlleagalException extends Exception{
public ItheimaAgeIlleagalException() {
}
public ItheimaAgeIlleagalException(String message) {
super(message);
}
}
//抛出ItheimaAgeIlleagalException异常给方法调用者,这里是JVM
public static void main(String[] args) throws ItheimaAgeIlleagalException {
int age = 201;
if (18 <= age && age <= 200) {
System.out.println("年龄合法,开始推荐");
} else {
//现在我认为未满18岁或大于200岁是异常
//抛出ItheimaAgeIlleagalException异常到函数体
throw new ItheimaAgeIlleagalException("ItheimaAgeIlleagalException,请输入合法的年龄~");
}
}
测试:
2、自定义运行时异常
- 定义一个异常类继承RuntimeException
- 重写构造器
- 在出现异常的地方用throw new 自定义对象抛出!
编译前不需要处理异常。
自定义编译时异常:
public class ItheimaAgeIlleagalRuntimeException extends RuntimeException{
public ItheimaAgeIlleagalRuntimeException() {
}
public ItheimaAgeIlleagalRuntimeException(String message) {
super(message);
}
}
@Test
public void test() {
int age = 201;
if (18 <= age && age <= 200) {
System.out.println("年龄合法,开始推荐");
} else {
//现在我认为未满18岁或大于200岁是异常
//抛出ItheimaAgeIlleagalException异常到函数体
throw new ItheimaAgeIlleagalRuntimeException("ItheimaAgeIlleagalRuntimeException,请输入合法的年龄");
}
}
测试:
7.4.throws和throw的区别和联系
throws和throw的区别,首先是出现的位置不同:
throws是定义在方法上的,而throw是使用在函数体内的。
其次是作用的不同:
throws是异常处理机制的一种,将可能出现的异常(不一定出现)抛给方法的调用者。
throw的作用是将已经出现或者说是构建的异常抛到函数体内,并且如果抛出的编译时异常(继承了Exception),就必须使用异常处理机制(throws或try…catch),处理throw出来的异常。
throws和throw的联系:
throw可以搭配throws使用,当throw到函数的异常是编译时异常,就可以使用throws来处理异常。
当这种处理方式是不负责也不满足日常开发,所以一般是使用 throw 加上 try…catch。
执行流程:
8.总结
1.异常是什么?
异常是程序执行中出现的错误。
2.异常的分类?
- Errror:系统级别的错误,异常出现后程序本身无法处理,需要重启应用程序。
- Exception:java.lang包下的异常,异常出现后程序本身就能解决的异常。
- RuntimeException:运行时异常,源码编译前不需要进行处理的异常。
- 编译时异常:源码编译前需要进行处理的异常,否则无法通过编译。
3.编译时异常处理机制有几种?
- throws:将方法的异常抛给方法的调用者,层层往外抛,直至抛给JVM,让JVM杀死程序,异常处之前的代码都会执行,异常处之后的代码不会执行。
- try…catch:将方法的异常进行捕捉处理,异常处之前的代码和异常处之后的代码都会正常执行。
- throws和try…catch结合:将需要处理的异常用throws抛给方法调用者,层层往外抛,在外层的方法调用者中统一用try…catch捕捉处理异常。
4.需要注意的点:
异常处在throws和try…catch种的定义不一样,在throws里,异常处就是异常出现的那一行,而在try…catch中,异常处是try里异常出现的那一行及其它往后的代码。
三种异常处理机制的比较:
throws是一种不负责任的行为,层层往外抛,最终把“麻烦”抛给虚拟机,运行完程序异常处之前的代码就杀死程序,异常处往后的代码都不会执行。
try…catch是程序自身处理错误,是一种负责任的行为,而且异常处往后的代码也能继续执行,直至程序执行完毕。但是不好的一点是,当方法嵌套过多时,可能有多个方法需要try…catch,非常麻烦。
如果前提是出现异常的那一行代码会影响其在try里它往后的代码,throws和try…catch相结合这种异常处理方案是最优选择,因为它将所有异常都抛给了最外层调用者去统一处理,能为程序员省去不少时间和精力。
5.异常处理让程序更加稳健
6.自定义异常分类
- 自定义编译时异常
- 自定义运行时异常(鸡肋)
7.throw和throws的区别