zoukankan      html  css  js  c++  java
  • 9.1 异常处理机制

    当程序运行出现以外的情形时,系统将自动生成一个Exception对象来通知程序,从而实现“业务功能实现代码”和“错误处理代码”分类,提供更好的可读性。

    一、使用try...catch捕获异常

    为了将“错误处理代码”从“业务实现代码”中分离出来,考虑下面的伪代码:

    1 if(一切正常)
    2 {
    3      //业务实现代码  
    4 }
    5 else
    6 {
    7     alert 输入不合法
    8     goto retry
    9 }

     java提出了一种假设:如果程序可以顺利完成,那么”一切正常”,把系统的业务实现代码放在try块中定义,所有异常处理逻辑放在catch块中处理。下面是Java异常处理机制的语法结构:

    1 try
    2 {
    3     //业务实现代码
    4 }
    5 catch(Exception e)//捕获(catch)异常
    6 {
    7     alert 输入不合法
    8     goto retry
    9 }

      如果执行try块里的业务代码时出现异常,系统将会自动生成一个异常对象,该异常对象被提交给Java运行环境,这个过程被称为抛出(throw)异常

      当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的catch块,则把该异常对象将给catch块处理,这个过程被称为捕获(catch)异常;如果Java运行时环境找不到捕获异常的catch块,则运行环境终止,Java程序也将退出。

     1 import java.io.*;
     2 public class Gobang
     3 {
     4     // 定义一个二维数组来充当棋盘
     5     private String[][] board;
     6     // 定义棋盘的大小
     7     private static int BOARD_SIZE = 15;
     8     public void initBoard()
     9     {
    10         // 初始化棋盘数组
    11         board = new String[BOARD_SIZE][BOARD_SIZE];
    12         // 把每个元素赋为"╋",用于在控制台画出棋盘
    13         for (var i = 0; i < BOARD_SIZE; i++)
    14         {
    15             for (var j = 0; j < BOARD_SIZE; j++)
    16             {
    17                 board[i][j] = "╋";
    18             }
    19         }
    20     }
    21     // 在控制台输出棋盘的方法
    22     public void printBoard()
    23     {
    24         // 打印每个数组元素
    25         for (var i = 0; i < BOARD_SIZE; i++)
    26         {
    27             for (var j = 0; j < BOARD_SIZE; j++)
    28             {
    29                 // 打印数组元素后不换行
    30                 System.out.print(board[i][j]);
    31             }
    32             // 每打印完一行数组元素后输出一个换行符
    33             System.out.print("
    ");
    34         }
    35     }
    36     public static void main(String[] args) throws Exception
    37     {
    38         var gb = new Gobang();
    39         gb.initBoard();
    40         gb.printBoard();
    41         // 这是用于获取键盘输入的方法
    42         var br = new BufferedReader(
    43             new InputStreamReader(System.in));
    44         String inputStr = null;
    45         // br.readLine():每当在键盘上输入一行内容按回车,
    46         // 用户刚刚输入的内容将被br读取到。
    47         while ((inputStr = br.readLine()) != null)
    48         {
    49             try
    50             {
    51                 // 将用户输入的字符串以逗号作为分隔符,分解成2个字符串
    52                 String[] posStrArr = inputStr.split(",");
    53                 // 将2个字符串转换成用户下棋的坐标
    54                 var xPos = Integer.parseInt(posStrArr[0]);
    55                 var yPos = Integer.parseInt(posStrArr[1]);
    56                 // 把对应的数组元素赋为"●"。
    57                 if (!gb.board[xPos - 1][yPos - 1].equals("╋"))
    58                 {
    59                     System.out.println("您输入的坐标点已有棋子了,"
    60                         + "请重新输入");
    61                     continue;
    62                 }
    63                 gb.board[xPos - 1][yPos - 1] = "●";
    64             }
    65             catch (Exception e)
    66             {
    67                 System.out.println("您输入的坐标不合法,请重新输入,"
    68                     + "下棋坐标应以x,y的格式");
    69                 continue;
    70             }
    71 
    72             gb.printBoard();
    73             System.out.println("请输入您下棋的坐标,应以x,y的格式:");
    74         }
    75     }
    76 }
    View Code

    上面的代码把处理用户输入字符串的代码都放在try块里进行,只要用户输入的字符串不是有效的坐标值,系统将会抛出一个异常,并把这个异常对象交给对应的catch块处理。

    二、异常类的继承关系

      当Java运行时环境收到异常对象后,会依次判断该异常对象是否是catch块后异常类或其子类的实例,如果是,Java运行时环境将调用该catch块来处理异常;否则再次拿该对象和下一个catch块里的异常类进行比较。Java异常捕获流程示意图:

     Java把所有的非正常情况分为两种:异常(Exception)和错误(Error),它们都继承Throwable父类。

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

    Java常见异常类之间的继承关系:

     程序示例:

     1 public class DivTest 
     2 {
     3     public static void main(String[] args) 
     4     {
     5         try
     6         {
     7             {
     8                 var a=Integer.parseInt(args[0]);
     9                 var b=Integer.parseInt(args[1]);
    10                 var c=a/b;
    11                 System.out.println("你输入的两个数相除的结果是:"+c);
    12             }
    13         }
    14         catch (IndexOutOfBoundsException ie)
    15         {
    16             System.out.println("数组越界:运行程序时输入的参数个数不够");
    17         }
    18         catch (NumberFormatException ne)
    19         {
    20             System.out.println("数组格式异常:程序只能接受整数参数");
    21         }
    22         catch (ArithmeticException ae)
    23         {
    24             System.out.println("算术异常");
    25         }
    26         catch (Exception e)
    27         {
    28             System.out.println("未知异常");
    29         }
    30     }
    31 }
    32 运行测试:
    33 E:>cd E:Java第十章 异常处理10.2 异常处理机制
    34 
    35 E:Java第十章 异常处理10.2 异常处理机制>javac DivTest.java
    36 
    37 E:Java第十章 异常处理10.2 异常处理机制>java DivTest 1 2
    38 你输入的两个数相除的结果是:0
    39 
    40 E:Java第十章 异常处理10.2 异常处理机制>java DivTest 5
    41 数组越界:运行程序时输入的参数个数不够
    42 
    43 E:Java第十章 异常处理10.2 异常处理机制>java DivTest 8 4
    44 你输入的两个数相除的结果是:2
    45 
    46 E:Java第十章 异常处理10.2 异常处理机制>java DivTest 5 0
    47 算术异常
    48 
    49 E:Java第十章 异常处理10.2 异常处理机制>java DivTest 1.2 1
    50 数组格式异常:程序只能接受整数参数
    View Code

    空指针异常:

     1 import java.util.Date;
     2 public class NullTest 
     3 {
     4     public static void main(String[] args) 
     5     {
     6         Date d=null;
     7         try
     8         {
     9             System.out.println(d.after(new Date()));
    10         }
    11         catch (NullPointerException ne)
    12         {
    13             System.out.println("空指针异常");
    14         }
    15         catch (Exception e)
    16         {
    17             System.out.println("未知异常");
    18         }
    19     }
    20 }
    21 输出:空指针异常
    View Code

    上面程序试图调用一个null对象的after()方法,这将引发NullPointerException异常(当试图调用一个null对象的实例方法或实例变量时,就会引发NullPointerException异常),Java运行时会调用NullPointerException对应的catch块处理该异常。

    实际上,进行异常捕获时不仅应该把Exception类对应的catch块放在最后,而且所有父类异常的catch块都应该排在子类异常catch块的后面(简称:先处理小异常,再处理大异常),否则编译器将出现错误。

    三、多异常捕获

    在Java 7以前,每个catch块只能捕获一种类型的异常;但从Java 7开始,一个catch块可以捕获多种类型的异常。

    使用一个catch块多种类型的异常需要注意:

    1、捕获多种类型的异常时,多种异常类型之间用竖线(|)隔开;

    2、捕获多种类型的异常时,异常变量有隐式的final修饰,程序不能对异常变量重写赋值。

     1 public class MultiExceptionTest
     2 {
     3     public static void main(String[] args)
     4     {
     5         try
     6         {
     7             var a = Integer.parseInt(args[0]);
     8             var b = Integer.parseInt(args[1]);
     9             var c = a / b;
    10             System.out.println("您输入的两个数相除的结果是:" + c );
    11         }
    12         catch (IndexOutOfBoundsException|NumberFormatException
    13             |ArithmeticException ie)
    14         {
    15             System.out.println("程序发生了数组越界、数字格式异常、算术异常之一");
    16             // 捕捉多异常时,异常变量默认有final修饰,
    17             // 所以下面代码有错:
    18 //            ie = new ArithmeticException("test");  //
    19         }
    20         catch (Exception e)
    21         {
    22             System.out.println("未知异常");
    23             // 捕捉一个类型的异常时,异常变量没有final修饰
    24             // 所以下面代码完全正确。
    25             e = new RuntimeException("test");    //
    26         }
    27     }
    28 }
    29 
    30 E:Java第十章 异常处理10.2 异常处理机制>javac MultiExceptionTest.java
    31 
    32 E:Java第十章 异常处理10.2 异常处理机制>java MultiExceptionTest 1 2.2
    33 程序发生了数组越界、数字格式异常、算术异常之一
    34 
    35 E:Java第十章 异常处理10.2 异常处理机制>
    View Code

     上面程序中使用了IndexOutOfBoundsException|NumberFormatException|ArithmeticException来定义异常类型,这就是表明该catch块可以同时捕获这三种异常类型。捕获多种类型的异常时,异常变量使用隐式final修饰,因此上面程序中①号粗体代码将会产生编译错误;捕获一种类型的异常时,异常变量没有final修饰,因此上面程序中②号代码可以通过。

    四、访问异常类型

    如果程序需要在catch块中访问异常对象的相关信息,则可以通过访问catch块的后异常形参来获得。当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋给catch块后的异常参数,程序可以通过该参数来获得异常的相关信息。

    所有异常都包含如下几个方法:
    1、getMessage():返回该异常的详细描述字符串。

    2、printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。

    3、printStackTrace(PrintStream s):将异常的跟踪栈信息输出到指定输出流。

    4、getStackTrace():返回异常的跟踪栈信息。

     1 import java.io.FileInputStream;
     2 import java.io.IOException;
     3 public class AccessExceptionMsg 
     4 {
     5     public static void main(String[] args) 
     6     {
     7         try
     8         {
     9             var fis=new FileInputStream("a.txt");
    10         }
    11         catch (IOException ioe)
    12         {
    13             System.out.println(ioe.getMessage());
    14             ioe.printStackTrace();
    15         }
    16     }
    17 }
    18 ---------- 运行Java捕获输出窗 ----------
    19 a.txt (系统找不到指定的文件。)//异常信息描述
    20 
    21 //异常根据栈信息
    22 java.io.FileNotFoundException: a.txt (系统找不到指定的文件。)
    23     at java.base/java.io.FileInputStream.open0(Native Method)
    24     at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
    25     at java.base/java.io.FileInputStream.<init>(FileInputStream.java:155)
    26     at java.base/java.io.FileInputStream.<init>(FileInputStream.java:110)
    27     at AccessExceptionMsg.main(AccessExceptionMsg.java:9)
    28 
    29 输出完成 (耗时 0 秒) - 正常终止
    View Code

    五、使用final回收资源

     程序在try块打开了一些物理资源(例如数据库连接、网络连接和磁盘文件等),这些物理资源必须显式回收。

    注意:Java的回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存。

     为了一定能回收try块中打开的物理资源,异常处理机制提供了finally块。不管try块中的代码是否出现异常,也不管哪一个catch块会被执行,甚至try块或catch块中执行了return语句,finally块总会被执行。完整的Java异常处理处理语法结构:

    try
    {
        //业务实现代码
        ...
    }
    catch (SubException1 e)
    {
        //异常处理块1
        ...
    }
    catch (SubException2 e)
    {
        //异常处理块2
        ...
    }
    finally
    {
        //资源回收块
        ...
    }

    异常处理语法结构中的try块是必需的,即没有try块,则不能有后面的catch块和finally块;catch块和finally块都是可选的,但catch块和fianlly块至少出现其中之一,也可以同时出现;可以有多个catch块,捕获父类异常的catch块必须位于捕获子类异常的后面;但不能只有try块,既没有catch块,也没有finally块;多个catch块位于try块之后,finally块必须位于所有的catch块之后。例如下面的程序: 

     1 import java.io.*;
     2 public class FinallyTest
     3 {
     4     public static void main(String[] args)
     5     {
     6         FileInputStream fis=null;
     7         try
     8         {
     9             fis=new FileInputStream("a.txt");
    10         }
    11         catch (IOException ioe)
    12         {
    13             System.out.println(ioe.getMessage());
    14             //return语句强制方法返回
    15             return;//16             //使用exit退出虚拟机
    17             //System.exit(1);//
    18         }
    19         finally
    20         {
    21             //关闭磁盘文件,回收资源
    22             if(fis!=null)
    23             {
    24                 try
    25                 {
    26                     fis.close();
    27                 }
    28                 catch (IOException ioe)
    29                 {
    30                     ioe.printStackTrace();
    31                 }
    32             }
    33             System.out.println("回收finally块里的资源回收!");
    34         }
    35     }
    36 }
    View Code

    上面程序增加了finally块,用于回收在try块中打开的物理资源。上面程序的catch块中①处有一条return语句,该语句强制方法返回。通常情况下,一旦方法执行到return语句的地方,程序将会立即结束该方法;现在不会,虽然return语句也强制方法结束,但一定会先执行到finally块里的代码。

    a.txt (系统找不到指定的文件。)
    回收finally块里的资源回收!

    如果使用System.exit(1)语句退出虚拟机,则finally块将失去执行的机会。执行上面的代码,将会看到如下结果:

    1 a.txt (系统找不到指定的文件。)

     注意:除非在try块、catch块中调用了退出虚拟机的方法,否则不管在try块、catch块中执行怎样的代码,出现增氧的情况,异常处理finally块总会被执行。

    在异常情况下,不要在finally块中使用如return或throw语句等导致方法终止的语句,一旦在finally块中使用了return或throw语句,将导致try块、catch块中的return或throw语句失效。例如:

    public class FinallyFlowTest
    {
        public static void main(String[] args)
            throws Exception
        {
            boolean a=test();
            System.out.println(a);
        }
        public static boolean test()
        {
            try
            {
                //因为finally块中return语句
                //所以下面的return语句失去作用
                return true;
            }
            finally
            {
                return false;
            }
        }
    }
    输出:false

    六、异常处理的嵌套

    异常处理流程代码可以放在任何可执行代码的地方,因此完整的异常处理流程既可以放在try块里,也可放在catch块里,还可以放在finally块里。这种情形叫做异常处理嵌套。异常处理的深度没有任何限制,但通常没有必要超过两层嵌套异常处理,因为层次太深将降低程序的可读性。

    七、Java 9增强的自动关闭资源的try语句

    回顾以前在finally块中关闭资源时,程序显得异常臃肿:

     1 public class FinallyTest
     2 {
     3     public static void main(String[] args)
     4     {
     5         FileInputStream fis=null;
     6         try
     7         {
     8             fis=new FileInputStream("a.txt");
     9         }
    10 ......
    11         finally
    12         {
    13             //关闭磁盘文件,回收资源
    14             if(fis!=null)
    15             {
    16                 try
    17                 {
    18                     fis.close();
    19                 }
    20                 catch (IOException ioe)
    21                 {
    22                     ioe.printStackTrace();
    23                 }
    24             }
    25             System.out.println("回收finally块里的资源回收!");
    26         }
    27     }
    28 }

    java 7增强了try语句的功能——它允许在try关键字后紧跟一对圆括号,圆括号可以声明、初始化一个或多个资源,此处的资源指那些必须在程序结束时显式关闭的资源(比如数据库连接、网络连接),try语句在该语句结束时自动关闭这些资源。

    需要注意的是,为了保证try语句可以正常关闭资源,这些资源类,必须实现AutoCloseable或Closeable接口,实现这两个接口就必须实现close。

    提示:

     1 import java.io.*;
     2 public class AutoCloseTest
     3 {
     4     public static void main(String[] args)
     5         throws IOException
     6     {
     7         try (
     8             // 声明、初始化两个可关闭的资源
     9             // try语句会自动关闭这两个资源。
    10             var br = new BufferedReader(
    11                 new FileReader("AutoCloseTest.java"));
    12             var ps = new PrintStream(new
    13                 FileOutputStream("a.txt")))
    14         {
    15             // 使用两个资源
    16             System.out.println(br.readLine());
    17             ps.println("庄生晓梦迷蝴蝶");
    18         }
    19     }
    20 }

    上面代码分别声明、初始化了两个IO流,由于BufferedReaderPrintStream流都实现了Closeable子接口,而且它们放在try语句中声明、初始化,所以try语句会自动关闭它们。

    自动关闭资源的try相当于包含了隐式的finally块(这个finally块用于关闭资源),因此这个try语句既没有catch块,也没有finally块。

    Java 9再次增强了这种try语句,Java 9不要求在try后的圆括号内声明并创建资源,只需要自动关闭的资源有final修饰或则时有效的final,Java 9允许将资源变量放在try后的圆括号内。

     1 import java.io.*;
     2 public class AutoCloseTest2
     3 {
     4     public static void main(String[] args)
     5         throws IOException
     6     {
     7         //有final修饰的资源
     8         final var br=new BufferedReader(new FileReader("AutoCloseTest2.java"));
     9         //没有显式使用final修饰,但只要不对该变量重新赋值,该变量就是有效的final,隐式修饰的
    10         //捕获多种类型异常时,异常变量有final修饰
    11         var ps=new PrintStream(new FileOutputStream("a.txt"));
    12         //只要将两个资源放在try后的圆括号即可
    13         try(br;ps)
    14         {
    15             //使用这两个资源
    16             System.out.println(br.readLine());
    17             ps.println("庄生晓梦迷蝴蝶");
    18         }
    19     }
    20 }
    21 ---------- 运行Java捕获输出窗 ----------
    22 import java.io.*;
    23 
    24 输出完成 (耗时 0 秒) - 正常终止
  • 相关阅读:
    8.5 day8
    8.1 day6
    课后作业 day29
    博客整理day29
    博客整理day28
    博客整理day27
    博客整理day26
    课后作业 day26
    Python 学习day22
    课后作业 day21
  • 原文地址:https://www.cnblogs.com/weststar/p/12616594.html
Copyright © 2011-2022 走看看