• 【Java学习笔记五】——异常处理


    声明:本文章内容主要摘选自尚硅谷宋红康Java教程、《Java核心卷一》、《Java语言程序设计-基础篇》、廖雪峰Java教程,示例代码部分出自本人,更多详细内容推荐直接观看以上教程及书籍,若有错误之处请指出,欢迎交流。

    一、处理错误

    1.异常的类型

    在理想状态下,用户输入数据的格式永远都是正确的,选择打开的文件也一定存在,并且永远不会出现bug。然而,在现实世界中却充满了不良的数据和带有问题的代码,现在是讨论Java程序设计语言处理这些问题的机制的时候了。

    假设在一个Java程序运行期间出现了一个错误。这个错误可能是由于文件包含了错误信息,或者网络连接出现问题造成的,也有可能是因为使用无效的数组下标,或者试图使用一个没有被赋值的对象引用而造成的。用户期望在出现错误时,程序能够采用一些理智的行为。如果由于出现错误而使得某些操作没有完成,程序应该:返回到一种安全状态,并能够让用户执行一些其他的命令;或者允许用户保存所有操作的结果,并以妥善的方式终止程序。这个时候就轮到异常处理登场了,在讲到如何处理之前,我们可以认识一下异常的所有类型:

    Exception
    │
    ├─ RuntimeException
    │  │
    │  ├─ NullPointerException
    │  │
    │  ├─ IndexOutOfBoundsException
    │  │
    │  ├─ SecurityException
    │  │
    │  └─ IllegalArgumentException
    │     │
    │     └─ NumberFormatException
    │
    ├─ IOException
    │  │
    │  ├─ UnsupportedCharsetException
    │  │
    │  ├─ FileNotFoundException
    │  │
    │  └─ SocketException
    │
    ├─ ParseException
    │
    ├─ GeneralSecurityException
    │
    ├─ SQLException
    │
    └─ TimeoutException
    

    例如,如果使用一个越界的下标访问数组,程序就会产生一个ArrayIndexoutofBoundsException的运行时错误。为了从文件中读取数据,需要使用new Scanner(new File(filename))创建一个Scanner对象。如果该文件不存在,程序将会出现一个FileNotFoundException的运行时错误。
    这些异常我们并不需要一一记住,在IDEA等软件上都可以帮助我们抛出异常,但在调试过程中出现异常提示时,如果我们知道该异常的具体意思,那么我们就可以迅速找出错误,所以建议记住常见的异常类型。

    2.声明与抛出异常

    如果遇到了无法处理的情况,那么Java的方法可以抛出一个异常。这个道理很简单:一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。例如,一段读取文件的代码知道有可能读取的文件不存在,或者内容为空,因此,试图处理文件信息的代码就需要通知编译器可能会抛出IOException类的异常。
    方法应该在其首部声明所有可能抛出的异常。这样可以从首部反映出这个方法可能抛出哪类受查异常。例如,下面是标准类库中提供的FilelnputStream类的一个构造器的声明。

    如:public FileInputStream(String name)throws FileNotFoundException

    这个声明表示这个构造器将根据给定的String参数产生一个FileInputStream对象,但也有可能抛出一个FileNotFoundException异常。如果发生了这种糟糕情况,构造器将不会初始化一个新的FileInputStream对象,而是抛出一个FileNotFoundException类对象。如果这个方法真的抛出了这样一个异常对象,运行时系统就会开始搜索异常处理器,以便知道如何处理FileNotFoundException对象。
    在自己编写方法时,不必将所有可能抛出的异常都进行声明。至于什么时候需要在方法中用throws子句声明异常,什么异常必须使用throws子句声明,需要记住在遇到下面4种情况时应该抛出异常:

    • 1)调用一个抛出受查异常的方法,例如,FilelnputStream构造器。
    • 2)程序运行过程中发现错误,并且利用throw语句抛出一个受查异常。
    • 3)程序出现错误,例如,a[-1]=0会抛出一个ArraylndexOutOfBoundsException这样的非受查异常。
    • 4)Java虚拟机和运行时库出现的内部错误。

    RuntimeException、Error以及它们的子类都称为免检异常(unchecked exception)。所有其他异常都称为必检异常(checked exception),意思是指编译器会强制程序员检查并处理它们。
    在大多数情况下,免检异常都会反映出程序设计上不可恢复的逻辑错误。例如,如果通过一个引用变量访问一个对象之前并未将一个对象赋值给它,就会抛出NullPointerException异常;如果访问一个数组的越界元素,就会抛出IndexoutofBoundsException异常。这些都是程序中必须纠正的逻辑错误。免检异常可能在程序的任何一个地方出现。为避免过多地使用try-catch块,Java语言不允许编写代码捕获或声明免检异常。

    检测一个错误的程序可以创建一个正确异常类型的实例并抛出它。这就称为抛出一个异常(throwing an exception)。这里有一个例子,假如程序发现传递给方法的参数与方法的合约不符(例如,方法中的参数必须是非负的,但是传入的是一个负参数),这个程序就可以创建IllegalArgumentException的一个实例并抛出它,如下所示:

    IllegalArgumentException ex = new IllegalArgumentException("Wrong Argument");
    throw ex;

    或者,如果你愿意,也可以使用下面的语句:
    throw new IllegalArgumentException("Wrong Argument");

    注意IllegalArgumentException是JavaAPI中的一个异常类。通常,JavaAPI中的每个异常类至少有两个构造方法:一个无参构造方法和一个带可描述这个异常的String参数的构造方法。该参数称为异常消息(exception message),它可以用getMessage()获取。

    注意:声明异常的关键字是throws,抛出异常的关键字是throw。

    3.创建异常类*

    *表示了解即可,创建自定义异常类在初学时一般不需要使用,在实际开发中才会用到

    在程序中,可能会遇到任何标准异常类都没有能够充分地描述清楚的问题。在这种情况下,创建自己的异常类就是一件顺理成章的事情了。我们需要做的只是定义一个派生于Exception的类,或者派生于Exception子类的类。例如,定义一个派生于IOException的类。
    习惯上,定义的类应该包含两个构造器,一个是默认的构造器;另一个是带有详细描述信息的构造器(超类Throwable的toString方法将会打印出这些详细信息,这在调试中非常有用)。

    /*
    一个常见的做法是自定义一个BaseException作为“根异常”,然后,派生出各种业务类型的异常。
    BaseException需要从一个适合的Exception派生,通常建议从RuntimeException派生:
    */
    public class BaseException extends RuntimeException {
    }
    //其他业务类型的异常就可以从BaseException派生:
    public class UserNotFoundException extends BaseException {
    }
    public class LoginFailedException extends BaseException {
    }
    ...
    //自定义的BaseException应该提供多个构造方法:
    public class BaseException extends RuntimeException {
        public BaseException() {
            super();
        }
        public BaseException(String message, Throwable cause) {
            super(message, cause);
        }
        public BaseException(String message) {
            super(message);
        }
        public BaseException(Throwable cause) {
            super(cause);
        }
    }
    //上述构造方法实际上都是原样照抄RuntimeException。这样,抛出异常的时候,就可以选择合适的构造方法。通过IDE可以根据父类快速生成子类的构造方法。
    

    二、捕获异常

    1.try-catch

    如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容。对于图形界面程序(applet和应用程序),在捕获异常之后,也会打印出堆栈的信息,但程序将返回到用户界面的处理循环中。

    //要想捕获一个异常,必须设置try/catch语句块。如下所示:
    try{
       ……
    }catch(Exception1 exVar1){
       ……
    }
    catch(Exception2 exVar2){
       ……
    }
    catch(Exception3 exVar3){
       ……
    }
    //注意在catch块中异常被指定的顺序是非常重要的。如果父类的catch块出现在子类的catch块之前,就会导致编译错误。具体子父关系在文首的异常类型中可以查看
    

    如果在try语句块中的任何代码抛出了一个在catch子句中说明的异常类,那么程序将跳过try语句块的其余代码,执行catch子句中的处理器代码。
    如果在try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。
    如果方法中的任何代码抛出了一个在catch子句中没有声明的异常类型,那么这个方法就会立刻退出。

    //这就是一个简单的例子,涉及IO流的内容,在接下来的笔记将会讲到
    public void read(String fileName) throws IOException {
            try {
                InputStream ips = new FileInputStream(fileName);
                int len;
                byte[] data = new byte[10];
                while((len = ips.read(data)) != -1){
                    String str = new String(data, 0, len);
                    System.out.print(str);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    2.finally

    引言:世界上最遥远的距离,是我在if里你在else里,似乎一直相伴又永远分离;世界上最痴心的等待,是我当case你是switch,或许永远都选不上自己;世界上最真情的相依,是你在try我在catch。无论你发神马脾气,我都默默承受,静静处理。到那时,再来期待我们的finally。

    有时候,不论异常是否出现或者是否被捕获,都希望执行某些代码。Java有一个finally子句,可以用来达到这个目的。finally子句的语法如下所示:

    try{
       ……
    }catch(Exception1 exVar1){
       ……
    }
    finally{
       ……
    }
    

    在任何情况下,finally块中的代码都会执行,不论try块中是否出现异常或者是否被捕获。考虑下面三种可能出现的情况:
    1)如果try块中没有出现异常,执行finalstatements,然后执行try语句的下一条语句。
    2)如果try块中有一条语句会引起异常,并被catch块捕获,然后跳过try块的其他语句,执行catch块和finally子句。执行try语句之后的下一条语句。
    3)如果try块中有一条语句引起异常,但是没有被任何catch块捕获,就会跳过try块中的其他语句,执行finally子句,并且将异常传递给这个方法的调用者。
    即使在到达finally块之前有一个return语句,finally块还是会执行。

    public void read(String fileName) throws IOException {
            InputStream ips = null;
            //注意我们在try-catch-finally外声明ips,因为按照原来的做法,有可能try语句内的声明ips语句未能执行,但此时我们执行finally语句时便会出错
            try {
                ips = new FileInputStream(fileName);
                int len;
                byte[] data = new byte[10];
                while((len = ips.read(data)) != -1){
                    String str = new String(data, 0, len);
                    System.out.print(str);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                ips.close();
            }
        }
          //实际上,最后的finally语句改为以下形式更好,不过上面为了便于理解
          finally {
                if (ips != null) {
                    try {
                        ips.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
    

    警告:当finally子句包含return语句时,将会出现一种意想不到的结果。假设利用returm语句从try语句块中退出。在方法返回前,finally子句的内容将被执行。如果finally子句中也有一个return语句,这个返回值将会覆盖原始的返回值。请看一个复杂的例子:

    public static int f(int n){
            try{
                int r = n * n;
                return r;
            }finally{
                if(n == 2) return 0;
            }
        }
    /*
    如果调用f(2),那么try语句块的计算结果为r=4,并执行return语句。然而,在方法真正返回前,还要执行finally子句。
    finally子句将使得方法返回0,这个返回值覆盖了原始的返回值4。
    */
    

    Tips:选择需要增加try/catch/finally保护的代码,注意要完整的一行,在Eclipse中,可以通过快捷键Alt+ Shift + Z 快速调用try/catch/finally等结构,在IDEA中,快捷键则是Ctrl + Alt + T(实际上这些快捷键的是用来为选中的代码块快速添加结构,叫做surround with)

    此笔记仅针对有一定编程基础的同学,且本人只记录比较重要的知识点,若想要入门Java可以先行观看相关教程或书籍后再阅读此笔记。

    最后附一下相关链接:
    Java在线API中文手册
    Java platform se8下载
    尚硅谷Java教学视频
    《Java核心卷一》

  • 相关阅读:
    ↗☻【高性能网站建设进阶指南 #BOOK#】第3章 拆分初始化负载
    ↗☻【高性能网站建设进阶指南 #BOOK#】第7章 编写高效的JavaScript
    【JavaScript】text
    ↗☻【高性能网站建设进阶指南 #BOOK#】第5章 整合异步脚本
    ↗☻【高性能网站建设进阶指南 #BOOK#】第10章 图像优化
    利用十大最佳游戏开发工具开发游戏
    传奇服务器端/客户端 完整源代码
    order by union 应用实例 mssql
    Nine Digits Expression
    Ninedigit Fractions
  • 原文地址:https://www.cnblogs.com/66ccffly/p/13459046.html
Copyright © 2020-2023  润新知