详解 exception

Exception

java程序使用exception处理errors和别的异常事件。

1:什么是异常:

异常是指在程序执行期间,打乱了正常的程序指令流而出现的异常事件。

exception是 excepitonal  event 的 速记。

当方法中出现一个错误的时候,这个方法会创建一个对象切换到正在运行的系统,这个对象 叫做异常(exception)对象,异常对象中包含了出现错误的信息,异常的类型,以及此时运行程序的状态。创建一个异常对象在运行时系统中处理它的过程叫做抛出一个异常。

当一个方法抛出一个异常后,这个运行时系统尝试着去找处理这个异常的方式,这个一系列处理异常的方式是一系列顺序的方法调用的 列表,这个方法列表叫做调用栈。


                                  上图是调用的方法栈。

运行时系统搜索调用栈,找一个包含这个异常处理代码块的方法。这段代码块叫做异常处理程序(exception  handler),搜索的顺序是:从这个错我出现的方法开始,依次进行处理,这个顺序是方法调用的反顺序,例如以上调用栈---是从上到下查找的顺序。当合适的处理程序被找到,运行时系统传递这个异常到异常处理程序,异常处理程序考虑合适是根据抛出异常对象的类型和这个处理程序的类型匹配。


这个异常处理程序被选择叫做 捕获一个异常,如果运行时系统搜索调用栈中所有的方法但是没有找到合适的异常处理程序,这个运行时系统将终止。


                           上图为搜索调用栈找对应的异常处理程序的过程

2:异常的分类:

一:异常处理方式

java语言提供两种处理异常的方式:
1:try语句捕获一个异常,try必须提供一个异常的处理。
2:一个方法指定抛出一个异常,方法必须提供throws语句列出需要抛出的异常,相当于方法抛出一个指定的异常。
并不是所有的异常都是需要捕获或者特定的抛出的,为了理解这句话,看三种不同的异常分类。

二:异常的三种类型

1:检测性异常(Checked Exception):

一些写的好的程序应该提前考虑到这些异常,这种异常通常出现在写好的程序的内部,应用程序能够提前预测覆盖和捕获这种异常并从中恢复。例如:假设一个应用程序中需要用户输入文件名,然后通过传递这个文件名字到java.io.FileReader类中的构造方法中 打开这个文件,正常情况下,用户提供存在的文件名称,可读性的文件,因此FileReader的对象能够构造成功,这个应用程序能够正常的执行,但是有时用户提供了不存在的用户名称, 这个构造方法会抛出一个FileNotFoundException,一个好的应用程序应该捕获这个异常,通知用户这个错误,有可能提示输入正确的文件名。
检测性异常是Catch or Specify Requirement主题,除了Error , RuntimeException以及它们的子类,所有的其他异常都是检测性异常(Checked Exception)。
2:error
第二种异常是error,这种异常通常出现在应用程序的外部,不能够进行提前预测和从中恢复。例如,假设一个应用程序成功的打开一个文件,但是因为硬盘或者系统故障问题,会出现读取文件不成功将抛出一个java.io.IOError,应用程序可能为了通知用户出现了问题选择捕获这个异常,但是打印一个堆栈信息并退出也许会更有意义。
Errors不是Catch or Specify Requirement的主题,Error是一种异常表示Error和它的子类。
3:运行时异常
第三种异常时运行时异常,这种异常时应用程序的内部的异常,应用程序通常不能够预测和从中恢复。这通常表明程序的bug,例如逻辑错误或者不正确的使用了API等,例如考虑上面的传递文件名到java.io.FileReader类的构造方法中创建对象的FileReader对象的例子,如果因为逻辑错误传递给构造方法一个null,构造方法将抛出一个NullPointException空指针异常,应用程序能够捕获这个异常,但是消除出现异常的这个bug也许更有意义。
运行时异常不是一种Catch or Specify Requirement的主题。运行时异常显示RuntimeException以及它的子类。

Error和runtime exception(运行时异常) 都叫做非检测性异常(unchecked exceptions)。

一些程序员认为 Catch or Specify Requirement是一种有严重缺陷的异常机制,想通过使用unchecked exception 代替 checked exception,通常是不建议。

3:捕获处理异常

1:解释异常

这一小节描述如何使用try ,catch,finally语句块去实现异常处理程序处理,在Java SE 7 中也介绍了 try-with-resources语句,try-with-resources特别适用于关闭使用的资源,例如流。
下列例子定义和实现了ListOfNumbers类,当构造的时候,包含了一个ArrayList---包含0-9是个Integer元素,这个类也定义了一个writeList方法,写这些数字进入Output.txt文件中:例子如下:

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,
第二次红体字是调用ArrayList类的get方法,如果 i 参数值小于0或者大于SIZE,将抛出一个IndexOutOfBoundsException异常。

如果试着编译ListOfNumbers类,编译器将打印出一个错误信息:the exception thrown by the FileWriterconstructor,但是它不打印出这个错误信息get异常抛出的信息,这是因为FileWriter构造方法抛出的IOException是一个检测性异常(checked exception),get方法抛出的异常IndexOutOfBoundsException是一个运行时异常,即非检测性异常(unchecked exception)。现在你已经熟悉了ListOfNumbers类,在其内部那些地方会抛出异常,我们能够写异常处理程序去捕获这些异常。

八卦:
检测性异常:编译程序的时候会进行检测,如果没有进行处理,不能通过编译。
非检测性异常:编译程序的时候能够通过检测,通常是外部环境(硬盘或者系统故障)等不可检测预见的Error和内部环境(逻辑错误等)的RuntimeException。

2:try 块

构造异常处理程序的第一步是:把有可能抛出异常的代码块写在try块中,通常一个try块如下所示:
try{
 code
}
catch and finally  块

code是一行或者多行一定抛出异常的代码块, 把有可能抛出异常的语句放在try中,以上例子中的关于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块:直接在try块的后面提供,在try结束和catch开始处没有其他代码,示例如下:
try {

} catch (ExceptionType name) {

} catch (ExceptionType name) {

}
每一个catch块是一种异常处理程序,处理一类异常,异常类型在()中包含,这个异常类型声明在catch中的参数必须是一个继承自Throwable类的类名,处理程序能够引用异常类型名。
如果异常处理程序被调用,catch块 中包含的代码会被执行,运行时系统调用异常处理程序当抛出的异常类型是第一个和调用栈中的类型匹配的时候,如果抛出的异常对象是合法的,并且和catch中的参数类型一致,系统认为它们是匹配的。
下面是writeList方法中的两种异常处理,两种的类型的检测性异常(checked exception)能够被抛出在try块中。
代码如下所示:
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 和 IOException都是检测性异常(checked exception)。

两个处理都打印出错误的信息,第二个异常处理除了打印出异常信息什么也不做,捕获了IOException说明那不是第一种处理,第二个异常处理允许程序继续执行。
第一个异常处理除了打印出错误信息,也抛出了用户自定义异常,在以上例子中,当FileNotFoundException 被捕获时,调用SampleException 抛出,这是程序处理异常的特定情况下的特定的方。

异常处理除了打印出错误信息和终止程序还能做许多其他的,能够做一些错误的恢复,提示用户去做决定,或者使用chained exceptions处理把错误交给更高级的处理。

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新出功能)

在try-with-resources的使用中在try中声明了一个或者更多的资源(resource),一个资源(resource)是一个对象在程序完成后必须释放(关闭)的对象,try-with-resources语句确保在语句的末尾每一个资源被释放(关闭)。资源是指一些实现了java.lang.AutoCloseable或者java.io.Closeable接口。
下面例子是从一个文件中读一行,它使用一个BufferedReader的对象从文件中读数据,BufferedReader是一个在程序完成后必须被关闭的资源,例子如下:
  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语句块是否正常的完成,它都将关闭。

在JDK7以前的版本中,能够使用finally块确保资源关闭,不管try块中如何结束,下面的例子使用finally块代替 try-with-resources语句:
	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及后来的版本,能够检索到挂起异常。

也能够声明更多的资源在try-with-resources.中,下面的例子检索包下的zip文件,创建一个包含这写名字的文本文件。

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的方法调用和创建相反的方法。

下列例子使用try-with-resources自动关闭java.sql.Statement的对象。
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.
注意:try-with-resources块可以像普通的try块一样有 catch和finally块,在try-with-resources语句块中,catch和finally是在resources被关闭之后。

挂起异常:
异常能够被抛出从关联的try-with-resources块中,在以上例子writeToFileZipFileContents中,异常能够从try块中抛出。两个异常能够被抛出从try-with-resources中,当试着关闭ZipFile和BufferedWriter对象的时候,如果一个异常被抛出从一个try块中和一个或者更多的异常被抛出从try-with-resources中,异常从try-with-resources中抛出的异常会被挂起,异常通过writeToFileZipFileContents方法向上抛出,你能访问这些挂起的异常通过从try块中抛出的异常调用Throwable.getSuppressed方法。

实现了AutoCloseable或者Closeable接口:
Java doc中列出了实现 AutoCloseable 和 Closeable接口的类,Closeable接口继承自AutoCloseable接口,当AutoCloseable接口的close方法抛出一个Excepiton时,Closeable接口的close方法抛出IOException异常,因此AutoCloseable的子类能够重写close方法,抛出特定的异常。

关于开始的例子的完整版:
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块后的第一个语句。
以下是抛出IOException的输出:
Entering try statement
Caught IOException: OutFile.txt
PrintWriter not open 
另外一种情况try块正常结束:
这种情况下所有的语句成功执行没有异常发生,执行完try块以后,运行时系统传递控制到finally块,因为所以的都是成功执行,PrintWriter是打开的,当执行到finally时,关闭PrintWriter关闭,再一次的,finally块完成执行,程序继续执行finally块结束后的语句块,下面是没有抛出异常的执行结果:
Entering try statement
Closing PrintWriter







 




 

posted @ 2013-04-02 22:47  坚固66  阅读(571)  评论(0编辑  收藏  举报