Java中的异常机制详解

关于IO流

IO流的概述

IO流是用来处理设备之间的数据传输,比如上传文件和下载文件;在上传和下载的时候,可能需要我们去处理一些比如下载中断的问题;这些数据在电脑中,是以文件的形式存在的;

IO流前奏

在学习IO流之前,我们需要学习异常机制和File类;

讲解IO流之前为什么先讲解异常和File类呢?

因为File表示的是IO流将来要操作的文件,所以我们需要学习File类;
而常见操作文件无非就是上传文件和下载文件,在这个操作的过程中可能出现问题,出现问题后,我们需要对对应的代码进行处理。所以我们需要学习异常;

异常机制

故事引入

张三是一个骑行爱好者,他在骑行遇到以下几种情况:
在骑行之前发现车闸松了,这个问题属于必须解决,不解决就不能出发;——编译期异常
在骑行途中遇到了自行车没气了,可以想办法解决,也可以不解决,推着自行车返回去;——错误
在骑行途中,如果车轱辘飞了,这个属于大问题,必须解决;——运行期异常

异常的概述
  • 在Java中,对于遇到的问题,有一个类来描述:Throwable,它是所有异常或者错误的父类;
  • 对于一般性的问题使用:Exception 类来描述;
  • 严重性问题或者错误使用:Error 类描述;
异常继承图解

在这里插入图片描述
在这里插入图片描述

运行期异常

交由Java默认处理运行期异常

代码示例:

public class MyTest {
    public static void main(String[] args) {
        int a=10;
        int b=0;
        System.out.println(a/b);
    }
}

输出结果:

Exception in thread “main” java.lang.ArithmeticException: / by zero
at demo4.MyTest.main(MyTest.java:7)

ArithmeticException:当出现异常的运算条件时,抛出此异常,它是运行期异常RuntimeException的子类;
我们事先并不知道出现此错误,只是说有可能出现这个错误,并没有自己解决;
这个运行期的异常,我们交给了Java默认处理,它的处理方式就是:遇到异常,Java虚拟机打印异常的堆栈信息,然后退出Java虚拟机,也就是后面的代码也不会执行了。如果你觉得这种默认的处理方式不够"友好",你可以自己去处理;

手动处理运行期异常
  • 遇到异常了,我么可以自己去提示异常信息,但是不退出Java虚拟机,怎么处理?使用关键字try、catch;
  • 语法:
try{
	有可能出现问题的代码 ;
}catch(异常名 变量名){
	针对问题的处理 ;
}

try里面放的是有可能出现问题的代码;
catch里面的参数是捕获何种类型的异常类名,即一旦出现这种异常处理的逻辑是什么;
catch括号里的参数没有出现你要捕获的异常类型,catch块里面的代码就不会执行;
有些异常我们无法预知,只是说有可能出现,我们就去捕获一下;

  • 代码示例:
public class MyTest {
    public static void main(String[] args) {
        int a=10;
        int b=0;
        try {
            System.out.println(a/b);
        } catch (ArithmeticException e) {
            System.out.println("除数为0了!");
        }

        System.out.println("下面的代码!");
    }
}

输出结果:

除数为0了!
下面的代码!
多种异常并列捕获
  • 语法:
try {
 可能出现问题的代码 ;
}catch(异常名1 变量名1){
 对异常的处理方式 ;
}catch (异常名2 变量名2){
  对异常的处理方式 ;
}....
  • 代码示例:
public class MyTest {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        int[] ints = new int[2];

        try {
            System.out.println(ints[3]);
            System.out.println(a / b);
        } catch (ArithmeticException e) {
            System.out.println("除数为0了!");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("角标越界了!");
        }

        System.out.println("下面的代码!");
    }
}

输出结果:

角标越界了!
下面的代码!
  • try-catch可以捕获多种异常,写多个catch并列捕获,算术异常、空指针异常、角标越界异常等等;如果你无法预知到底会出现什么错误,我们可以直接捕获一个大异常,也就是Exception异常;
public class MyTest {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        int[] ints = new int[2];
        
        try {
            System.out.println(ints[3]);
            System.out.println(a / b);
        } catch (Exception e) {
            System.out.println("代码出现问题了!");
        }
        System.out.println("下面的代码!");
    }
}
/*代码出现问题了!
下面的代码!*/
  • 但是不建议这样使用,为什么?

能明确的异常尽量明确,不能一捕了之,这样写不太规范;
并且你发现如果这时候再把算术异常等写在这个大异常后面,语法会报错,这是因为:捕获的多个异常之间有父子继承关系,父类异常放在最后,子类并列异常谁前谁后没关系;
try块里面尽量不要放一些废代码,比如定义变量等语句,会造成性能的浪费

代码示例:

public class MyTest {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        int[] ints = new int[2];
        ArrayList<Integer> list = new ArrayList<>();
        list = null;

        try {
            list.add(200);
            System.out.println(ints[3]);
            System.out.println(a / b);
        } catch (ArithmeticException e) {
            System.out.println("除数为0了!");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("角标越界了!");
        } catch (NullPointerException e) {
            System.out.println("空指针异常了!");
        } catch (Exception e) {
            System.out.println("代码出问题了!");
        }
        System.out.println("下面的代码!");
        /*空指针异常了!
        下面的代码!*/
    }
}
JDK1.7之后的新语法
  • JDK 1.7之后,支持使用catch( 异常 1 | 异常2 |……),这种写法就是简化了代码,将多个异常处理逻辑写在一个catch块里面,但是不能明确异常,你也可以在catch里面使用关键字instanceof去判断该异常的类型,但是这样就显得没必要了;
  • 注意:
    1、处理方式是一致的,(实际开发中,好多时候可能就是针对同类型的问题,给出同一个处理);
    2、多个异常间必须是平级关系;
  • 语法:
try {
 可能出现问题的代码 ;
} catch(异常名1 | 异常名2 | ....   变量名){
	对异常的处理方案 ;
}

代码示例:

public class MyTest {
    public static void main(String[] args) {
        int a = 10;

        int b = 0;
        int[] ints = new int[2];
        ArrayList<Integer> list = new ArrayList<>();
        list = null;

        try {
            list.add(200);
            System.out.println(ints[3]);
            System.out.println(a / b);
        } catch (ArithmeticException | ArrayIndexOutOfBoundsException | NullPointerException e) {
            if (e instanceof ArithmeticException) {
                System.out.println("除数为0了!");
            } else if (e instanceof ArrayIndexOutOfBoundsException) {
                System.out.println("角标越界了!");
            } else if (e instanceof NullPointerException) {
                System.out.println("空指针异常了!");
            }
        }
        System.out.println("下面的代码!");
        /*空指针异常了!
        下面的代码!*/
    }
}
finally块
  • try-catch-finally 的完整语法是:
try	{
	可能出现问题的代码 ;
}catch(异常名 变量名){
	针对问题的处理 ;
}finally{
	释放资源;
}
  • 不管try里面的代码有没有遇到异常,finally里面的代码一定会执行,一般我们会把一些善后收尾的代码写在里面,比如释放资源;finally块不是必须要写的,可以写也可以不写,根据自己的需求来设计程序;

注意:在善后收尾的时候,做一下非空判断,否则就会报空指针异常的错误;

public class MyTest {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        int[] ints = new int[2];
        Scanner sc = new Scanner(System.in);
        ArrayList<Integer> list = new ArrayList<>();
        list = null;

        try {
            list.add(200);
            System.out.println(ints[3]);
            System.out.println(a / b);
        } catch (ArithmeticException | ArrayIndexOutOfBoundsException | NullPointerException e) {
            if (e instanceof ArithmeticException) {
                System.out.println("除数为0了!");
            } else if (e instanceof ArrayIndexOutOfBoundsException) {
                System.out.println("角标越界了!");
            } else if (e instanceof NullPointerException) {
                System.out.println("空指针异常了!");
            }
        } finally {
            if (sc != null) {
                sc.close();
            }
        }
        System.out.println("下面的代码!");
        /*空指针异常了!
        下面的代码!*/
    }
}
finally块与return关键字

代码示例1:

public class MyTest {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        try {
            System.out.println(a / b);
        } catch (ArithmeticException e) {
            System.out.println("除数为0了!");
        } finally {
            System.out.println("你会执行吗?");
        }
        System.out.println("下面的代码!");
        /*除数为0了!
        你会执行吗?
        下面的代码!*/
    }
}

代码示例2:

public class MyTest {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        try {
            System.out.println(a / b);
        } catch (ArithmeticException e) {
            System.out.println("除数为0了!");
            return;
        } finally {
            System.out.println("你会执行吗?");
        }
        System.out.println("下面的代码!");
        /*除数为0了!
        你会执行吗?*/
    }
}

代码示例3:

public class MyTest {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        try {
            System.out.println(a / b);
        } catch (ArithmeticException e) {
            System.out.println("除数为0了!");
            System.exit(0);//程序正常退出
        } finally {
            System.out.println("你会执行吗?");
        }
        System.out.println("下面的代码!");
        /*除数为0了!*/
    }
}
  • 由上面的代码可以知道:

即使存在return关键字,finally块里面的代码仍然执行,并且是在return关键字之前执行,否则的话就不会输出了;
如果是程序正常退出,终止当前正在运行的 Java 虚拟机,finally块里面的代码也就不会执行了

编译期异常

  • 是指非RuntimeException及其子类,编译期异常必须处理,否则程序无法运行;
  • 代码示例:
public class MyTest {
    public static void main(String[] args) throws ParseException {
        String sDate = "2012-08-23";
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = format.parse(sDate);
        System.out.println(date);
        //Thu Aug 23 00:00:00 CST 2012
    }
}

在学习SimpleDateFormat类的parse()的时候,我们遇到一个编译期的异常,当时处理的方法是使用IDEA的纠错键直接抛出这个错误;

编译期异常的处理的两种方式

在这里插入图片描述

  • 上面的程序使用向上抛出和自己手动处理的区别:

向上抛出

public class MyTest {
    public static void main(String[] args) throws ParseException {
        String sDate = "2012-08-==23";
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = format.parse(sDate);
        System.out.println(date);
        /*Exception in thread "main" java.text.ParseException: Unparseable date: "2012-08-==23"
        at java.text.DateFormat.parse(DateFormat.java:366)
        at org.westos.demo4.MyTest.main(MyTest.java:11)*/
    }
}

手动解决

public class MyTest {
    public static void main(String[] args) {
        String sDate = "2012-08-==23";
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        try {
            date = format.parse(sDate);
        } catch (ParseException e) {
            System.out.println("解析异常");
        }
        System.out.println(date);
        /*解析异常
        null*/
    }
}
处理异常的时机选择
  • 看一段代码:
public class MyTest {
    public static void main(String[] args) throws ParseException {
        String sDate = "2012-08-==23";
        dateParse(sDate);
        /*解析异常
        null*/
    }

    private static void dateParse(String sDate) throws ParseException {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        date = format.parse(sDate);
        System.out.println(date);
    }
}
  • 我们把错误向上抛出,一层甩给一层,上面的代码里面,自定义方法抛给main(),如果main()还不想处理,仍然向上抛出,这时候只能是Java虚拟机来使用默认的方式进行处理;
  • 一般来说,到了main()就不再继续抛出了,而是选择自己捕获处理,因为自己处理的方式友好;
  • 还有就是假如我们上面的解析日期的方法写在工具类里面,这时候异常选择向上抛出,谁调用,谁解决,因为别人的处理异常的方式有可能和你的不一样;
  • 对于异常的处理,不要做空处理(也就是catch块里面的代码不能为空),哪怕是一句简单的提示语句;
异常里面的几个方法

String getMessage()——返回此 throwable 的详细消息字符串;
StackTraceElement[] getStackTrace() ——提供编程访问由 printStackTrace() 输出的堆栈跟踪信息;
void printStackTrace()——将此 throwable 及其追踪输出至标准错误流;虚拟机默认调用该方法打印堆栈信息;

public class MyTest {
    public static void main(String[] args) {
        String sDate = "2012-08-==23";
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        try {
            date = format.parse(sDate);
        } catch (ParseException e) {
            System.out.println(e.getMessage());
            //Unparseable date: "2012-08-==23"
            System.out.println(e.getStackTrace());
            //[Ljava.lang.StackTraceElement;@60e53b93
            e.printStackTrace();
            /*java.text.ParseException: Unparseable date: "2012-08-==23"
            at java.text.DateFormat.parse(DateFormat.java:366)
            at org.westos.demo4.MyTest.main(MyTest.java:13)*/
        }
        System.out.println(date);
    }
}

关键字throw与throws的区别

  • 相同点:两者都可以进行异常的抛出;
  • 不同点:throws用在方法的声明上,throw用在方法的内部;
    throws抛出来的是一个类,而throw则是new出来的对象;

代码示例:(throws之前已经遇到过了,这里练习一下throw的用法)

public class MyTest {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入第一个数字:");
        int one = sc.nextInt();
        System.out.println("请输入第二个数字:");
        int tow = sc.nextInt();
        if (tow == 0) {
            throw new ArithmeticException("除数为0了~");
        } else {
            System.out.println(one / tow);
        }
    }
}

在这里插入图片描述

  • 其实在学习ArrayList里面的内部迭代的时候,就已经接触到了throw关键字,内部迭代是ArrayList类里面的一个内部类,他的很多方法使用到了throw关键字;
    在这里插入图片描述

自定义异常

  • 在我们进行实际的开发时,会遇到各种异常,Java给我们提供的异常类,并不能完全描述我们遇到的异常;比如你在做银行类的项目,可能遇到余额不足的问题,这时候程序检测到这个异常,就需要抛出异常,然程序终止;Java没有提供给我们这样的异常,还有诸如人脸识别的异常、指纹识别的异常等,业务需要这些异常,我们可以自定义它们;
  • 如何把自定义异常纳入异常体系当中?自定义的异常都需要继承运行期异常类;
  • 代码示例:
---测试类:
public class MyTest {
    public static void main(String[] args) {
        //输入成绩

        System.out.println("请输入你的成绩:");
        Scanner sc = new Scanner(System.in);
        int score = sc.nextInt();
        if (score < 0 || score > 100) {
            throw new ScoreLegalException("成绩不合格!");
        } else {
            System.out.println("你的成绩合格!");
        }
    }
}
----异常类:
public class ScoreLegalException extends RuntimeException{
    public ScoreLegalException(String message) {
        super(message);
    }
}

在这里插入图片描述

使用异常需要注意的问题

  • 子类在重写父类的方法时,如果父类没有抛出异常,子类也不能抛出异常,这时候,一旦有异常,你就内部消化,使用try-catch块处理;
public class Fu {
    public void show() {
        String sDate = "2012-08-==23";
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        try {
            date = format.parse(sDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        System.out.println(date);
    }

}

class Zi extends Fu{
    @Override
    public void show() {
        System.out.println("这是子类~");
    }
}
  • 子类不能抛出父类没有抛出过的异常;
  • 子类抛出的异常必须是和父类异常一样或者是父类异常的子类,不能是基类;
    在这里插入图片描述
  • 父类抛出异常,子类可以不抛出;
public class Fu {
    public void show() throws ParseException {
        String sDate = "2012-08-==23";
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        date = format.parse(sDate);
        System.out.println(date);
    }
}

class Zi extends Fu{
    @Override
    public void show() {
        try {
            super.show();
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值