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

基本异常

异常是阻止当前方法或作用域继续执行的问题, 因为在当前环境无法获取运行的必要信息, 就需要将这个问题抛到其上级环境, 来交给上级环境来解决
当抛出异常之后,系统会通过new在堆上创建对应的异常对象, 然后当前执行环境(不能继续下去)终止, 并且从当前环境弹出异常对象的引用, 此时异常处理系统接管程序, 并开始寻找一个恰当的执行环境来执行程序,这个恰当的程序就叫异常处理程序, 它的任务就是让程序从异常中恢复, 以使程序要么换一种方式运行, 要么继续运行.


异常参数

基异常有两个构造器, 一个是无参的,一个是参数是一个字符串的
new 创建完异常对象之后, 会将异常对象的引用传给throw, throw跟返回值return有一些相似的地方, 比如都可以跳出方法的作用域, 只是throw的跨度更大, 能将异常抛到更外层的地方去
Throwable对象时异常的根类, 通常错误信息保存在异常内部或者异常名中, 一般情况下是异常信息是保存在异常名中, 而异常内部的信息没有什么意义


异常捕获

要明白异常是如何捕获的,首先就要理解监控区域概念, 它是一段可能发生异常的代码, 并且后面跟着能处理这个异常的代码

try 语句块

try {
// Code that might generate exceptions
}

catch( )语句, 跟在try之后, 能按类型捕获异常

终止与恢复

异常的处理有两种, 一种是直接终止程序, 另一种是通过catch来捕获, 然后进行修改, 在try外部套循环, 然后直到运行不再报异常位置, 但是恢复这种异常模式耦合性太强,不好用


自定义异常

因为系统提供的异常可能并没有你需要的那么全面,这个时候就需要你自己去自定义异常, 这就需要继承系统已存在的异常, 一般情况下要找相近的系统异常, 尽管这样并不好找

异常声明

void f() throws TooBig, TooSmall, DivZero { // …
这种就是异常的声明
异常声明的作用就是告诉客户程序员这里可能会发生哪些异常, 针对那些可能会产生异常的代码, 要么try catch 要么就需要进行异常声明

捕获所有异常

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

因为Exception是所有异常的基类, 所以这样catch 就会捕捉到所有的异常 ,正因如此 这个catch要放在其他异常捕获之后

多重捕获

  1. // exceptions/MultiCatch.java
  2. public class MultiCatch {
  3. void x() throws Except1, Except2, Except3, Except4 {}
  4. void process() {}
  5. void f() {
  6. try {
  7. x();
  8. } catch(Except1 | Except2 | Except3 | Except4 e) {
  9. process();
  10. }
  11. }
  12. }

这对书写更整洁的代码很有帮助。

栈轨迹printStackTrace()

printStackTrace()里的信息可以通过getStackTrace()来访问, 这个方法返回的是一个由栈轨迹元素组成的数组,每一个元素对应栈的一帧, 栈顶元素(0)对应的是最后一个调用的方法, 先进后出

e.printStackTrace();

这个东西最好不要在运行代码中出现, 非常的占用内存

重新抛出异常

  1. catch(Exception e) {
  2. System.out.println("An exception was thrown");
  3. throw e;
  4. }

这就是重新抛出异常,同一个try catch 后续的子catch 会被忽略,
如果重新抛出异常, 那么printStackTrace()所展示的信息是原来异常点的调用栈信息, 而不是最新的,如果想要更新这个信息, 可以调用fillInStackTrace(), 这个方法将返回一个throwable对象,它是通过把当前调用栈的信息填入原来的那个异常对象而建立的

  1. try {
  2. f();
  3. } catch(Exception e) {
  4. System.out.println(
  5. "Inside h(), e.printStackTrace()");
  6. e.printStackTrace(System.out);
  7. throw (Exception)e.fillInStackTrace();
  8. }

异常链

在捕获一个异常之后再跑一个异常, 并希望把原始的异常保存下来,这就叫做异常链, 在JDK1.4之前 是需要自己保存原始异常信息的, 之后的jdk提供了一个接受cause(因由)对象的异常构造函数, 这个cause就用来表示原始异常,这样就可以通过新异常也可以追溯到异常的始发地
在Throwable 有三个子类是支持cause构造器的, 分别是 Exception, RunTimeException,Error, 如果其他的异常类要使用异常链,则需要使用initCause()方法

  1. if(value == null) {
  2. // Most exceptions don't have a "cause"
  3. // constructor. In these cases you must use
  4. // initCause(), available in all
  5. // Throwable subclasses.
  6. DynamicFieldsException dfe =
  7. new DynamicFieldsException();
  8. dfe.initCause(new NullPointerException());
  9. throw dfe;
  10. }

Java 标准异常

Java有Error 系统异常跟 Exception 一般情况下 Error是不需要我们关心的, 我们只关心Expection

特例RuntimeExpection

例如NPE, 就是RuntimeException。 这类异常不需要人为的去抛, Java就自己去在运行时检验它,例如引用未空的时候抛npe, 这类在运行时抛出的异常 就叫RunTimeException(编程异常)
这样继承RunTimeExpection就可以使你自定义的异常编程运行时自动抛出的异常(也就是不用在类旁边给她throw出来),也就是运行时自动监测

RuntimeException 代表的是编程错误:

  1. 无法预料的错误。比如从你控制范围之外传递进来的 null 引用。
  2. 作为程序员,应该在代码中进行检查的错误。(比如对于 ArrayIndexOutOfBoundsException,就得注意一下数组的大小了。)在一个地方发生的异常,常常会在另一个地方导致错误。
  1. // exceptions/NeverCaught.java
  2. // Ignoring RuntimeExceptions
  3. // {ThrowsException}
  4. public class NeverCaught {
  5. static void f() {
  6. throw new RuntimeException("From f()");
  7. }
  8. static void g() {
  9. f();
  10. }
  11. public static void main(String[] args) {
  12. g();
  13. }
  14. }
  15. 输出
  16. ___[ Error Output ]___
  17. Exception in thread "main" java.lang.RuntimeException:
  18. From f()
  19. at NeverCaught.f(NeverCaught.java:7)
  20. at NeverCaught.g(NeverCaught.java:10)
  21. at NeverCaught.main(NeverCaught.java:13)

看这个例子可以看出来, 对于RunTimeExceprion来说,他不需要被捕获,也会输出错误日志,他会在程序运行结束前调用printStackTrace来输出错误信息

注意:只有RuntimeException及其子类的异常类型可以被忽略(也就是不用特意的去再类名边上throw或者catch)

使用 finally 进行清理

finally就是不管catch不catch 必进行, break 和 continue 或者return语句的时候,finally 子句也会得到执行

缺憾:异常丢失

目前Java中存在一个问题就是可以通过finally来让异常丢失

  1. // exceptions/LostMessage.java
  2. // How an exception can be lost
  3. class VeryImportantException extends Exception {
  4. @Override
  5. public String toString() {
  6. return "A very important exception!";
  7. }
  8. }
  9. class HoHumException extends Exception {
  10. @Override
  11. public String toString() {
  12. return "A trivial exception";
  13. }
  14. }
  15. public class LostMessage {
  16. void f() throws VeryImportantException {
  17. throw new VeryImportantException();
  18. }
  19. void dispose() throws HoHumException {
  20. throw new HoHumException();
  21. }
  22. public static void main(String[] args) {
  23. try {
  24. LostMessage lm = new LostMessage();
  25. try {
  26. lm.f();
  27. } finally {
  28. lm.dispose();
  29. }
  30. } catch(VeryImportantException | HoHumException e) {
  31. System.out.println(e);
  32. }
  33. }
  34. }
  35. 输出
  36. A trivial exception
  37. 或者直接在finallyruturn
  38. // exceptions/ExceptionSilencer.java
  39. public class ExceptionSilencer {
  40. public static void main(String[] args) {
  41. try {
  42. throw new RuntimeException();
  43. } finally {
  44. // Using 'return' inside the finally block
  45. // will silence any thrown exception.
  46. return;
  47. }
  48. }
  49. }

上述两个例子都会导致异常的丢失

异常限制

当方法覆盖的时候,只能抛出基类方法的异常说明里的那些异常, 如果基类跟接口中都有同样的 方法, 那么子类即实现,又继承的时候, 异常仍然是取基类的异常, 构造器的异常就是自己顾自己的,

构造器

绝大多数的异常会被清理掉,不需要你考虑,但是构造器中的异常就需要不一定了,构造器会把对象设成安全的初始状态,并且还会有其他的一些动作,这些动作只用在用户对象使用完毕,并调用特殊的方法清理后才能被清理,如果构造器出现了异常, 那么这些清理的方法也就不能正常的运行了, 所以构造器在编写的时候要格外的小心
你可能会说调用finally来处理, 但是构造器可能运行一半就抛异常了,而此时对象还没被创建出来, 而finally里你是通过对象来调用清理的方法, 这样一来调用一个不存在的对象 ,肯定就是有问题的

所以针会抛异常的构造器使用嵌套try

  1. // exceptions/Cleanup.java
  2. // Guaranteeing proper cleanup of a resource
  3. public class Cleanup {
  4. public static void main(String[] args) {
  5. try {
  6. InputFile in = new InputFile("Cleanup.java");
  7. try {
  8. String s;
  9. int i = 1;
  10. while((s = in.getLine()) != null)
  11. ; // Perform line-by-line processing here...
  12. } catch(Exception e) {
  13. System.out.println("Caught Exception in main");
  14. e.printStackTrace(System.out);
  15. } finally {
  16. in.dispose();
  17. }
  18. } catch(Exception e) {
  19. System.out.println(
  20. "InputFile construction failed");
  21. }
  22. }
  23. }
  24. 输出
  25. dispose() successful

这种对构造函数进行try 只有构造函数成功的时候 才会调用dispose(),也就是确保的了对象的一定存在

  • [1] 相当简单,遵循了在可去除对象之后紧跟 try-finally 的原则。如果对象构造不会失败,就不需要任何 catch。
  • [2] 为了构造和清理,可以看到将具有不能失败的构造器的对象分组在一起。
  • [3] 展示了如何处理那些具有可以失败的构造器,且需要清理的对象。为了正确处理这种情况,事情变得很棘手,因为对于每一个构造,都必须包含在其自己的 try-finally 语句块中,并且每一个对象构造必须都跟随一个 try-finally 语句块以确保清理。

    Try-With-Resources 用法

    这个东西自带一个close()方法
    格式为
    1. // exceptions/TryWithResources.java
    2. import java.io.*;
    3. public class TryWithResources {
    4. public static void main(String[] args) {
    5. try(
    6. InputStream in = new FileInputStream(
    7. new File("TryWithResources.java"))
    8. ) {
    9. int contents = in.read();
    10. // Process contents
    11. } catch(IOException e) {
    12. // Handle the error
    13. }
    14. }
    15. }
    这里可以看出 这个try多出来了一个( ),这个括号用来创建对象, 可以创建多个对象, 用分号;分割, 它的名字叫资源规范头 , 它的作用就是你创建出来的对象 , 在try的{}里面是可以使用的, 同时 你不用自己去清理这个资源, 它会在try语句运行结束的时候调用close()来清理资源, 其它的方法跟普通的try块一样, 而且它可以不搭配catch来用

使用要求

这种Try-With-Resource用法可以简化代码, 不用你去手动的去close()清除资源, 使用它也是有要求的, 它的要求就是在规范头里的类必须实现AutoCloseable接口, 你可以通过javadoc来看都有哪些类实现了该接口, 同时java7之后修改了closeable()接口, 使其集成了AutoCloseable()接口, 所有间接的实现closeable()接口的类 也可以用这种Try-With-Resource用法

揭示细节

这个Try-With-Resources可以尽你所想

  1. // exceptions/AutoCloseableDetails.java
  2. class Reporter implements AutoCloseable {
  3. String name = getClass().getSimpleName();
  4. Reporter() {
  5. System.out.println("Creating " + name);
  6. }
  7. public void close() {
  8. System.out.println("Closing " + name);
  9. }
  10. }
  11. class First extends Reporter {}
  12. class Second extends Reporter {}
  13. public class AutoCloseableDetails {
  14. public static void main(String[] args) {
  15. try(
  16. First f = new First();
  17. Second s = new Second()
  18. ) {
  19. }
  20. }
  21. }
  22. 输出
  23. Creating First
  24. Creating Second
  25. Closing Second
  26. Closing First

特点一 : 后创先删

从这个demo中可以看出 try 会按照规范头中的代码顺序来创建对象, 在清除对象的时候正好相反,先清除存在时间短的, 这样做是因为有可能存在s依赖f的情况, 这时候如果先清除了f的话, s会报异常

特点二 : 规范头里只接受实现了AutoCloseable接口的对象

  1. // exceptions/ConstructorException.java
  2. class CE extends Exception {}
  3. class SecondExcept extends Reporter {
  4. SecondExcept() throws CE {
  5. super();
  6. throw new CE();
  7. }
  8. }
  9. public class ConstructorException {
  10. public static void main(String[] args) {
  11. try(
  12. First f = new First();
  13. SecondExcept s = new SecondExcept();
  14. Second s2 = new Second()
  15. ) {
  16. System.out.println("In body");
  17. } catch(CE e) {
  18. System.out.println("Caught: " + e);
  19. }
  20. }
  21. }
  22. 输出为
  23. Creating First
  24. Creating SecondExcept
  25. Closing First
  26. Caught: CE

特点三 : 出现异常会终止继续的对象创建,关闭已创建的对象

从这个demo中可以看出来, 因为SecondExcept在创建的时候出现了异常, 所以后面没有执行Second()的创建, 同时你可以看到, 是先关闭了First()再捕获异常, 另外 由于SecondExcept()出现异常,这是属于构造器内的异常, 你不知道此时对象有没有创建出来, 所以并没有关闭SecondExcept对象, 它的实现其实就是嵌套try catch ,只是它替我们做了

  1. // exceptions/BodyException.java
  2. class Third extends Reporter {}
  3. public class BodyException {
  4. public static void main(String[] args) {
  5. try(
  6. First f = new First();
  7. Second s2 = new Second()
  8. ) {
  9. System.out.println("In body");
  10. Third t = new Third();
  11. new SecondExcept();
  12. System.out.println("End of body");
  13. } catch(CE e) {
  14. System.out.println("Caught: " + e);
  15. }
  16. }
  17. }
  18. Creating First
  19. Creating Second
  20. In body
  21. Creating Third
  22. Creating SecondExcept
  23. Closing Second
  24. Closing First
  25. Caught: CE

特点四 : 非规范头里的对象创建, 系统不会帮你关闭资源

从这个demo中可以看出在创建SecondExcept()对象时抛出了异常, 导致了程序的终止, 因为Third没有在规范头里,它并没有执行资源的close

  1. // exceptions/CloseExceptions.java
  2. class CloseException extends Exception {}
  3. class Reporter2 implements AutoCloseable {
  4. String name = getClass().getSimpleName();
  5. Reporter2() {
  6. System.out.println("Creating " + name);
  7. }
  8. public void close() throws CloseException {
  9. System.out.println("Closing " + name);
  10. }
  11. }
  12. class Closer extends Reporter2 {
  13. @Override
  14. public void close() throws CloseException {
  15. super.close();
  16. throw new CloseException();
  17. }
  18. }
  19. public class CloseExceptions {
  20. public static void main(String[] args) {
  21. try(
  22. First f = new First();
  23. Closer c = new Closer();
  24. Second s = new Second()
  25. ) {
  26. System.out.println("In body");
  27. } catch(CloseException e) {
  28. System.out.println("Caught: " + e);
  29. }
  30. }
  31. }
  32. Creating First
  33. Creating Closer
  34. Creating Second
  35. In body
  36. Closing Second
  37. Closing Closer
  38. Closing First
  39. Caught: CloseException

特点五 : 当close发生异常的时候, 也会先close 之后再捕获异常

从这个demo中可以看出, 中间的Closer的close方法抛了异常, 它在second的close方法之前() , 但是还是执行了second, 也就是说close虽然会异常 ,但是也会执行close 因为此时的对应已经存在, 必须要将其清理, 所以会在清理完资源之后再抛异常

异常匹配

catch的异常捕获顺序是按照代码从上至下的顺序的也就是说就近顺序. 基类的异常可以捕获子类的异常

// exceptions/Human.java
// Catching exception hierarchies
class Annoyance extends Exception {}
class Sneeze extends Annoyance {}
public class Human {
    public static void main(String[] args) {
        // Catch the exact type:
        try {
            throw new Sneeze();
        } catch(Sneeze s) {
            System.out.println("Caught Sneeze");
        } catch(Annoyance a) {
            System.out.println("Caught Annoyance");
        }
        // Catch the base type:
        try {
            throw new Sneeze();
        } catch(Annoyance a) {
            System.out.println("Caught Annoyance");
        }
    }
}
输出
Caught Sneeze
Caught Annoyance

从这个demo中可以看出 抛Sneeze()异常的时候回西安就近捕获Sneeze() , 也就是谁离得近 用谁, 下面那个例子就是证明基类的异常也能处理子类的异常
如果父类在子类之前, 就可以屏蔽掉子类的异常了

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

被检查异常就是你必须catch或者在方法里进行异常说明. 而不检查异常就是把它作为cause放到RunTimeException()的构造器里 也就是new RunTimeException(cause), 有的异常是继承RunTimeException的, 对于不是的,需要将这个异常作为cause传进RunTimeException里面

// exceptions/TurnOffChecking.java
// "Turning off" Checked exceptions
import java.io.*;
class WrapCheckedException {
    void throwRuntimeException(int type) {
        try {
            switch(type) {
                case 0: throw new FileNotFoundException();
                case 1: throw new IOException();
                case 2: throw new
                        RuntimeException("Where am I?");
                default: return;
            }
        } catch(IOException | RuntimeException e) {
            // Adapt to unchecked:
            throw new RuntimeException(e);
        }
    }
}
class SomeOtherException extends Exception {}
public class TurnOffChecking {
    public static void main(String[] args) {
        WrapCheckedException wce =
                new WrapCheckedException();
        // You can call throwRuntimeException() without
        // a try block, and let RuntimeExceptions
        // leave the method:
        wce.throwRuntimeException(3);
        // Or you can choose to catch exceptions:
        for(int i = 0; i < 4; i++)
            try {
                if(i < 3)
                    wce.throwRuntimeException(i);
                else
                    throw new SomeOtherException();
            } catch(SomeOtherException e) {
                System.out.println(
                        "SomeOtherException: " + e);
            } catch(RuntimeException re) {
                try {
                    throw re.getCause();
                } catch(FileNotFoundException e) {
                    System.out.println(
                            "FileNotFoundException: " + e);
                } catch(IOException e) {
                    System.out.println("IOException: " + e);
                } catch(Throwable e) {
                    System.out.println("Throwable: " + e);
                }
            }
    }
}
输出
FileNotFoundException: java.io.FileNotFoundException
IOException: java.io.IOException
Throwable: java.lang.RuntimeException: Where am I?
SomeOtherException: SomeOtherException

从这个demo中你可以看到在WrapCheckedException类中, FileNotFoundException()是继承IOException(); 所以在try中只捕获IOException();即可,就将对应的Exception作为cause传进RuntimeException,这样就保存了最原始的报错信息

在TurnOffChecking中通过getCause();就能拿到这个源异常
当然另一种方法就是你让异常继承RunTimeException