一、异常机制 1.异常的概念 掌握
2.异常的本质 掌握
3.异常的分类 掌握
二、异常处理 1.try-catch-finally 掌握
2.throws声明异常 掌握
3.try-witch-resource新特性 掌握
4.自定义异常 了解
三、学会使用百度处理异常 1.处理异常的步骤 掌握
2.百度:超级搜索 掌握
四、IDEA调试功能 如何使用IDEA调试功能进行调试 掌握

异常

一、异常机制

1.异常的概念

当程序运行出现意外情况时,如:用户输入错误、除数为0、需要处理的文件不存在、数组下标越界等,Java异常机制会自动生成一个Exception对象来通知程序,从而可以使程序中的异常处理代码和正常业务代码分离,提供更好地可读性和提高程序的健壮性。

2.异常的本质

所谓异常,就是程序在出现问题时,程序安全退出、异常机制处理完后依然可以正确地执行完。

  1. package com.sundegan;
  2. public class Test {
  3. public static void main(String[] args) {
  4. System.out.println("step1");
  5. int i = 1/0;
  6. System.out.println("step2");
  7. }
  8. }
  9. //step1
  10. //Exception in thread "main" java.lang.ArithmeticException: / by zero
  11. // at com.sundegan.Test.main(Test.java:6)

程序出错后直接停止运行,”step2”不能继续执行。

  1. public class Test {
  2. public static void main(String[] args) {
  3. System.out.println("step1");
  4. try {
  5. int i = 1/0;
  6. }catch (Exception e){
  7. e.printStackTrace();//打印异常信息
  8. }
  9. System.out.println("step2");
  10. }
  11. }
  12. //step1
  13. //java.lang.ArithmeticException: / by zero
  14. // at com.sundegan.Test.main(Test.java:7)
  15. //step2

程序在出现错误时,通过异常处理处理完后,依然可以往下执行。
Java是采用面向对象的方式来处理异常的,处理过程:
1)抛出异常:在执行一个方法时,如果发生异常,则这个方法生成代表该异常的一个对象,停止当前程序的执行,把异常对象提交给JRE。
2)捕获异常:JRE得到该异常对象后,寻找对应的代码来处理该异常。JRE在方法的调用栈中查找,从生成异常的方法开始回溯,直到找到相应的异常处理代码为止。

3.异常的分类

Java异常机制中引入了很多用来描述和处理异常的类,称为异常类,异常类定义中包含了该类异常的信息和对异常进行处理的方法,不同类型的异常用不同的Java类来表示,所有异常的跟类为java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception。Java异常类结构层次图如下所示:
Day10 - 图1

3.1 Error

Error错误,一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误等,这种错误无法恢复或不可能捕获,将导致应用程序中断。通常应用程序无法处理这些错误,因此不应该试图使用catch来捕获Error对象。在定义方法时,也无须在其throws子句中声明该方法可能抛出Error及其任何子类。

3.2 Exception

异常是程序自身能处理的,如空指针异常、数组索引越界异常、类型转化异常、算术异常。Java将异常分为两种,Checked异常和Runtime异常,Java认为Checked异常都是可以在编译阶段被处理的异常,所以它强制程序处理所有的Checked异常;而Runtime异常则无须处理。

3.3 RuntimeException

Runtime运行时异常,如被0除、数组下标越界、空指针等,其产生比较频繁,处理麻烦,如果显式地声明或捕获将会对程序可读性和运行效率影响很大,因此由系统自动检测并交给缺省的异常处理程序(用户不用处理)。这类异常通常由编程错误导致,在编程时并不要求通过异常处理机制来处理这类异常,通常通过增加逻辑处理来避免这些异常。

  1. public class Test {
  2. public static void main(String[] args) {
  3. //除数为0异常
  4. int b = 0;
  5. if(b != 0){
  6. c = a / b;
  7. }
  8. //空指针异常
  9. String str = null;
  10. if(str != null){
  11. System.out.println(str.charAt(0));
  12. }
  13. }
  14. }
  1. class Animal{
  2. }
  3. class Dog extends Animal{
  4. }
  5. class Cat extends Animal{
  6. }
  7. public class Test {
  8. public static void main(String[] args) {
  9. //ClassCastException异常
  10. Animal a = new Dog();
  11. //Cat c = (Cat) a;这里不能强制类型转换
  12. if(a instanceof Cat){
  13. Cat c = (Cat) a;
  14. }
  15. }
  16. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. //数组索引越界异常
  4. int[] a = new int[5];
  5. //System.out.println(a[5]);只能到4
  6. int index = 5;
  7. if(index < 5 && index >= 0){
  8. System.out.println(a[index]);
  9. }
  10. //数字格式化异常
  11. String str = "123abc";
  12. System.out.println(Integer.parseInt(str));
  13. //引入正则表达式判断是否为数字
  14. Pattern p = Pattern.compile("^\\d+$");
  15. Matcher m = p.matcher(str);
  16. if(m.matches()){//如果str都为数字才会转换
  17. System.out.println(Integer.parseInt(str));
  18. }
  19. }
  20. }

3.4 CheckedException

所有不是RuntimeException的异常统称为CheckedException,又被称为“已检查异常”,如IOException、SQLException、用户自定义的异常等。这类异常在编译时必须被处理,否则无法通过编译。

二、异常处理

1.异常的处理方式之一:捕获异常

捕获异常通过使用三个关键字来实现的:try—catch—finally。使用try来执行一段程序,如果出现异常,系统抛出一个异常,可以通过它的类型来捕获(catch)处理。最后,通过finally为异常处理提供一个统一的出口,不管有没有出现异常,finally所指定的代码都会被执行。catch语句可有多条,分别捕获不同类型的异常,finally语句只能有一条,根据需要可有可无。

  1. try{
  2. 语句1
  3. 语句2
  4. ......
  5. }catch (Exception1 e){
  6. 处理代码;
  7. }catch (Exception2 e){
  8. 处理代码;
  9. }finally {
  10. 资源回收语句;
  11. }

如果需要捕获的异常间存在父子关系,则子类异常放前面捕获,父类异常放后面捕获。
try—catch—finally语句块的执行过程:程序首先执行可能发生异常的try语句块,如果try语句块没有出现异常则正常向下执行,最后跳到finally语句块执行。如果try语句块出现异常,则中断执行并根据异常类型跳至相应的catch语句块处理,catch语句块执行完再执行finally语句块。
注:

  • 即使try和catch语句块中有return语句,finally语句块也会执行,在执行完finally语句块后跳回return语句返回。
  • finally语句块只有一种情况不会执行,在finally语句之前程序遇到了System.exit(0)退出。

技巧:选中需要处理代码,IDEA中使用Ctrl+Alt+t快捷键自动增加try—catch语句块。

  1. public class Test {
  2. public static void main(String[] args) {
  3. try {
  4. FileReader reader = new FileReader("d:/a.txt");//创建一个a.txt文件的索引
  5. char c = (char)reader.read();//从文件中读一个字符,注意返回的是整数,因此要强制转型
  6. char c1 = (char)reader.read();
  7. System.out.println("" + c + c1);
  8. //可能存在的异常:文件不存在或文件为空
  9. } catch (FileNotFoundException e){//文件不存在
  10. e.printStackTrace();
  11. } catch (IOException e) {//文件读写错误
  12. e.printStackTrace();
  13. } finally {
  14. reader.close();
  15. //关闭文件,但有问题,这里reader变量是try语句块中的局部变量,
  16. //在finally语句块里无法找到,因此需把reader变量定义在try和catch语句块之外
  17. //因为这里是关闭文件,又可能出现异常,需要处理
  18. }
  19. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. FileReader reader = null;//先创建一个空的文件索引
  4. try {
  5. reader = new FileReader("d:/a.txt");//让该文件索引指向一个文件
  6. char c = (char) reader.read();//从文件中读一个字符,注意返回的是整数,因此要强制转型
  7. char c1 = (char) reader.read();
  8. System.out.println("" + c + c1);
  9. //可能存在的异常:文件不存在或文件为空
  10. } catch (FileNotFoundException e) {
  11. e.printStackTrace();
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. } finally {
  15. //此部分一定会执行
  16. //关闭文件,但有问题,这里reader变量是try语句块中的局部变量,
  17. //在finally语句块里无法找到,因此需把reader变量定义在try和catch语句块之外
  18. //因为这里是关闭文件,又可能出现异常,需要处理
  19. try {
  20. if (reader != null) {
  21. reader.close();
  22. }
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. }
  28. }

上面的异常处理是IO流中非常常见的写法,需要掌握。

2.异常的处理方式之二:声明异常(throws语句)

在CheckedException异常发生时,可以把异常throws出去。
在方法中使用try—catch—finally语句时,由这个方法处理这个异常。但有些时候当前方法并不需要处理所发生的异常,而是向上传递给调用它的方法处理。
如果一个方法可能产生某种异常,但是并不能确定如何处理这种异常,则应根据异常规范在方法的首部声明该方法可能抛出的异常。如果一个方法抛出多个已检查异常,则必须在方法首部列出所有的异常,之间以逗号隔开。

  1. public class Test {
  2. //往调用main方法的虚拟机抛出异常,给虚拟机处理
  3. public static void main(String[] args) /*throws Exception*/{
  4. try{
  5. readFile("d:/a.txt");
  6. }catch (Exception e){
  7. e.printStackTrace();
  8. }
  9. }
  10. //往调用该方法的对象抛出异常
  11. public static void readFile(String path) throws Exception{
  12. FileReader reader = null;
  13. try {
  14. reader = new FileReader(path);
  15. char c = (char) reader.read();
  16. char c1 = (char) reader.read();
  17. System.out.println("" + c + c1);
  18. } finally {
  19. try {
  20. if (reader != null) {
  21. reader.close();
  22. }
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. }
  28. }

使用抛出异常时,并不是对异常不处理,而是交给更上层的调用者处理,常见的是一层一层往外抛,最后交给顶层的框架来处理。

3.异常的处理方式之三:自动关闭

Java中,JVM的垃圾回收机制可以对内部资源实现自动回收,给开发者带来了极大的便利。但是JVM对外部的资源(调用了底层操作系统打开的资源)的引用却无法自动回收,例如数据库连接、网络链接以及输入输出IO流等。这些资源就需要我们手动的去关闭,不然会导致外部资源泄漏,比如连接池溢出、文件被异常占用等。
JDK7之后,实现了try—catch—resource,只使用try—catch就可以自动关闭我们打开的外部资源。但这只是一种语法糖,在编译时仍然会转化为try—catch—finally形式。

  1. public class Test {
  2. public static void main(String[] args) {
  3. //在try语句块的括号里说明打开的资源,编译器会帮我们加finally语句块关闭这些资源
  4. //本质上和try——catch——finally没什么区别,只是编译器帮我们加上了关闭资源的代码
  5. try(FileReader reader = new FileReader("d:/a.txt");){
  6. char c = (char) reader.read();
  7. char c1 = (char) reader.read();
  8. System.out.println("" + c + c1);
  9. }catch (Exception e){
  10. e.printStackTrace();
  11. }
  12. }
  13. }

4.自定义异常

  • 在程序中可能会遇到JDK提供的标准异常类无法充分描述我们想要表达的问题,这种情况下可以创建自己的异常类,即自定义异常。
  • 自定义异常只需从Exception类或者它的子类派生出一个类即可。
  • 自定义异常如果继承Exception,则为受检查异常,必须进行处理;如果不想处理,可以继承RuntimeException类。
  • 习惯上,自定义异常类应该包含两个构造器:一个是默认的构造器,一个是带有详细信息的构造器。
  • 在开发项目过程中,其实不需要自己自定义异常,如果是开发框架,就需要自定义异常,如在Spring框架中定义了许多自定义异常,有着自己的一整套异常体系,对应不同的异常情况。

    三、利用百度解决异常

    在学习和开发过程中,我们会遇到很多的异常,当遇到异常的时候不要慌,耐心解决掉这些异常才能有所提高,独立解决掉50次异常之后,我们以后遇到的异常基本都能自己解决。

    1.解决异常的步骤

  • 细心查看异常信息,确定异常种类和出错代码的位置;

  • 确定上下文中的关键词;
  • 拷贝异常信息到百度,查看相关帖子,寻找解决思路;
  • 前面无法搞定,寻找同事朋友的帮忙;
  • 最后,还是无法搞定再请示领导。

注:遇到异常不要马上搬救兵,一定要自己尝试解决,如果花了一些时间解决不掉一定要及时请教别人。一、只会找别人帮忙,次数多了别人会不耐烦,只是把别人当苦力。二、失去了自我提升的机会,自己解决掉一个异常,以后就有能力解决掉这一类异常,解决一类异常能大大提高自己的能力。

2.百度搜索的方法

使用百度/goole的关键是:关键词的确认。

  • 寻找问题本身的关键词(名词)
  • 寻找问题上下文的关键词(名词)
  • 尽量细致地使用这些关键词进行搜索
  • 如果寻找不到,减少关键词,扩大搜索范围

    四、IDEA的调试功能

    https://www.bilibili.com/video/BV1oy4y1H7R6?p=146

    1.断点(breakpoint)

    IDEA提供了非常方便的程序调试功能,进行调试的核心是设置断点。程序执行到断点时,暂停执行,这样我们可以详细查看此时程序的运行情况。

  • 在行号后面单击即可增加断点

  • 在断点上单击即可取消断点

    2.进入调试视图

    image.png
    image.png
    image.png
    image.png
中文名 英文名 图标 作用
单步调试:遇到方法跳过 step over image.png 若当前执行的是一个方法,则会把这个方法当作整体一步执行完,不会进入方法的内部
单步调试:遇到自定义方法进入 step into image.png 若当前执行的是一个自定义方法,则会进入这个方法的内部。JDK内部方法不会进入。
强制进入方法内部 force step into image.png 任何方法都能进入
跳出方法 step out image.png 当单步执行到子方法内时,用step out方法就可以执行完子方法余下部分,并返回到上一层方法
执行到光标处 run to cursor image.png 一直执行,到光标处停止,用在循环内部时,点击一次就执行一个循环
删除当前帧 drop frame image.png 将返回到当前方法的调用处(如上图,程序会返回到main方法中)重新执行,并且所有上下文变量的值也回溯到那时候

image.png