JAVA异常类详解

异常这一块知识点小而杂,整理一下便于以后查找。
1. Java异常Exception的结构分析
在这里插入图片描述
Throwable
  Throwable是 Java 语言中所有错误或异常的超类。
  Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。

Exception
  Exception及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。
RuntimeException
  RuntimeException代表编程错误。编译器不会检查RuntimeException异常,如果RuntimeException没有被捕获而直达main,那么程序在推出前将调用printStackTrace()方法。虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。
Error
  和Exception一样,Error也是Throwable的子类。表示编译时和系统错误。和RuntimeException一样,编译器也不会检查Error。
  
2.自定义异常

//: exceptions/LoggingExceptions.java
// An exception that reports through a Logger.
import com.sun.javafx.util.Logging;

import java.util.logging.*;
import java.io.*;

//自定义异常类必须继承已有异常类
class LoggingException extends Exception {
  private static Logger logger = Logger.getLogger("LoggingException");
  //可加入额外成员
  private int x;
  //覆盖getMessage
  public String getMessage(){
    return "Detail Message: "+x+" "+super.getMessage();
  }
  //可以有自己的构造器
  public LoggingException(String msg ,int i) {
    super(msg);
    this.x = i;
    //为了获取异常Sting,重载printStackTrace(),并将StringWriter传给PrintWriter构造器,
    // 调用StringWriter.toString()将获得异常String
    StringWriter trace = new StringWriter();
    printStackTrace(new PrintWriter(trace));
    //java.util.logging.Logger将输出发送到System.err
    logger.severe(trace.toString());
  }
}

public class LoggingExceptions {
  public static void main(String[] args) {
    try {
      throw new LoggingException("throw LoggingException from main", 5);
    } catch(LoggingException e) {
      //通过System.err将错误发送给标准错误流,可以通过System.setErr(PrintStream sttream)重定向
      System.err.println("Caught " + e);
    }
  }
} /* Output:
十月 03, 2018 12:33:14 下午 LoggingException <init>
严重: LoggingException: Detail Message: 5 throw LoggingException from main
   at LoggingExceptions.main(LoggingExceptions.java:33)

Caught LoggingException: Detail Message: 5 throw LoggingException from main
*///:~

自定义异常只需继承已有的异常类,并可加入额外的成员和构造器,重写覆盖已有的方法便可打印不同的信息。System.err将错误发送给标准错误流,通常比System.out要好,如果把结果送到System.err,就不会随System.out一起被重定向(Systrm.setOut(PrintStream sttream)),当然System.err也可以单独重定向。java.util.logging.Logger将输出记录到日志中。

3.常用方法
String getMessage():获取详细信息
String getLocalizedMessage():获取本地语言表示的详细信息
String toString()返回对Throwable的简单描述
void printStackTrace()打印Throwable和Throwable的调用栈轨迹,输出到标准错误流
void printStackTrace(PrintStream) 打印Throwable和Throwable的调用栈轨迹,输出到PrintStream
void printStackTrace(java.io.PrintWriter) 打印Throwable和Throwable的调用栈轨迹,输出到PrintWriter
StackTraceElement getStackTrace()返回栈轨迹数组,0是栈顶元素(Throwable被创建和抛出之处)

public class WhoCalled {
  static void f() {
    // Generate an exception to fill in the stack trace
    try {
      throw new Exception();
    } catch (Exception e) {
      //获得栈轨迹数组
      for(StackTraceElement ste : e.getStackTrace())
        //获得调用的方法名
        System.out.println(ste.getMethodName());
    }
  }
  static void g() { f(); }
  static void h() { g(); }
  public static void main(String[] args) {
    f();
    System.out.println("--------------------------------");
    g();
    System.out.println("--------------------------------");
    h();
  }
} /* Output:
f
main
--------------------------------
f
g
main
--------------------------------
f
g
h
main
*///:~

Throwable fillStackTrace() 在Throwable对象内部记录栈帧的当前状态

public class Rethrowing {
  public static void f() throws Exception {
    System.out.println("originating the 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()");
      //f->g->main
      e.printStackTrace(System.out);
      throw e;
    }
  }
  public static void h() throws Exception {
    try {
      f();
    } catch(Exception e) {
      System.out.println("Inside h(),e.printStackTrace()");
      e.printStackTrace(System.out);
      //有关原来异常发生点的信息会丢失,剩下与新抛出点有关信息:h->main,丢了f的栈轨迹
      throw (Exception)e.fillInStackTrace();
    }
  }
  public static void main(String[] args) {
    try {
      g();
    } catch(Exception e) {
      System.out.println("main: printStackTrace()");
      e.printStackTrace(System.out);
    }
    try {
      h();
    } catch(Exception e) {
      System.out.println("main: printStackTrace()");
      e.printStackTrace(System.out);
    }
  }
} /* Output:
originating the exception in f()
Inside g(),e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:7)
        at Rethrowing.g(Rethrowing.java:11)
        at Rethrowing.main(Rethrowing.java:29)
main: printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:7)
        at Rethrowing.g(Rethrowing.java:11)
        at Rethrowing.main(Rethrowing.java:29)
originating the exception in f()
Inside h(),e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:7)
        at Rethrowing.h(Rethrowing.java:20)
        at Rethrowing.main(Rethrowing.java:35)
main: printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.h(Rethrowing.java:24)
        at Rethrowing.main(Rethrowing.java:35)
*///:~

4.异常链
捕获一个异常后抛出另一个异常,并且希望把原是异常信息保存下来 ,这被称为异常链。所有Throwable的子类在构造器中都可以接受一个cause对象作为参数,这个cause就用来表示原始异常。Throwable子类中只有Error、Exception、RuntimeException三种基本异常类提供了带cause参数的构造器,其他不带cause参数构造器的异常,可以用initCause()方法。

class DynamicFieldsException extends Exception {
  DynamicFieldsException(){
    super();
  }
  //创建带有cause构造器
  DynamicFieldsException(Throwable cause){
    super(cause);
  }
}

public class DynamicFields {

  public static void main(String[] args) {
    int i = 3;
    //将try语句块放入循环中,便可回到异常抛出点,重新尝试调用有问题的方法
    while(i > 0){
      try{
        if(i == 3){
          DynamicFieldsException dfe = new DynamicFieldsException();
          //通过initCause生成异常链
          dfe.initCause(new NullPointerException());
          throw dfe;
        }else if (i==2){
          //通过构造器生成异常链
          throw new DynamicFieldsException(new IllegalArgumentException());
        }else{
          //如果代码块中无异常抛出,也可写成try-finally
          System.out.println("before --i: "+i);
        }
      }catch(DynamicFieldsException e) {
        e.printStackTrace(System.out);
      }
      //不管有没有异常被捕获,finally中的语句都将在方法返回前被执行
      finally{
        System.out.println("after  --i: "+--i);
      }
    }
  }
} /* Output:
DynamicFieldsException
   at DynamicFields.main(DynamicFields.java:23)
Caused by: java.lang.NullPointerException
   at DynamicFields.main(DynamicFields.java:25)
after  --i: 2
DynamicFieldsException: java.lang.IllegalArgumentException
   at DynamicFields.main(DynamicFields.java:29)
Caused by: java.lang.IllegalArgumentException
   ... 1 more
after  --i: 1
before --i: 1
after  --i: 0
*///:~

5.异常限制
1.派生类构造器异常说明必须包含基类构造器异常说明,即子类构造器抛出的异常必须大于等于父类构造器。
2.在继承和覆盖中,某个方法的异常说明变小了,即子类方法的异常说明要小于等于父类或接口中方法的异常说明

class ClassException extends Exception {}
class ClassException1 extends ClassException {}
class ClassException2 extends ClassException {}
class ClassException11 extends ClassException1 {}

abstract class ClassFather {
  public ClassFather() throws ClassException1 {}
  public void event() throws ClassException {}
  public abstract void atBat() throws ClassException1, ClassException2;
}
class InterfaceException extends Exception {}
class InterfaceException1 extends InterfaceException {}

interface Interface {
  public void event() throws InterfaceException1;
  public void rainHard() throws InterfaceException1;
}

public class StormyInning extends ClassFather implements Interface {
  //子类构造器抛出的异常必须大于等于父类构造器,ClassException≥ClassException1,可以包含别的异常
  public StormyInning() throws InterfaceException, ClassException {}
  public StormyInning(String s)throws  ClassException1 {}
  public void rainHard() throws InterfaceException1 {}
  //子类方法的异常说明要小于等于父类或接口中方法的异常说明
  public void atBat() throws ClassException11 {}
  //public void event() throws InterfaceException1,ClassException{}
  public void event(){};
} ///:~

6.main()作为一个方法也可以有异常说明

import java.io.*;

public class MainException {
  // 异常信息传递到控制台,main中不必try-catch
  public static void main(String[] args) throws Exception {
    FileInputStream file = new FileInputStream("MainException.java");
    file.close();
  }
} /* Output:
Exception in thread "main" java.io.FileNotFoundException: MainException.java (系统找不到指定的文件。)
   at java.io.FileInputStream.open0(Native Method)
   at java.io.FileInputStream.open(FileInputStream.java:195)
   at java.io.FileInputStream.<init>(FileInputStream.java:138)
   at java.io.FileInputStream.<init>(FileInputStream.java:93)
   at MainException.main(MainException.java:7)
*///:~

7.吞食包装
如果在捕获异常后不做任何处理,程序继续向下执行,则这个异常就会被吞食消失,可以通过包装成自定义异常继续向上抛出或者用getCause()方法将原始异常继续向上抛出。

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(Exception e) {
      //捕获异常后包装成别的异常向上抛出
      throw new RuntimeException(e);
    }
  }
}

class SomeOtherException extends Exception {}

public class TurnOffChecking {
  public static void main(String[] args) {
    WrapCheckedException wce = new WrapCheckedException();
    wce.throwRuntimeException(3);
    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);
        }
      }
  }
} /* Output:
FileNotFoundException: java.io.FileNotFoundException
IOException: java.io.IOException
Throwable: java.lang.RuntimeException: Where am I?
SomeOtherException: SomeOtherException
*///:~