详解 exception
Exception
java程序使用exception处理errors和别的异常事件。
1:什么是异常:
异常是指在程序执行期间,打乱了正常的程序指令流而出现的异常事件。
exception是 excepitonal event 的 速记。
当方法中出现一个错误的时候,这个方法会创建一个对象切换到正在运行的系统,这个对象 叫做异常(exception)对象,异常对象中包含了出现错误的信息,异常的类型,以及此时运行程序的状态。创建一个异常对象在运行时系统中处理它的过程叫做抛出一个异常。
当一个方法抛出一个异常后,这个运行时系统尝试着去找处理这个异常的方式,这个一系列处理异常的方式是一系列顺序的方法调用的 列表,这个方法列表叫做调用栈。
上图是调用的方法栈。
运行时系统搜索调用栈,找一个包含这个异常处理代码块的方法。这段代码块叫做异常处理程序(exception handler),搜索的顺序是:从这个错我出现的方法开始,依次进行处理,这个顺序是方法调用的反顺序,例如以上调用栈---是从上到下查找的顺序。当合适的处理程序被找到,运行时系统传递这个异常到异常处理程序,异常处理程序考虑合适是根据抛出异常对象的类型和这个处理程序的类型匹配。
这个异常处理程序被选择叫做 捕获一个异常,如果运行时系统搜索调用栈中所有的方法但是没有找到合适的异常处理程序,这个运行时系统将终止。
上图为搜索调用栈找对应的异常处理程序的过程
2:异常的分类:
一:异常处理方式
二:异常的三种类型
一些程序员认为 Catch or Specify Requirement是一种有严重缺陷的异常机制,想通过使用unchecked exception 代替 checked exception,通常是不建议。
3:捕获处理异常
1:解释异常
package test.exception; import java.io.FileWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; public class ListOfNumbers { private List<Integer> list; private static final int SIZE = 10; public ListOfNumbers(){ list = new ArrayList<Integer>(SIZE); for(int i = 0; i<SIZE; i++){ list.add(new Integer(i)); } } public void writeList(){ PrintWriter out = new PrintWriter(new FileWriter("output.txt")); for(int i =0;i<SIZE;i++){ out.println("Values at "+i+" = "+list.get(i)); } out.close(); } }以上代码中第一次红体字是调用了一个构造方法,这个构造方法初始化了一个文件输出流,如果这个文件不打开,这个构造方法将抛出一个IOException,
FileWriter
constructor,但是它不打印出这个错误信息get异常抛出的信息,这是因为FileWriter构造方法抛出的IOException是一个检测性异常(checked exception),get方法抛出的异常IndexOutOfBoundsException是一个运行时异常,即非检测性异常(unchecked exception)。现在你已经熟悉了ListOfNumbers类,在其内部那些地方会抛出异常,我们能够写异常处理程序去捕获这些异常。
2:try 块
public void writeList() { try{ out= new PrintWriter(new FileWriter("output.txt")); for(int i =0;i<SIZE;i++){ out.println("Values at "+i+" = "+list.get(i)); } } out.close(); }
为了捕获异常必须有相关的catch块
3:catch 块
try { } catch (ExceptionType name) { } catch (ExceptionType name) { }每一个catch块是一种异常处理程序,处理一类异常,异常类型在()中包含,这个异常类型声明在catch中的参数必须是一个继承自Throwable类的类名,处理程序能够引用异常类型名。
public void writeList() throws SampleException { try{ out= new PrintWriter(new FileWriter("output.txt")); for(int i =0;i<SIZE;i++){ out.println("Values at "+i+" = "+list.get(i)); } }catch(FileNotFoundException e1){ System.err.println("FileNotFoundException: " + e1.getMessage()); throw new SampleException(); }catch(IOException e2){ System.err.println("Caught IOException: " + e2.getMessage()); e2.printStackTrace(); } out.close(); }
SampleException的声明如下:
package test.exception; public class SampleException extends Throwable { public SampleException() { // TODO Auto-generated constructor stub System.out.println("SampleException............."); } }
FileNotFoundException
被捕获时,调用SampleException
抛出,这是程序处理异常的特定情况下的特定的方。
4:finally 块
当try块存在的时候,finally块总是执行,即使一个不希望的异常出现了,finally块也继续执行,finally块不仅仅用于异常处理,它让程序员避免了意外的代码清除绕过 return,continue或者break,把清除代码(最后必须执行的代码)放入finally块中是一个很好的选择,即使没有期望的异常的时候。
注意:当try或者catch块在执行的时候,JVM退出了,finally块也许不执行,同样的,如果执行try或者catch块的线程是中断了或者被killed了,即使整个应用程序继续执行,finally块也许不执行。
回到writeList方法中:
在以下三种方式中的一种,try块能够退出。
1:new FileWriter语句失败了,抛出一个IOException。
2:get(i)方法失败了,抛出一个ArrayIndexOutOfBoundsException
3:方法中的语句都成功执行,程序正常退出。
不管try块以哪一种方式退出,finally块总是执行,因此finally块是一个很好的cleanup的地方。
代码如下:
finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } }
重要:finally块是一个关键的工具---防止资源泄露。当关闭一个文件或者恢复一个资源,把代码放在finally块中确保资源总是被恢复。如果使用JDK7或者以后的版本,可以考虑使用try-with-resources语句在这种情况下,能够自动的释放不需要的系统资源。
5:try-with-resources语句(JDK新出功能)
static String readFirstLineFromFile(String path) throws IOException{ try( BufferedReader br = new BufferedReader(new FileReader(path));){ return br.readLine(); } }
在这个例子中,资源声明在try-with-resources中的是BufferedReader,这个资源出现在try后的括号中,BufferedReader在JDK7及以后的版本实现了 java.lang.AutoCloseable接口,因为BufferedReader实例是声明在try语句块中,不管try语句块是否正常的完成,它都将关闭。
static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException { BufferedReader br = new BufferedReader(new FileReader(path)); try { return br.readLine(); } finally { if (br != null) br.close(); } }
在以上例子中,如果readLine()和 close()都抛出异常,方法 readFirstLineFromFileWithFinallyBlock()抛出异常从finally块中,从try块中抛出的异常将要被挂起。在readFirstLineFromFile例子中相反,如果异常从try块中和try-with-resources中抛出,readFirstLineFromFile从try块中抛出异常,从try-with-resources中抛出的异常被挂起,在JDK7及后来的版本,能够检索到挂起异常。
public static void writeToFileZipFileContents(String zipFileName, String outputFileName) throws java.io.IOException { java.nio.charset.Charset charset = java.nio.charset.StandardCharsets.US_ASCII; java.nio.file.Path outputFilePath = java.nio.file.Paths.get(outputFileName); // Open zip file and create output file with // try-with-resources statement try (java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName); java.io.BufferedWriter writer = java.nio.file.Files .newBufferedWriter(outputFilePath, charset)) { // Enumerate each entry for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) { // Get the entry name and write it to the output file String newLine = System.getProperty("line.separator"); String zipEntryName = ((java.util.zip.ZipEntry) entries .nextElement()).getName() + newLine; writer.write(zipEntryName, 0, zipEntryName.length()); } } }
在以上例子中,try-with-catch块使用了两个申明通过分号分开,ZipFile和BufferedWriter,当这个代码块直接终止,或者因为异常而终止,BufferedWriter和 ZipFile对象的close方法自动被有顺序的调用,注意Resources的方法调用和创建相反的方法。
public static void viewTable(Connection con) throws SQLException { String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES"; try (Statement stmt = con.createStatement()) { ResultSet rs = stmt.executeQuery(query); while (rs.next()) { String coffeeName = rs.getString("COF_NAME"); int supplierID = rs.getInt("SUP_ID"); float price = rs.getFloat("PRICE"); int sales = rs.getInt("SALES"); int total = rs.getInt("TOTAL"); System.out.println(coffeeName + ", " + supplierID + ", " + price + ", " + sales + ", " + total); } } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } }
java.sql.Statement
Resources是使用的JDBC4.1以后的API.
public void writeList() { PrintWriter out = null; try { System.out.println("Entering" + " try statement"); out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < SIZE; i++) out.println("Value at: " + i + " = " + vector.elementAt(i)); } catch (ArrayIndexOutOfBoundsException e) { System.err.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); } finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } } }
两种执行情况:
创建FileWriter对象失败有很多原因,如果程序不能创建或者写入显示的文件,将会抛出一个IOException异常,当FileWriter抛出一个异常的IOException的时候,运行时系统立刻停止try块的执行,调用的方法不完全执行了,运行时系统开始调用方法调用栈的顶端找到合适匹配的异常进行处理,在这个例子中,当IOException出现的时候,FileWriter构造方法在调用战斗顶端,但是FileWriter没有一个合适的异常处理程序,因此运行时系统检查下一个方法,即writeList方法,在这个方法的调用栈中,writeList方法有两个异常处理程序,一个IOException和一个IndexOutOfBoundsException,运行时系统顺序的检测在try语句块后出现的catch种类,第一个处理程序的参数类型是IndexOutOfBoundsException,这和抛出的异常不匹配,因此运行时系统检测下一个异常处理程序---IOException,运行时系统找到了匹配的异常处理程序,catch块中的代码被执行。
异常处理程序执行完毕后,运行时系统传递控制到finally块,不管catch是否被调用,finally块中的代码执行。在这个例子中FileWriter从来没有打开,因此不需要继续关闭,当finally执行完毕后,程序继续执行finally块后的第一个语句。
Entering try statement Caught IOException: OutFile.txt PrintWriter not open另外一种情况try块正常结束:
Entering try statement Closing PrintWriter