第十二章 通过异常处理错误

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Youyou_0826/article/details/80568999

第十二章 通过异常处理错误

标签: Java编程思想


Java的基本理念是“结构不佳的代码不能运行”。

异常处理是Java中唯一正式的错误报告机制,并通过编译器强制执行。

12.1 概念

异常允许将当前环境中的错误提交到更高一级的环境中,以便得到正确处理;此外异常处理能够降低代码复杂度,不必在方法调用时进行错误检查,因为异常机制能够保证所有错误都被捕获,由此将代码逻辑与错误处理分开,使得代码更加井井有条

12.2 基本异常

异常情形是阻止当前方法或作用域继续执行的问题,将异常情形和普通问题分离开。
抛出一个异常:

if(t == null) {
    throw new NullPointerException("t is a null object");
}

异常改变了原程序的执行路径。

12.2.1 异常参数

异常对象与普通对象类似,使用new在堆上创建异常对象,并且伴随着存储空间的分配。所有的标准异常类有两个构造方法,一个默认构造方法,一个接收字符串信息的构造方法。

throw new NullPointerException("t is null");

使用new创建异常对象之后,此对象的引用将传给throw,返回的异常对象类型与方法的返回类型不一致,异常返回的地点与普通方法返回的地点完全不同。

12.3 捕获异常

监控区域:可能产生异常的一段代码。

12.3.1 try块

如果方法内部出现异常,并且不希望方法在遇到异常的地方结束,可以使用try尝试方法调用。

try {
    //Code that might generate exceptions
}

12.3.2 异常处理程序

异常处理程序处理抛出的异常。

扫描二维码关注公众号,回复: 3849849 查看本文章
try {
    //Code that might generate exceptions
} catch(Type exceptionType) {
    //Handle exception of type
} catch(Type1 exceptionType1) {
    //Handle exception of type1
}

异常处理机制负责收集第一个与抛出异常类型相匹配的异常处理程序。

终止与恢复

Java理论上支持两种基本模型:终止模型恢复模型
终止模型:假设错误非常关键,以至于程序无法返回到发生错误的地方继续执行。
恢复模型:指异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法。并认为第二次能够成功。可以使用while循环使Java实现恢复模型。

大多数程序员偏向于终止模型,因为回复模型会导致耦合。因为恢复程序需要了解异常抛出的地点,势必会包含异常抛出的非通用性代码,增加代码的复杂度。

12.4 创建自定义异常

自定义异常必须继承已有的Java异常类,最好从最相近的异常类开始继承。
所有的异常类继承自Throwable。

class SimpleException extends Exception {
    //members and methods
}

12.4.1 异常与日志记录

直接调用与日志级别相关的方法写入日志。

package com.myexception;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;

/**
 * @author [email protected]
 * @function 异常与日志记录
 * @since 2018-06-04 12:37
 */
class LoggingException extends Exception {
    private static Logger logger = Logger.getLogger("LoggingException");

    public LoggingException() {
        StringWriter trace = new StringWriter();
        printStackTrace(new PrintWriter(trace));
        logger.severe(trace.toString());
    }
}

public class LoggingExceptions {
    public static void main(String[] args) {
        try {
            throw new LoggingException();
        } catch (LoggingException e) {
            System.err.println("caught " + e);
        }
    }
}

12.5 异常说明

Java鼓励将可能发生的异常告知客户端程序员,使得调用者可以确切的知道调用时可能会产生哪些异常。

void f() throws TooBig, TooSmall, DevZero {
    //do something
}

对于此种情况:
- 1. 将异常抛出;
- 2. 在调用方法时处理异常。

12.6 捕获所有异常

通过捕获所有异常的基类Exception可以捕获所有的异常,不过最好将这种捕获异常放在处理程序列表的末尾。

catch(Exception e) {
    System.out.println("Caught an exception.");
}

不过基类含有含有的信息不够具体,可以使用
- 1. String getMessage();
- 2. String getLocalizedMessage();
- void printStackTrace();
方法获取更加具体的异常信息。

12.6.1 栈轨迹

printStackTrace()方法所提供的信息可以由getStackTrace()方法访问,该方法将返回一个由栈轨迹中的元素所构成的数组,每个元素表示栈中的一帧。调用顺序从下往上。

package com.myexception;

/**
 * @author [email protected]
 * @function 异常栈轨迹
 * @since 2018-06-04 13:04
 */
public class WhoCalled {
    static void f() {
        try {
            throw new Exception();
        } catch (Exception e) {
            for (StackTraceElement stackTraceElement : e.getStackTrace()) {
                System.out.println(stackTraceElement.getMethodName());
            }
        }
    }

    static void g() {
        f();
    }

    static void h() {
        f();
    }

    public static void main(String[] args) {
        f();
        System.out.println("-----------------");
        g();
        System.out.println("-----------------");
        h();
        System.out.println("-----------------");
    }
}

output:

"C:\Program Files\Java\jdk1.8.0_161\bin\java.exe" "-javaagent:D:\IDEA\IntelliJ IDEA 2018.1.3\lib\idea_rt.jar=51695:D:\IDEA\IntelliJ IDEA 2018.1.3\bin"  com.myexception.WhoCalled
f
main
-----------------
f
g
main
-----------------
f
h
main
-----------------

Process finished with exit code 0

12.6.2 重新抛出异常

可以将刚捕获的异常重新抛出给上一级处理环境,此时同一try块后的catch将被抛弃,并且异常的所有信息都会得以保持。不过异常被重新抛出后,printStackTrace()得到的将是原始异常抛出点的调用栈信息。如果想要得到重新抛出点的异常信息,可使用fillStackTrace()方法,将重新抛出点的信息填充到原始异常栈信息,并且返回一个Throwable对象

catch(Exception e) {
    throw e;
}
package com.myexception;

/**
 * @author [email protected]
 * @function 重新抛出异常
 * @since 2018-06-04 13:15
 */
public class ReThrowing {
    public static void f() throws Exception {
        System.out.println("Origin exception in f().");
        throw new Exception("thrown from f().");
    }

    public static void g() throws Exception {
        try {
            f();
        } catch (Exception e) {
            System.out.println("Inside g(), e.printStackTrace().");
            e.printStackTrace();
            throw e;
        }
    }

    public static void h() throws Exception {
        try {
            f();
        } catch (Exception e) {
            System.out.println("Inside h(), e.printStackTrace().");
            e.printStackTrace();
            throw (Exception) e.fillInStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            g();
        } catch (Exception e) {
            System.out.println("main, printStackTrace().");
            e.printStackTrace();
        }
        try {
            h();
        } catch (Exception e) {
            System.out.println("main, printStackTrace().");
            e.printStackTrace();
        }
    }
}

output:

Origin exception in f().
java.lang.Exception: thrown from f().
    at com.myexception.ReThrowing.f(ReThrowing.java:11)
Inside g(), e.printStackTrace().
    at com.myexception.ReThrowing.g(ReThrowing.java:16)
main, printStackTrace().
    at com.myexception.ReThrowing.main(ReThrowing.java:36)
Origin exception in f().
Inside h(), e.printStackTrace().
java.lang.Exception: thrown from f().
main, printStackTrace().
    at com.myexception.ReThrowing.f(ReThrowing.java:11)
    at com.myexception.ReThrowing.g(ReThrowing.java:16)
    at com.myexception.ReThrowing.main(ReThrowing.java:36)
java.lang.Exception: thrown from f().
    at com.myexception.ReThrowing.f(ReThrowing.java:11)
    at com.myexception.ReThrowing.h(ReThrowing.java:26)
    at com.myexception.ReThrowing.main(ReThrowing.java:42)
java.lang.Exception: thrown from f().
    at com.myexception.ReThrowing.h(ReThrowing.java:30)
    at com.myexception.ReThrowing.main(ReThrowing.java:42)

Process finished with exit code 0

但是如果在fillStackTrace()之前发生异常,将会只得到新的抛出点的信息

12.6.3 异常链

捕获一个异常后抛出另一个异常,并把原始信息保存下来。Throwable子类构造器可以接受cause参数,用来保存原始异常信息,通过原始异常传递给新的异常,可以追踪到最初的异常。

12.7 Java标准异常

Throwable对象分为:
- 1. Eorror:编译时和系统错误;
- 2. 可以被抛出的基本类型错误。

12.7.1 RuntimeException

对于运行时异常,不要求强制异常说明,其代表编程错误。

package com.myexception;

/**
 * @author [email protected]
 * @function RuntimeException
 * @since 2018-06-04 15:12
 */
public class NeverCaught {
    static void f() {
        throw new RuntimeException("from f().");
    }

    static void g() {
        f();
    }

    public static void main(String[] args) {
        g();
    }
}

output:

Exception in thread "main" java.lang.RuntimeException: from f().
    at com.myexception.NeverCaught.f(NeverCaught.java:10)
    at com.myexception.NeverCaught.g(NeverCaught.java:14)
    at com.myexception.NeverCaught.main(NeverCaught.java:18)

Process finished with exit code 1

12.8 使用finally进行清理

finally语句后的代码总能得到执行,可以使用finally构造重试,增加代码的健壮性。

package com.myexception;

/**
 * @author [email protected]
 * @function finally
 * @since 2018-06-04 15:18
 */
class ThreeException extends Exception {

}

public class FinallyWorks extends Exception {
    private static int count = 0;

    public static void main(String[] args) {
        while (true) {
            try {
                if (count++ == 0) {
                    throw new ThreeException();
                }
                System.out.println("no exception.");
            } catch (Exception e) {
                System.out.println("three exception.");
            } finally {
                System.out.println("in finally clause.");
                if (count == 2) {
                    break;
                }
            }
        }
    }
}

output:

three exception.
in finally clause.
no exception.
in finally clause.

Process finished with exit code 0

12.8.1 finally的作用

将内存之外的资源恢复到原始状态,例如已经打开的文件、网络连接、屏幕界面的画图等。

12.8.2 在return中使用finally

finally中的代码总会执行,因此程序可以从多个点返回,并且能保证重要的清理工作依然能进行。

12.8.3 异常丢失

Java异常处理中的抛出的异常可能会被忽略,try或catch中抛出的异常会finally中抛出的异常取代。

package com.myexception;

/**
 * @author [email protected]
 * @since 2018-06-04 15:35
 */
public class LostMessage {
    void f() throws ImportantException {
        throw new ImportantException();
    }

    void dispose() throws HoHumException {
        throw new HoHumException();
    }

    public static void main(String[] args) {
        try {
            LostMessage lostMessage = new LostMessage();
            try {
                lostMessage.f();
            } finally {
                lostMessage.dispose();
            }
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

class ImportantException extends Exception {
    public String toString() {
        return "important exception.";
    }
}

class HoHumException extends Exception {
    public String toString() {
        return "a trivial exception.";
    }
}

output:

a trivial exception.

Process finished with exit code 0

12.9 异常的限制

    1. 当覆盖方法的时候,只能抛出基类方法中说明的异常列表中的异常,这意味着基类方法应用到子类对象时能正常工作;
    1. 派生类构造器不能捕获基类构造器抛出的异常。

12.10 构造器

对于构造阶段可能抛出异常,并且要求清理的类(如文件读取),最安全的方法是使用嵌套的try字句。

12.11 异常匹配

抛出异常的时候,异常处理系统会按照书写顺序寻找最近的的处理程序,找到匹配的异常处理程序。查找的过程中不会要求两个类类型完全匹配,派生类的对象也可以匹配其基类的处理程序。

12.12 其他可选方式

12.12.1 历史

每次调用的时候都必须执行测试条件,以确定会产生何种错误,这使程序难以阅读,并且有可能降低执行效率,因此程序员们既不愿意指出,也不愿意处理异常。

12.12.2 观点

仅从小程序来看,会认为一场说明能增加开发人员的效率,并提高代码质量,但考察项目的时候,结论就不同了——开发效率下降了,而代码质量之后微不足道的提高,甚至毫无提高。

总体来说,我觉得异常很不错,但是Java的被检查的异常带来的麻烦比好处多。

12.12.3 把异常传递给控制台

package com.myexception;

import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author [email protected]
 * @since 2018-06-04 16:06
 */
public class MainException {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("MainException.java");
        fileInputStream.close();
    }
}

12.12.4 把“被检查的异常”转换为“不被检查的异常”

try {
    //do something
} catch (SomeException) {
    throw new RuntimeException();
}

异常使用指南

应该在下列情况下使用异常:
1. 在恰当的级别处理问题;
2. 解决问题并且重新调用产生异常的方法;
3. 进行少许修补,然后绕过异常发生的地方继续执行;
4. 用别的数据进行计算,以代替方法预计会返回的值;
5. 把当前环境下能做完的事尽量做完,然后把相同的异常抛到最高层;
6. 把当前环境下能做完的事尽量做完,然后把不同的异常抛到最高层;
7. 终止程序;
8. 进行简化;
9. 让类库和程序更加安全。

猜你喜欢

转载自blog.csdn.net/Youyou_0826/article/details/80568999