try-with-resources
try-with-resources语句是一个声明一个或多个资源的try语句。一个资源作为一个对象,必须在程序结束之后关闭。try-with-resources语句确保在语句的最后每个资源都被关闭,任何实现了java.lang.AutoCloseable和java.io.Closeable的对象都可以使用try-with-resource来实现异常处理和关闭资源。
下面通过对比来体会这个新特新
JDK1.7之前:
/**
* JDK1.7之前我们必须在finally块中手动关闭资源,否则会导致资源的泄露
* @author Liao
*
*/
public class PreJDK7 {
public static String readFirstLingFromFile(String path) throws IOException {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(path));
return br.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {//必须在这里关闭资源
if (br != null)
br.close();
}
return null;
}
}
JDK1.7及以后版本
/**
* JDK1.7之后就可以使用try-with-resources,不需要
* 我们在finally块中手动关闭资源
* @author Liao
*/
public class AboveJDK7 {
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
}
通过上面的对比,try-with-resources的优点
1.代码精炼,在JDK1.7之前都有finally块,如果使用一些矿建可能会将finally块交由框架处理,如Spring。JDK及以后的版本只要资源类实现了AutoCloseable或Closeable程序在执行完try块后会自动close所使用的资源无论br.readLine()是否抛出异常,我估计针对JDK1.7像Spring这些框架也会做出一些比较大的调整。
2.代码更完全。在出现资源泄漏的程序中,很多情况是开发人员没有或者开发人员没有正确的关闭资源所导致的。JDK1.7之后采用try-with-resources的方式,则可以将资源关闭这种与业务实现没有很大直接关系的工作交给JVM完成。省去了部分开发中可能出现的代码风险。
异常抛出顺序
在JDK1.7之前如果rd.readLine()与rd.close()都抛出异常则只会抛出finally块中的异常,不会抛出rd.readLine()中的异常,这样经常会导致得到的异常信息不是调用程序想要得到的。
在JDK1.7及以后采用了try-with-resource机制,如果在try-with-resource声明中抛出异常(如文件无法打开或无法关闭)的同时rd.readLine()也抛出异常,则只会抛出rd.readLine()的异常。
try-with-resource可以声明多个资源。下面的例子是在一个ZIP文件中检索文件名并将检索后的文件存入一个txt文件中。
JDK1.7及以上版本:
public class AboveJDK7_2 {
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);
//打开zip文件,创建输出流
try (
java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
)
{//遍历文件写入txt
for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) {
String newLine = System.getProperty("line.separator");
String zipEntryName = ((java.util.zip.ZipEntry) entries.nextElement()).getName() + newLine;
writer.write(zipEntryName, 0, zipEntryName.length());
}
}
}
}
注:上面的例子,无论正常执行还是有异常抛出,zf和write都会被执行close()方法,不过需要注意的是在JVM里调用的顺序是与生命的顺序相反。在JVM中调用的顺讯为:
writer.close();
zf.close();
所以在使用时一定要注意资源关闭的顺序。
catch多种异常并抛出新的异常
1.catch多种异常抛出一种异常
在JDK1.7之前catch多个异常是这样的:
try{
//逻辑代码
}catch (IOException ex) {
logger.log(ex);
throw new SpecialException();
catch (SQLException ex) {
logger.log(ex);
throw new SpecialException();
}
从上述代码中可以看出这样写非常的难看,并且会出现许多重复的代码。
JDK1.7及以后可以这样:
try{
//逻辑代码
}catch (IOException | SQLException ex) {
logger.log(ex);
throw new SpecialException();
}
注:上面例子中的ex是隐式的final不可以在catch块中改变ex。
2.Rethrowing Exception with more inclusive Type Checking
在JDK1.7以前的版本,在方法声明中声明抛出的异常如果在方法体内没有抛出时不被允许的,如下:
static class FirstException extends Exception {
}
static class SecondException extends Exception {
}
public void rethrowException(String exceptionName) throws Exception {
try {
if (exceptionName.equals("First")) {
//如果异常名称为"First",则抛出异常一
throw new FirstException();
} else {
//否则的话,则抛出异常二
throw new SecondException();
}
} catch (Exception e) {
throw e;
}
}
上述代码其实很多开发人员都不喜欢,有的可能会提出一些改进方法,但无法做到非常精确的方法异常声明。
JDK1.7及以后版本:
static class FirstException extends Exception {
}
static class SecondException extends Exception {
}
public void rethrowException(String exceptionName) throws Exception, FirstException, SecondException {
try {
// 逻辑代码
}catch (Exception e) {
throw e;
}
}