Java异常处理新特性

来源:互联网 发布:安卓刷机软件哪个最好 编辑:程序博客网 时间:2024/04/30 19:45

在Java7中Java对其自身的异常处理机制进行了改进,同时增加了很多新的特性,这些特性对于编写更加可靠的程序有着非常大的帮助作用。这里我们对其进行详细的介绍。

1 try-with-resources语句

    Java7中提供了一种更为简单实用的用于处理资源使用异常处理的特性,称为try-with-resources,这个所谓的try-with-resources,是个语法糖。实际上就是自动调用资源的close()函数。使用try-with-resources的语法可以实现资源的自动回收处理,是代码更为简洁,运行更为稳定。

    如在Java7之前,我们在处理资源使用的代码时,通常会采用try-catch-finally语句来保证异常被处理以及资源被释放。如下所示:

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块中抛出的异常被抑制了,同时可能造成资源泻漏。那么在Java7中,我们可以利用try-with-resources特性来改写上面的程序,如下所示:

static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path)))    {
        return br.readLine();
  }
}

    上述实现try语句无论正常地完成或是发生意外,BufferedReader都将会自动关闭。这就是try-with-resources特性所带来的优势,之所以会有这样的优势,是其中的资源类实现了Java的AutoCloseable接口,该接口只有一个close()方法,在退出try语句后会自动被执行,在Java7中很多涉及资源使用的类都实现了AutoClosable接口。如JDBC操作,如下代码所示:

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);
      }
    }

在JDK7中只要实现了AutoCloseable或Closeable接口的类或接口,都可以使用try-with-resource来实现异常处理和资源关闭。我们也可以自己实现该接口,然后利用try-with-resource特性控制资源释放。如下所示:

public class MyResource implements AutoCloseable {

 

@Override

public void close() throws Exception {

System.out.println("Close resource!");

}

 

public void readResource() {

System.out.println("Read resource!");

}

 

}

try-resource单元测试:

1

2

3

4

5

6

@Test

public void testCloseResource() throws Exception {

try(MyResource autoCloseable = new MyResource()) {

autoCloseable.readResource();

}

}

另外,一个 try-with-resources语句可以像普通的 try 语句那样有 catch  finally 块。在try-with-resources语句中,任意的 catch 或者 finally 块都是在声明的资源被关闭以后才运行。

   也可以在一个 try-with-resources语句中声明一个或多个资源。下面的例子检索zip文件 zipFileName 中所有文件的名称并创建一个包含那些文件名称的文本文件:

    public static void writeToFileZipFileContents(String zipFileName, String outputFileName)
      throws java.io.IOException {
 
      java.nio.charset.Charset charset = java.nio.charset.Charset.forName("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-resources 语句包含两个由分号隔开的声明: ZipFile 和 BufferedWriter。当代码块直接伴随着它正常地或由于一个异常终止时, BufferedWriter 和 ZipFile 对象的 close 方法以这种顺序自动地调用 。注意:资源的 close 方法调用顺序与它们的创建顺序相反

2捕获多个异常

   在Java7中,可以在同一个catch分支中捕获多个异常类型,如下所示:

try{

  可能抛出异常的代码

}catch(FileNotFoundException|UnkonwnHostExceptionex){

  对于丢失文件和未知主机等紧急情况的处理

}catch(IOException ex){

  对于其他IO问题的处理

}

   这种机制不仅使代码更加简洁,而且会使效率更高。但是只有当捕获的异常均不是其他异常的子类时,才能使用此方式,同时捕获多异常时,异常变量会被隐式的声明为final类型,因此不能在异常处理时对异常对象赋值,如在catch(FileNotFoundException|UnkonwnHostExceptionex){......}中对ex对象赋值是非法的。

3异常重新抛出

    另一个升级是编译器对重新抛出异常(rethrown exceptions)的处理。这一特性允许在一个方法声明的throws从句中指定更多特定的异常类型。Java SE 7 的编译器能够对再次抛出的异常(rethrown exception)做出更精确的分析。这使得你可以在一个方法声明的throws从句中指定更具体的异常类型。

如下所示:

1.  static class FirstException extends Exception { }
2.  static class SecondException extends Exception { }
3.   
4.  public void rethrowException(String exceptionName) throws Exception {
5.      try {
6.          if (exceptionName.equals("First")) {
7.              throw new FirstException();
8.          } else {
9.              throw new SecondException();
10.        }
11.    } catch (Exception e) {
12.        throw e;
13.    }
14.}

    这个例子中的try语句块可能会抛出FirstException或者SecondException类型的异常。设想一下,你想在rethrowException方法声明的throws从句中指定这些异常类型。在JavaSE 7之前的版本,你无法做到。因为在catch子句中的异常参数ejava.lang.Exception类型的,catch子句对外抛出异常参数e,你只能在rethrowException方法声明的throws从句中指定抛出的异常类型为java.lang.Exception (或其父类java.lang.Throwable)。

    不过,在Java SE 7中,你可以在rethrowException方法声明的throws从句中指定抛出的异常类型为FirstExceptionSecondException。Java SE 7的编译器能够判定这个被throw语句抛出的异常参数e肯定是来自于try子句,而try子句只会抛出FirstExceptionSecondException类型的异常。尽管catch子句的异常参数ejava.lang.Exception类型,但是编译器可以判断出它是FirstExceptionSecondException类型的一个实例:

1.  public void rethrowException(String exceptionName) throws FirstException, SecondException {
2.      try {
3.          // ...
4.      }
5.      catch (Exception e) {
6.          throw e;
7.      }
8.  }

    不过,如果catch捕获的异常变量在catch子句中被重新赋值,那么异常类型检查的分析将不会启用,因此在这种情况下,你不得不在方法声明的throws从句中指定异常类型为java.lang.Exception。更具体地说,从Java SE 7开始,当你在单个catch子句中声明一种或多种类型的异常,并且重新抛出这些被捕获的异常时,需符合下列条件,编译器才会对再次抛出的异常进行类型验证:

  • try子句会抛出该异常。
  • 在此之前,没有其他的catch子句捕获该异常。
  • 该异常类型是catch子句捕获的多个异常中的一个异常类型的父类或子类。

4更简单的处理反射方法的异常

   在Java7之前,当调用一个反射方法时,不得不捕获多个不相关的检查期异常。比如下面的代码:

public class Main{

 public static void main(String[] args){

   ......

   Class.forName("ClassName").getMethod("Methodname").invoke(null,newString[]{});

   ......

 }

}

上述代码中通过Java反射技术动态加载一个类,并调用它的某个方法,此时该段代码在执行时,你必须去捕获很多反射相关的异常,如:ClassNotFoundException、NoSuchMethodException、IllegalAccessException、InvocationTargetException等等。

   当然在这里可以使用前面介绍的Java7的异常处理新特性,同时来捕获多个异常将这些异常组合到一个分支语句中,如下所示:

catch(ClassNotFoundException|NoSuchMethodException|IllegalAccessException|InvocationTargetExceptionex){

......

}

但是这样会存在几个问题,首先很明显代码量显得有些多;其次更为重要的一点是,这些异常对象互无关联不成体系。在这个处理反射相关部分上,没有为相关异常提供一个公共父类的设计,在Java中一直是一个不大不小的问题,有些时候的确会让人感到有些痛苦。

   这个问题在Java7中得到了完善,通过引入一个新的反射操作相关异常的父类ReflectiveOperationException,修复了之前设计上的缺陷,这样的话就可以通过这一异常来捕获所有其他反射操作相关的子类异常,时代更为简洁更成体系。如下所示:

catch(ReflectiveOperationExceptionex){

......

}
0 0
原创粉丝点击