认识Java中的异常
1. 异常的概念与体系结构
异常不同于编译错误,语法错误
算数异常:

数组下标越界异常:

空指针异常:

异常其实就是一个一个的类,所有异常都继承了Throwable类,其派生出两个重要的子类, Error 和 Exception

其中Exception又分为两大类,一类是运行时异常/非受查异常,另一类为编译时异常/受查异常。
(1)运行时异常就是代码还没运行就报错了


对于这种异常有一个很明显的特点就是要处理掉异常才能继续运行
(2)编译时异常指的是Java虚拟机无法解决的严重问题

这种情况需要程序员手动去处理错误
总结:
1. Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception
2. Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表: StackOverflowError和OutOfMemoryError,一旦发生回力乏术。
3. Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。我们平时所说 的异常就是Exception。
2. 异常的处理
防御式编程
(1)LBYL: Look Before You Leap. 在操作之前就做充分的检查. 即:事前防御型
缺陷:正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱
(2)EAFP: It's Easier to Ask Forgiveness than Permission. "事后获取原谅比事前获取许可更容易". 也就是先操 作, 遇到问题再处理. 即:事后认错型
优势:正常流程和错误流程是分离开的, 程序员更关注正常流程,代码更清晰,容易理解代码 异常处理的核心思想就是 EAFP。
在Java中,异常处理主要的5个关键字:throw、try、catch、final、throws。
异常的抛出
在编写程序时,如果程序中出现错误,此时就需要将错误的信息告知给调用者,比如:参数检测。 在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者
程序员可以通过throw来抛出异常,也可以在抛出的异常创建构造方法


异常的捕获
异常的捕获,也就是异常的具体处理方式,主要有两种:异常声明throws 以及 try-catch捕获处理
异常声明throws:处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛 给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常。
以克隆为例:
当加上throws CloneNotSupportedException后,声明当前方法可能会出现异常,但是这里的异常并没有被处理,只是交给了JVM处理,一旦交给JVM处理,程序就立即终止了。(即告诉JVM这里会出现异常)


【注意事项】
1. throws必须跟在方法的参数列表之后
2. 声明的异常必须是 Exception 或者 Exception 的子类
3. 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型 具有父子关系,直接声明父类即可。
4. 调用声明抛出异常的方法时,调用者必须对该异常进行处理(需要用到try-catch),或者继续使用throws抛出。(此处以克隆为例,当方法抛出异常时,在main调用这个方法需要处理该异常,或者也抛出该异常)


try-catch捕获并处理
throws对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者处理。如果真正要对异常进行 处理,就需要try-catch。

以克隆为例:当方法中存在异常会进入catch部分,执行catch中的语句,当不抛出异常时会继续执行,不会进入catch部分。


进一步理解

当处理完异常,想定位异常的准确位置可以通过printStackTrace()
当想处理两个不同类型的异常时,可以用多个catch()

也可以用分隔符|来进行类型的分割(但分隔符不好确定异常的种类,通常是使用多个catch())
但当有多个异常时,只会打印出一个异常。


不能任何异常都用Exception,Exception是所有异常的父类,此时从上到下过滤的




【注意事项】
1. try块内抛出异常位置之后的代码将不会被执行
2. 如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到 JVM收到后中断程序----异常是按照类型来捕获的
3. try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获。如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,否则语法错误:
4. 可以通过一个catch捕获所有的异常,即多个异常,一次捕获(不推荐)
由于 Exception 类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常.
finally
finally用于资源的释放,是否出现异常finally都可以实现断后操作。close()可以写入finally当中(try后面也可以跟小括号,可实现输入)
注意:finally中的代码一定会执行的,一般在finally中进行一些资源清理的扫尾工作

关于finally的执行是最后,需要特殊记一下(理论上返回的值是10,但不是立刻返回的,底层将其修改为20)

一般我们不建议在 finally 中写 return (被编译器当做一个警告)
异常的处理流程
处理异常的顺序:从方法本身来处理异常,到谁调用此方法谁处理异常,最后交给JVM来处理异常
如果向上一直传递都没有合适的方法处理异常, 最终就会交给 JVM 处理, 程序就会异常终止(和我们最开始未使用 try catch 时是一样的)

【异常处理流程总结】
程序先执行 try 中的代码
如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
如果找到匹配的异常类型, 就会执行 catch 中的代码
如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
如果上层调用者也没有处理的了异常, 就继续向上传递.
一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.
3.throw 和 throws 的区别
throw是扔出一个程序,而throws是在方法声明的地方声明一个异常

4. 自定义异常类
Java 中虽然已经内置了丰富的异常类, 但是并不能完全表示实际开发中所遇到的一些异常,此时就需要维护符合我 们实际情况的异常结构.
以用户登录为例

此时我们想自定义用户账户和密码异常,可以定义一个异常类继承RuntimeException,并且重写
两个构造方法



继承RuntimeException和Exception的不同
继承RuntimeException,异常为编译时异常或者受查异常。
继承Exception,异常为运行时异常或者非受查异常。
解决方法如下
(1)自己用try和catch处理异常,同时注意不要再去main中去解决异常

(2)声明异常

注意事项
自定义异常通常会继承自 Exception 或者 RuntimeException
继承自 Exception 的异常默认是受查异常
继承自 RuntimeException 的异常默认是非受查异常