使用异常能下降处理错误代码的复杂程度,而且将错误在一个地方进行处理,因而将“描述在正常行为过程当中作过什么事”的代码和“出了问题怎么办”的代码相分离java
异常情形指的是当前环境下没有足够的信息来让咱们解决这个问题,好比当除数为0发生的时候,咱们不知道除数为零表明着什么,(好比在算淘宝购物花销所占百分比的时候,你发现你这个月根本没花钱,总数是零),并不知道该如何处理这个异常。所以就要抛出异常
抛出异常后,会在堆上new出一个新的异常对象,而后当前执行路径终止,并弹出这个异常对象的引用,而后异常处理机制接管程序抓住这个异常,进行异常处理。
抛出异常的时候就像这样。编程
throw new NullPointerException()
异常也是对象,也有他本身的构造器,当在堆上new出一个异常对象的时候,他也能够执行不一样的对象构造器。标准异常类都有两个构造器:一个是默认构造器;另外一个是接受字符串参数,好比:api
throw new NullPointerException("t=null");
从效果上看,将这个异常给throw了,就像是从方法中“返回”同样,另外还能用抛出异常的方式从当前做用域退出。
可以抛出任意类型的Throwable对象,他是异常类型的根类。安全
首先要理解监控区域的概念,他是一段可能产生异常的代码,后面跟着处理这些可能出现的异常的代码。数据结构
若是在方法内部抛出了异常,那么这个方法将在抛出异常的时候结束,若是不但愿方法直接结束,能够在方法内设置一个块来“尝试”各类可能产生异常的方法。app
try{ //code }
抛出的异常必须在异常处理程序中获得处理。异常处理程序跟随在try块后ide
try{ }catch(Type1 id1){ //handle exceptions of type1 }catch(Type2 id2){ }
当在try块中出现异常后,异常被抛出,异常处理程序将负责搜寻与这个异常参数类型匹配的第一个异常处理程序,而后进行异常处理,一旦catch结束,则异常处理程序的查找过程结束。函数
异常处理有两种模型,Java支持终止模型,一旦异常被抛出,代表错误没法挽回,没法退回来继续执行以前出错的代码。
另外一种叫作恢复模型,指的是异常处理程序的工做是修正错误而后从新尝试调用出问题的方法,并认为第二次能成功。ui
要本身定义异常类,必须从已有的异常类继承,最好选择意思相近的异常类继承。this
package tij.exception; public class Test { void f() throws SimpleException { System.out.println("Throw SimpleException from f()"); throw new SimpleException(); } public static void main(String[] args) { Test t = new Test(); try { t.f(); } catch (SimpleException e) { System.out.println("Caught it"); } } } class SimpleException extends Exception {}
对于异常来讲,最重要的部分就是类名。
这个例子的结果被打印到了控制台上,也能够经过写入System.err将错误发送给标准错误流。一般这比把错误输出到System.out要好,由于System.out也许会被重定向。
package tij.exception; public class Test { static void f() throws MyException { System.out.println("Throw MyException from f()"); throw new MyException(); } static void g() throws MyException { System.out.println("Throw MyException from g()"); throw new MyException(); } public static void main(String[] args) { try { f(); } catch (MyException e) { e.printStackTrace(System.out); } try { g(); } catch (MyException e) { e.printStackTrace(); } } } class MyException extends Exception { public MyException() {} public MyException(String msg) { super(msg); } }
在异常处理程序中,调用了在Throwable类(Exception也是从他继承的)的printStackTrace方法,它将打印“从方法调用处知道异常抛出处的方法调用序列”,在上例中,若是信息被发送到了System.out,则将信息显示在输出中,若是使用默认版本e.printStackTrace则将输出到标准错误流。
看,上下两个颜色不同
可使用java.util.logging将输出记录到日志中。
package tij.exception; import java.io.PrintWriter; import java.io.StringWriter; import java.util.logging.Logger; public class Test { public static void main(String[] args) { try { throw new LoggingException(); } catch (LoggingException e) { System.err.println("Caught " + e); } try { throw new LoggingException(); } catch (LoggingException e) { System.err.println("Caught " + e); } } } class LoggingException extends Exception { private static Logger logger = Logger.getLogger("LoggingExcetpion"); public LoggingException() { StringWriter trace = new StringWriter(); printStackTrace(new PrintWriter(trace)); logger.severe(trace.toString()); } }
LoggingException首先建立了一个Logger对象,这个对象会将其输出发送到System.err。若是为了产生日志记录信息,如今咱们想把栈轨迹记录下来。而printStackTrace不会产生字符串,所以采用了带有PrintWriter参数的printStackTrace方法,这个方法会将栈轨迹的字符串信息传入到PrintWriter中,而后将栈轨迹信息穿进trace,而后运用severe方法向Logger写入信息。(其实书上没说这个是我猜的)
StringWriter我之前也没有见过,因而查了查api,用了一下发现挺好玩
一个字符流,能够用其回收在字符串缓冲区中的输出来构造字符串。
而后试了试这玩意有啥用
public class Test { public static void main(String[] args) { StringWriter str=new StringWriter(); PrintWriter pw=new PrintWriter(str); pw.print("abc"); System.out.println(str.toString()); } }
看起来这个StringWriter就是用来收集各类缓冲区里的字符串的。上面的代码也就好解释了。
好回到原来的问题,更常见的情形是,咱们须要捕捉与记录其余人编写的异常,所以能够在异常处理程序中生成日志信息
package tij.exception; import java.io.PrintWriter; import java.io.StringWriter; import java.util.logging.Logger; public class Test { private static Logger logger = Logger.getLogger("LoggingException"); static void logException(Exception e) { StringWriter trace = new StringWriter(); e.printStackTrace(new PrintWriter(trace)); logger.severe(trace.toString()); } public static void main(String[] args) { try { throw new NullPointerException(); } catch (NullPointerException e) { logException(e); } } }
还能够进一步自定义异常,好比加入额外的构造器和成员
package tij.exception; public class Test { static void f() throws MyException{ System.out.println("Throwing MyException from f()"); throw new MyException(); } static void g() throws MyException{ System.out.println("Throwing MyException from g()"); throw new MyException("Originated in g()",47); } public static void main(String[] args) { try{ f(); }catch(MyException e){ e.printStackTrace(); } try{ g(); }catch(MyException e){ e.printStackTrace(); } } } class MyException extends Exception{ private int x; public MyException(){} public MyException(String msg){ super(msg); } public MyException(String msg,int x){ super(msg); this.x=x; } public int val(){ return x; } public String getMessage(){ return "Detail Message: "+x+" "+super.getMessage(); } }
大段文字,没啥可说
一个简单的
catch(Exception e)
能够捕获全部类型的异常,由于Exception是全部与编程相关的异常的父类,但最好把他放在处理列表的末尾。
Exception做为父类天然不会有太多具体信息,但他能够调用从Throwable继承下来的方法好比
package tij.exception; import java.util.Arrays; public class Test { static void f() throws Exception{ System.out.println("Throwing Exception from f()"); throw new Exception(); } public static void main(String[] args) { try{ f(); }catch(Exception e){ System.out.println(Arrays.asList(e.getStackTrace())); e.printStackTrace(); } } }
看起来栈中的一帧指的是一次方法调用啊
当前异常处理程序里也能够从新抛出异常
catch(MyException e){ throw e; }
若是要想把当前的异常对象从新抛出,那再调用printStackTrace方法的时候将是原来异常抛出点的调用栈信息,没有从新抛出点的信息,要想更新这个信息,能够调用fillInStackTrace方法,这将返回一个Throwable对象,它是经过把当前调用栈信息填入原来那个异常对象而创建的。
package tij.exception; public class Test { static void f() throws Exception { System.out.println("originating the exception from f()"); throw new Exception(); } static void g() throws Exception { try { f(); } catch (Exception e) { System.out.println("Inside g().e.printStackTrace"); e.printStackTrace(System.out); throw e; } } static void h() throws Exception{ try{ f(); }catch(Exception e){ System.out.println("Inside h().e.printStackTrace"); e.printStackTrace(System.out); 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); } } }
对比输出结果,发如今主程序中的针对h的catch块中打印栈轨迹的时候,发现她只有两行,由于他捕捉到的异常实际上是(Exception)e.fillInStackTrace(),这实际上是一个新返回的异常,它只记录了本身这个位置的栈信息,由于他是一个新的异常。
恩要注意从新抛出的异常和原来的异常究竟是啥关系,极可能就没啥关系的
在捕获一个异常后抛出另外一个异常,并但愿吧原是一场的信息保留下来,这被称为异常链。全部Throwable的子类的构造器能够接受一个cause对象做为参数,cause表示原始异常对象。
package tij.exception; public class Test { public static void main(String[] args) { DynamicFields df = new DynamicFields(3); System.out.println(df); try { df.setField("d", "a value of d"); df.setField("killer47", 47); df.setField("fatkiller48", 48); System.out.println(df); df.setField("d", "a new value of d"); df.setField("thinkiller", 11); System.out.println("df:" + df); System.out.println("df.getField(\"d\")" + df.getField("d")); Object field = df.setField("d", null); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (DynamicFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class DynamicFieldException extends Exception {} class DynamicFields { private Object[][] fields; public DynamicFields(int initialSize) { this.fields = new Object[initialSize][2]; for (int i = 0; i < initialSize; i++) { fields[i] = new Object[]{null, null}; } } public String toString() { StringBuilder result = new StringBuilder(); for (Object[] object : fields) { result.append(object[0] + ": " + object[1] + "\n"); } return result.toString(); } private int hasField(String id) { for (int i = 0; i < fields.length; i++) { if (id.equals(fields[i][0])) return i; } return -1; } private int getFieldNumber(String id) throws NoSuchFieldException { int fieldNum = hasField(id); if (fieldNum == -1) { throw new NoSuchFieldException(); } return fieldNum; } private int makeField(String id) { for (int i = 0; i < fields.length; i++) { if (fields[i][0] == null) { fields[i][0] = id; return i; } } // 若是空间满了,那就在造一个空间 Object[][] temp = new Object[fields.length + 1][2]; for (int i = 0; i < fields.length; i++) { temp[i] = fields[i]; } temp[fields.length] = new Object[]{null, null}; fields = temp; return makeField(id); } public Object getField(String id) throws NoSuchFieldException { return fields[getFieldNumber(id)][1]; } public Object setField(String id, Object value) throws DynamicFieldException { if (value == null) { DynamicFieldException dfe = new DynamicFieldException(); dfe.initCause(new NullPointerException()); throw dfe; } int fieldNumber = hasField(id); if (fieldNumber == -1) { fieldNumber = makeField(id); } Object result = null; try { result = getField(id); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } fields[fieldNumber][1] = value; return result; } }
其实他就是完成了一个相似于map的数据结构,在
Object field = df.setField("d", null);
这段代码中,尝试插入一个value为null的对儿,他抛出了一个DynamicFieldException异常,这个异常是因为NullPointerException引发的,在结果中能够看到,虽然抛出的是DynamicFieldException,但NullPointerException也被记录了下来
Throwable对象能够分为两类:Error用来表示编译时和系统错误;Exception是能够被抛出的基本类型。
运行时异常发生的时候会自动被虚拟机抛出不必定要在异常说明中将它们列出来。
但若是不人工捕获这种异常,他会穿越全部执行路径直达main方法。
package tij.exception; public class Test { static void f() { throw new RuntimeException("From f()"); } static void g() { f(); } public static void main(String[] args) { g(); } }
对于这种异常咱们程序猿内心要有点B数,不处理的话出错了全崩了
对于一些代码,不管try块中是否有异常抛出,他们都应该执行。这一般适用于内存回收以外的状况。能够运用finally语句。
package tij.exception; public class Test { 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 (ThreeException e) { System.out.println("ThreeException"); } finally { System.out.println("in finally clause"); if (count == 2) break; } } } } class ThreeException extends Exception {}
(额= =Java内存回收机制和构析函数不是一个东西么?)
package tij.exception; public class Test { private static Switch sw = new Switch(); static void f() throws OnOffException1, OnOffException2 {} public static void main(String[] args) { try { sw.on(); f(); } catch (OnOffException1 e) { System.out.println("OnOffException1"); } catch (OnOffException2 e) { System.out.println("OnOffException2"); } finally { sw.off(); } } } class OnOffException1 extends Exception {} class OnOffException2 extends Exception {} class Switch { private boolean state = false; boolean read() { return this.state; } void on() { this.state = true; System.out.println(this); } void off() { this.state = false; System.out.println(this); } public String toString() { return state ? "on" : "off"; } }
能够保证sw最后都是关闭的。
finally总会执行,因此一个方法中,能够从多个点返回
package tij.exception; public class Test { static void f(int i) { try { System.out.println("Point 1"); if (i == 1) return; System.out.println("Point 2"); if (i == 2) return; System.out.println("Point 3"); if (i == 3) return; } finally { System.out.println("Performing cleanup"); } } public static void main(String[] args) { for (int i = 1; i <= 4; i++) { f(i); } } }
异常有时候会被轻易地忽略。
package tij.exception; public class Test { void f() throws VeryImportantException { throw new VeryImportantException(); } void dispose() throws HoHumException { throw new HoHumException(); } public static void main(String[] args) { try { Test t = new Test(); try { t.f(); } finally { t.dispose(); } } catch (Exception e) { System.out.println(e); } } } class VeryImportantException extends Exception { public String toString() { return "A very important exception!"; } } class HoHumException extends Exception { public String toString() { return "A trival exception"; } }
还有一种更加容易丢失的异常
package tij.exception; public class Test { @SuppressWarnings("finally") public static void main(String[] args) { try { throw new RuntimeException(); } finally { return; } } }
package tij.exception; public class Test { public static void main(String[] args) { try { StormyInning si = new StormyInning(); si.atBat(); } catch (PopFoul e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (RainedOut e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (BaseballException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } try { Inning i = new StormyInning(); i.atBat(); } catch (RainedOut e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Strike e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Foul e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (BaseballException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class BaseballException extends Exception {} class Foul extends BaseballException {} class Strike extends BaseballException {} abstract class Inning { public Inning() throws BaseballException {} public void event() throws BaseballException {} public abstract void atBat() throws Strike, Foul; public void walk() {} } class StormException extends Exception {} class RainedOut extends StormException {} class PopFoul extends Foul {} interface Storm { public void event() throws RainedOut; public void rainHard() throws RainedOut; } class StormyInning extends Inning implements Storm { // 对于构造方法来讲,你能够添加新的抛出异常,可是你必须也得抛出父类构造方法所声明的异常 public StormyInning() throws RainedOut, BaseballException {} public StormyInning(String s) throws Foul, BaseballException {} // 普通方法抛出的异常必须必须遵循父类,父类抛啥你抛啥,抛多了也不行,父类不抛你也不准抛,阿父真的很严格 // public void walk() throws PopFoul{} // 能够看到接口和父类中有一个相同的方法event,他们抛出了不一样的异常,前面说了继承方法不能多抛异常,因此即便是接口,也不能向父类中已经存在的方法添加新的抛出异常 // public void event() throws RainedOut{} // 但rainHard只在接口中出现了,一样也不能多抛其余异常 public void rainHard() throws RainedOut {} // 但庆幸的是即便父类或者接口的方法抛异常了,子类重写的方法能够不抛异常,就是说能够偷懒恩 public void event() {} // 而且子类抛出的异常能够遵循继承原则,下面这个函数中至关于把Strike异常忽略了,而后抛出了Foul异常的子类PopFoul public void atBat() throws PopFoul {} }
看书,书上这段写的超棒!
class InputFile { private BufferedReader in; InputFile(String fname) throws Exception { try { in = new BufferedReader(new FileReader(fname)); } catch (FileNotFoundException e) { System.out.println("Could not open " + fname); // 这个文件并无被成功的打开 throw e; } catch (Exception e) { try { in.close(); } catch (IOException e2) { System.out.println("in没有被成功关闭"); } throw e; } finally { // 不要关闭这个文件 } } String getLine() { String s; try { s = in.readLine(); } catch (IOException e) { throw new RuntimeException("readLine() failed"); } return s; } void dispose() { try { in.close(); System.out.println("dispose() successful"); } catch (IOException e2) { throw new RuntimeException("in.close() failed"); } } }
从中能够看出,若是in这个对象建立失败,他会抛出一个建立异常,而且它不须要关闭,由于他根本没有被成功建立出来;若是in这个对象若是建立成功了,但若是除了其余的岔子,这个in应该被关闭掉,这个对象的构造函数具有了这个功能。
public class Test { public static void main(String[] args) { try { InputFile in = new InputFile("src\\tij\\exception\\Test.java"); String s; try { while ((s = in.getLine()) != null) { } } catch (Exception e) { System.out.println("caught Exception in main"); e.printStackTrace(System.out); } finally { in.dispose(); } } catch (Exception e) { System.out.println("InputFile construction failed"); } } }
因为InputFile in这个对象知足两个特征:1.构造的时候可能产生异常。2.用完以后须要被清理。所以上面的try-catch嵌套用法是最安全的。由于它保证了:1.若是建立失败,直接抛出异常,这个对象不须要也不该该执行关闭方法(所以不能傻了吧唧的都把close丢finally块中)。2.若是建立成功,那么应该保证这个对象在用完以后关闭掉。
package tij.exception; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class Test { public static void main(String[] args) { NeedsCleanup nc1=new NeedsCleanup(); try{ }finally{ nc1.dispose(); } NeedsCleanup nc2=new NeedsCleanup(); NeedsCleanup nc3=new NeedsCleanup(); try{ }finally{ nc3.dispose(); nc2.dispose(); } try { NeedsCleanup2 nc4=new NeedsCleanup2(); try { NeedsCleanup2 nc5=new NeedsCleanup2(); try{ }finally{ nc5.dispose(); } } catch (ConstructionException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ nc4.dispose(); } } catch (ConstructionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class NeedsCleanup{ //个人构造不会出错 private static long counter=1; private final long id=counter++; public void dispose(){ System.out.println("NeedsCleanup "+id+" disposed"); } } class ConstructionException extends Exception{} class NeedsCleanup2 extends NeedsCleanup{ public NeedsCleanup2() throws ConstructionException{ } }
nc123都不会出错,而nc45均可能出错的,上面的方法虽然麻烦,可是可行且可靠。
从上到下,遵循继承
书上这段写的主要是思想方面的事,回来补,今天累= =马克
end