写在前面:
视频是什么东西,有看文档精彩吗?
视频是什么东西,有看文档速度快吗?
视频是什么东西,有看文档效率高吗?
1. 演示
诸小亮:异常,就是不正常,是在程序运行时出现的各种错误,比如:
public class Demo{
public static void main(String[] args){
int num = 2 / 0;//0不能作为除数,所以会产生异常
System.out.println("程序结束。。。。");
}
}
结果:
张小飞:之前也遇到过很多次了,可以具体解释一下这输出都是什么意思吗?
诸小亮:这是必须的,上图中可以分为3个部分:
- ArithmeticException:是异常类型,
- / by zero:是异常信息
- (Demo.java:5) 是异常出现的位置
张小飞:明白了
诸小亮:另外,出现异常后程序会终止,所以 System.out.println(“程序结束。。。。”); 不运行
张小飞:噢,你不说还真没注意,我记下了
2. 介绍
诸小亮:在 Java 中,用 **Throwable类 **对程序运行时的不正常情况进行了描述
张小飞:不对啊,上面的异常不是 ArithmeticException吗?
诸小亮:你说的没错,但是 Java 中所有异常都是 Throwable 的子类,具体分为两大类:
- Exception:可以通过修改代码进行处理的不正常现象,比如:ArithmeticException
- Error:一般是系统底层问题,无法听过代码处理,比如:内存溢出
我们主要学习的是如何处理 Exception 这类异常
3. 处理异常
张小飞:那么,如何处理异常呢?
诸小亮:可以使用 try catch 去处理
1. try catch
诸小亮:通过 try catch 代码块,捕获指定的异常并处理,比如:
public static void main(String[] args){
try {
int num = 2 / 0;
}catch (Exception e){//catch 里面捕获指定的异常类型
//TODO 这里书写一些代码处理这种异常情况
//e.getMessage():获取异常信息
System.out.println("发现错误了。。。。" + e.getMessage());
}
System.out.println("程序结束。。。。");
}
结果:,程序可以正常结束
张小飞:这句代码 ‘catch (Exception e)’,表示 Exception 的子类型,都可以捕获到吗?
诸小亮:是的,然后就会执行 catch 中的代码
张小飞:您看我这样写行不行
诸小亮:当然没问题,但是这样的话,如果代码中有其他异常,就不能catch到,比如:
public static void main(String[] args){
try {
int[] arr = new int[2];
System.out.println(arr[2]);
int num = 2 / 0;
}catch (ArithmeticException e){
System.out.println("发现算术异常。。。。" + e.getMessage());
}
System.out.println("程序结束。。。。");
}
结果:
解释:因为我们 catch 的是 ArithmeticException ,却出现了ArrayIndexOutOfBoundsException异常,所以并没有走 catch中的代码
张小飞:原来是这样,我明白了
2. throws
诸小亮:下面我们看看——throws关键字
张小飞:这个是做什么用的?
1. 演示
诸小亮;如果不想用 try catch 处理异常,可以通过 throws 关键字 声明异常,比如:
//throws 写在方法上,用来声明异常
public static int div(int a, int b) throws Exception{
return a / b;
}
张小飞:这是???
诸小亮:就是提醒调用者,这个方法可能发生异常
张小飞:您的意思是,在调用这个方法的时候需要用 try catch?
诸小亮:是的,谁调用谁处理,这时调用这个方法的地方就应该:
public static void main(String[] args){
try{
div(2,0);
}catch (Exception e){
//这里处理异常
}
}
张小飞:如果调用者也不想处理呢?
诸小亮:那就继续 throws,比如:
张小飞:这时候,如果异常,是谁处理?
诸小亮:就是 JVM 自己处理了
2. 多种异常
诸小亮:另外,throws 可以声明多种异常,但是要逗号分隔,比如:
张小飞:这样的话,catch 那里怎么办呢?
诸小亮:如果声明多个异常,catch 也应该有多个,保证每种异常都能被 cath 到并处理,比如:
public static void main(String[] args){
try{
div(2,0);
}catch (ArrayIndexOutOfBoundsException e){
}catch (ArithmeticException e){
}
}
//throws 写在方法上,用来声明异常
public static int div(int a, int b) throws ArrayIndexOutOfBoundsException, ArithmeticException{
return a / b;
}
张小飞:原来如此
诸小亮:还可以优化
张小飞:哦?怎么优化?
诸小亮:因为上面的异常都是 Excepetion 的子类,所以直接 catch 一个 Excetion 就行了,比如:
张小飞:明白了
3. 常用方法
诸小亮:接着我们说说 Exception 中的常用方法
1. getMessage()
诸小亮:getMessage() ,用来获取异常信息,已经演示过,就不在多说了
张小飞:好的
2. toString
诸小亮:toString(),把异常转换为字符串输出,你可以尝试一下
张小飞:我试过了,您看看
结果:
3. printStackTrace
诸小亮:printStackTrace(),打印异常的堆栈信息,这个功能比较有用
张小飞:是这样吗?比如:
结果:
诸小亮:没错,上图中,展示出了具体报错的位置
4. throw关键字
诸小亮:接下来,我们看看——throw关键字
张小飞:咦,这个不是刚才说过来吗?
诸小亮:看清楚,刚才说的是 throws,现在是 throw,不一样
张小飞:哦哦,这个 throw 又是什么意思?
诸小亮:throw,用来抛出指定的异常,比如:
public static void main(String[] args){
try {
int num = div(2,0);
} catch (Exception e) {
e.printStackTrace();
}
}
public static int div(int a, int b) throws Exception{
if(b == 0){
//通过 throw 关键字,抛出指定的异常,
//当程序运行到这里后中断,下面的 return a/b; 不再运行
throw new ArithmeticException("小学没毕业吗?除数不能是0---"+b);
}
return a / b;
}
结果:
张小飞:原来还可以这样,不过这有什么用呢?不用 throw 也会自动抛出异常啊?
诸小亮:这样做,有两个好处,第一,可以自定义异常信息
张小飞:噢,确实,我们自定义的信息更加清楚一些,第二呢?
诸小亮:第二,可以抛出其他异常,比如:
张小飞:明白了
诸小亮:另外,如果不知道应该抛出什么异常,可以使用Exception,比如:
张小飞:我试了试,抛出这个是有问题的,您看看
诸小亮:因为,只有 Throwable 类型的对象才能被 throw 抛出
张小飞:原来如此
诸小亮:来,我们总结一下 throw 跟 throws 的区别
- 位置不同
- throws:用在方法上,其后指定异常的种类可以有多个
- throw:用在方法内,其后指定具体的异常对象
- 功能不同
- throws:声明异常,告诉调用者可能会出现问题
- throw:抛出异常,程序运行到此处时就中断了
张小飞:明白了,不过,一般什么时候用 throw 呢?
诸小亮:一般用来校验参数,如果参数不对抛出异常
5. RuntimeException
诸小亮:下面我们说说——RuntimeException
张小飞:这个是什么意思?
“RuntimeException,运行时异常,即:程序运行时抛出的异常,这是 Exception 比较特殊的一个儿子”
“哦?怎么个特殊法?”
“一般情况下,只是在代码中用 throw 抛出异常后,会编译失败,比如:”
“这时候需要在方法上使用 throws 声明异常,比如:”
张小飞:不对吧,刚才我没有用 throws ,也没有编译失败啊
诸小亮:因为 IllegalArgumentException 是 RuntimeException 的子类
- 使用 throw 抛出运行时异常,不需要在方法上声明
张小飞:原来如此
张小飞:那,ArithmeticException ArrayIndexOutOfBoundsException,也是运行时异常?
诸小亮:是的
张小飞:为什么这样呢?使用 throws 后,调用者就需要用 try catch 处理,这样不是更好吗?
如果不声明的话,调用者就可能忘记使用 try catch,这样程序容易报错啊
诸小亮:你说的没错,不过出现RuntimeException,一般都是调用者写的代码有问题
这样做的目的:就是停止程序,让调用者修改代码
张小飞:原来是这样
诸小亮:因为 RuntimeException,所以可以把 Exception 分为两类——受检 和 非受检异常
张小飞:这是什么意思?
诸小亮:给你解释一下
- 受检异常:在编译的时,要强制检查的异常,这种异常需要去通过 try catch 来进行捕获或者使用throws在方法上声明,否则无法通过编译的
- 非受检异常:在编译的时,不需要去强制检查的异常,这种异常可以不需要显示去捕获或者抛出
张小飞:那么,RuntimeException 就是非受检异常?
诸小亮:没错,在 Exception 下,RuntimeException就是非受检异常,其他的都是受检异常
张小飞:明白了
6. 自定义异常
张小飞:自定义异常——是自己定义一个类,来描述某种异常吗?
诸小亮:是的
张小飞:Java已经提供 Exception,为什么还要自定义异常呢?
诸小亮:Java 虽然用 Exception 描述程序运行时的不正常情况,但是现实中的异常多种多样
其内部的异常类型(Exception的子类)可能不满足实际需求,这时候就要自定义异常
1. 演示
诸小亮:比如,定义一个方法做除法运算,如果除数小于等于0,就抛出一个除数是负数的异常
目前 Java 中没有定义除数是负数的异常,这时候就需要自定义
示例:
//自定义异常,注意:一般使用自定义异常都是起一些容易理解的名字,这样阅读性更好
class FuShuException extends Exception{
}
public class Demo{
public static void main(String[] args) {
try {
//除数传一个负数
int num = div(2,-1);
} catch (Exception e) {
e.printStackTrace();
}
}
public static int div(int a, int b) throws FuShuException {
if(b < 0){
throw new FuShuException();
}
if(b == 0){
throw new ArithmeticException("除数不能等于0。。。。。");
}
return a / b;
}
}
张小飞:没有必要自定义吧,这样子岂不是更好,您看看
诸小亮:你说的不错,但有时候需要针对不同的异常,做不同的处理,比如:
张小飞:明白了
2. 自定义异常信息
张小飞:您定义的这个 FuShuException ,不能自定义异常信息啊,IllegalArgumentException是可以的
他们不都是 Exception 的子类吗?
诸小亮:这时因为,FuShuException少一个构造函数,比如:
FuShuException(String message){
super(message);//调用 super,设置异常信息
}
张小飞:原来如此,我来试试
3. 继承RuntimeException
诸小亮:另外,目前 FuShuException 继承 Exception,必须在方法上用 throws 声明。。。。
张小飞:这个简单,直接继承 RuntimeException 就行了,比如:
诸小亮:看来你已经充分了解:非受检异常
7. finally
诸小亮:接着来看看——finally代码块
张小飞:这又是干什么用的?
1. 演示
诸小亮:finally 是配合 try catch 使用的一种特殊的代码块,比如:
public static void main(String[] args) {
try{
int num = 2 /0 ;
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("finally中的代码执行。。。。");
}
}
结果:
诸小亮:你有没有什么发现?
张小飞:finally 中的代码,在 catch 处理异常后,正常执行
诸小亮:说得对,这就是它最重要的地方
2. finally中的代码一定会执行
诸小亮:其实,finally代码块最重要的意义就是:它里面的代码一定会执行
张小飞:一定会执行?
“没错,即使在 catch 中使用 return 语句,finally也会执行,比如:”
public static void main(String[] args) {
try{
int num = 2 /0 ;
}catch (Exception e){
e.printStackTrace();
return;//catch中加上return语句,finally中的代码依然执行
}finally {
System.out.println("finally中的代码执行。。。。");
}
}
结果:
“有没有什么例外情况?”
“凡事都有例外,如果是退出 JVM 虚拟机,finally不执行,比如:”
结果:
3. return 和 finally
诸小亮:问你个问题,return后的语句和finally中的语句,哪个先执行?
张小飞:额…,稍等我验证一下
public static void main(String[] args) {
show();
}
private static int show() {
try{
int num = 2 /0 ;
}catch (Exception e){
e.printStackTrace();
//return 后面调用一个方法
return test();
}finally {
System.out.println("finally中的代码执行。。。。");
}
return 1;
}
private static int test() {
System.out.println("返回一个0.....");
return 0;
}
结果:
张小飞:这跟我预想的结果不太一样,本来以为 finally 中的代码先执行呢?
诸小亮:它的执行过程是这样的:
- 先执行 test 方法,拿到一个返回值 0
- 再执行 finally 中的代码
- 最后执行 return,把 0 给返回
张小飞:明白了
8. try catch finally 的组合方式
诸小亮:下面给你介绍几种 try catch finally 常用的组合方式
“第一种:try catch 单独使用,这种最常见”
“第二种:一个try,多个catch”
“第三种:try 和 finally,没有catch”
张小飞:“这样也行?产生的异常时被谁处理了?”
诸小亮:“因为没有catch,所以会自动把异常抛给调用者,但是 finally 中的代码一定会执行”
9. 方法覆盖时的异常
诸小亮:在做继承的时候,有几点需要注意
第一:如果父类方法或父接口没有 throws 异常,那么子类复写的方法也不能 throws 异常
class HeroException extends Exception{
}
class YaseException extends HeroException{
}
class DajiException extends Exception{
}
interface People{
void run();
}
class Person{
void eat(){
}
}
class child extends Person implements People{
//复写父类的方法
@Override
void eat()throws HeroException {
System.out.println("吃饭。。。。。");
}
//实现父接口的方法
@Override
public void run()throws HeroException {
System.out.println("走。。。。。");
}
}
张小飞:上面的代码,编译失败了
第二:子类方法声明的异常类型,只能是父类声明的异常类型或者其子类型,比如:
张小飞:您的意思是,子类复写后 run 方法后,声明的异常只能是 HeroException 或 其子类型?
诸小亮:是的,准确来说有 3 种方式可以选择
第一种:声明 HeroException
第二种:声明 HeroException 的子类型
第三种:不声明异常
张小飞:如果是声明其他类型的异常呢?
诸小亮:那么会直接编译失败,比如:
张小飞:如果父类方法没声明异常呢?
诸小亮:这是最特殊的一种情况,子类复写方法后,可以声明 RuntimeException 或 Error ,比如: