Java1.7新特性
1.switch语句支持字符串变量
public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) { String typeOfDay; switch (dayOfWeekArg) { case "Monday": typeOfDay = "Start of work week"; break; case "Tuesday": case "Wednesday": case "Thursday": typeOfDay = "Midweek"; break; case "Friday": typeOfDay = "End of work week"; break; case "Saturday": case "Sunday": typeOfDay = "Weekend"; break; default: throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg); } return typeOfDay; }
switch 语句比较表达式中的String对象和每个case标签关联的表达式,就好像它是在使用String.equals方法一样;因此,switch语句中 String对象的比较是大小写敏感的。相比于链式的if-then-else语句,Java编译器通常会从使用String对象的switch语句中生成更高效的字节码。
2.泛型实例化类型自动推断
以下两个语句等价:
ArrayList<String> al1 = new ArrayList<String>(); // Old ArrayList<String> al2 = new ArrayList<>(); // New
3. 新的整数字面表达方式 - "0b"前缀和"_"连数符
a. 表示二进制字面值的前缀0b。
比如以下三个变量的值相同:
byte b1 = 0b00100001; // New byte b2 = 0x21; // Old byte b3 = 33; // Old
b. 字面常量数字的下划线。用下划线连接整数提升其可读性,自身无含义,不可用在数字的起始和末尾。
Java编码语言对给数值型的字面值加下划线有严格的规定。如上所述,你只能在数字之间用下划线。你不能用把一个数字用下划线开头,或者已下划线结尾。这里有一些其它的不能在数值型字面值上用下划线的地方:
- 在数字的开始或结尾
- 对浮点型数字的小数点附件
- F或L下标的前面
- 该数值型字面值是字符串类型的时候
float pi1 = 3_.1415F; // 无效的; 不能在小数点之前有下划线 float pi2 = 3._1415F; // 无效的; 不能在小数点之后有下划线 long socialSecurityNumber1=999_99_9999_L;//无效的,不能在L下标之前加下划线 int a1 = _52; // 这是一个下划线开头的标识符,不是个数字 int a2 = 5_2; // 有效 int a3 = 52_; // 无效的,不能以下划线结尾 int a4 = 5_______2; // 有效的 int a5 = 0_x52; // 无效,不能在0x之间有下划线 int a6 = 0x_52; // 无效的,不能在数字开头有下划线 int a7 = 0x5_2; // 有效的 (16进制数字) int a8 = 0x52_; // 无效的,不能以下划线结尾 int a9 = 0_52; // 有效的(8进制数) int a10 = 05_2; // 有效的(8进制数) int a11 = 052_; // 无效的,不能以下划线结尾
4.在单个catch代码块中捕获多个异常,以及用升级版的类型检查重新抛出异常
在Java 7中,catch代码块得到了升级,用以在单个catch块中处理多个异常。如果你要捕获多个异常并且它们包含相似的代码,使用这一特性将会减少代码重复度。下面用一个例子来理解。
Java 7之前的版本:
catch (IOException ex) { logger.error(ex); throw new MyException(ex.getMessage()); catch (SQLException ex) { logger.error(ex); throw new MyException(ex.getMessage()); }catch (Exception ex) { logger.error(ex); throw new MyException(ex.getMessage()); }
在Java 7中,我们可以用一个catch块捕获所有这些异常:
catch(IOException | SQLException | Exception ex){ logger.error(ex); throw new MyException(ex.getMessage()); }
如果用一个catch块处理多个异常,可以用管道符(|)将它们分开,在这种情况下异常参数变量(ex)是定义为final的,所以不能被修改。这一特性将生成更少的字节码并减少代码冗余。
另一个升级是编译器对重新抛出异常(rethrown exceptions)的处理。这一特性允许在一个方法声明的throws从句中指定更多特定的异常类型。
与以前版本相比,Java SE 7 的编译器能够对再次抛出的异常(rethrown exception)做出更精确的分析。这使得你可以在一个方法声明的throws从句中指定更具体的异常类型。
我们先来看下面的一个例子:
static class FirstException extends Exception { } static class SecondException extends Exception { } public void rethrowException(String exceptionName) throws Exception { try { if (exceptionName.equals("First")) { throw new FirstException(); } else { throw new SecondException(); } } catch (Exception e) { throw e; } }
这个例子中的try语句块可能会抛出FirstException或者SecondException类型的异常。设想一下,你想在rethrowException方法声明的throws从句中指定这些异常类型。在Java SE 7之前的版本,你无法做到。因为在catch子句中的异常参数e是java.lang.Exception类型的,catch子句对外抛出异常参数e,你只能在rethrowException方法声明的throws从句中指定抛出的异常类型为java.lang.Exception (或其父类java.lang.Throwable)。
不过,在Java SE 7中,你可以在rethrowException方法声明的throws从句中指定抛出的异常类型为FirstException和SecondException。Java SE 7的编译器能够判定这个被throw语句抛出的异常参数e肯定是来自于try子句,而try子句只会抛出FirstException或SecondException类型的异常。尽管catch子句的异常参数e是java.lang.Exception类型,但是编译器可以判断出它是FirstException或SecondException类型的一个实例:
public class Java7MultipleExceptions { public static void main(String[] args) { try{ rethrow("abc"); }catch(FirstException | SecondException | ThirdException e){ //以下赋值将会在编译期抛出异常,因为e是final型的 //e = new Exception(); System.out.println(e.getMessage()); } } static void rethrow(String s) throws FirstException, SecondException, ThirdException { try { if (s.equals("First")) throw new FirstException("First"); else if (s.equals("Second")) throw new SecondException("Second"); else throw new ThirdException("Third"); } catch (Exception e) { //下面的赋值没有启用重新抛出异常的类型检查功能,这是Java 7的新特性 // e=new ThirdException(); throw e; } } static class FirstException extends Exception { public FirstException(String msg) { super(msg); } } static class SecondException extends Exception { public SecondException(String msg) { super(msg); } } static class ThirdException extends Exception { public ThirdException(String msg) { super(msg); } } }
不过,如果catch捕获的异常变量在catch子句中被重新赋值,那么异常类型检查的分析将不会启用,因此在这种情况下,你不得不在方法声明的throws从句中指定异常类型为java.lang.Exception。
更具体地说,从Java SE 7开始,当你在单个catch子句中声明一种或多种类型的异常,并且重新抛出这些被捕获的异常时,需符合下列条件,编译器才会对再次抛出的异常进行类型验证:
- try子句会抛出该异常。
- 在此之前,没有其他的catch子句捕获该异常。
- 该异常类型是catch子句捕获的多个异常中的一个异常类型的父类或子类。
5.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的优点
-
代码精炼,在JDK1.7之前都有finally块(这时可能会有一个问题,1.7后的finally将何去何从,还有用处吗?当然有用了,try-with-resources只是帮助我们避免了资源关闭的重复工作,但它并没有代替finally的作用啊,所以1.7后finally仍然是用来处理一定会被执行的代码块,不过这个功能使用场景不是很大。),如果使用一些扩建可能会将finally块交由框架处理,如spring。JDK及以后的版本只要资源类实现了AutoCloseable或Closeable程序在执行完try块后会自动close所使用的资源无论br.readLine()是否抛出异常,我估计针对JDK1.7像Spring这些框架也会做出一些比较大的调整。
-
代码更完全。在出现资源泄漏的程序中,很多情况是开发人员没有或者开发人员没有正确的关闭资源所导致的。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();
所以在使用时一定要注意资源关闭的顺序。