Java7 中有一些令开发者很高兴的新特性,如switch语句中的使用字符串作为表达式,多catch异常处理,try-with-resources (自动资源管理),新的文件系统API, jvm扩展,支持动态类型语言,支持并行任务的fork/join框架,以及其他一些肯定会被社区支持的特性。
下面就概述以上功能,并提供适当的例子。 这里可以下载一个zip文件,其中包含在这篇文章中的代码片段。
语言增强
Java7 通过Project Coin 提供了一些新的语言特性,这些功能对开发者来说非常方便。
Diamond Operator
你可能已经注意到在使用泛型的时候,IDE经常会出现警告提示。例如,如果我们要声明一个使用trades类作为泛型的Map类时,我们会写出像下面的代码:
Map<String, List<Trade>> trades = new TreeMap<String, List<Trade>> ();
我们必须在等号两边都声明同样的类型,这并不是一件很方便的事情,显然右边的声明看上去有些多余。编译器能根据左边的声明推断右边的类型吗?之前是不行的,在Java7中的代码可以写成下面的样子:
Map<String, List<Trade>> trades = new TreeMap <> ();
是不是很酷?你不需要输入实例类型的完整列表。相反,你使用<>符号,这就是所谓的钻石操作符,请注意,虽然没有声明的钻石操作符是合法的, trades = new TreeMap () ),但它会使编译器生成几个类型安全警告。
在switch语句中使用的字符串
之前的switch语句只能使用原始数据类型或枚举类型。Java 7中引入另一种类型,我们可以在switch语句中使用字符串String类型。
假设我们需要根据状态处理一个交易, 到现在为止,我们使用if - else语句。
private void processTrade(Trade t) {
String status = t.getStatus();
if (status.equalsIgnoreCase(NEW)) {
newTrade(t);
} else if (status.equalsIgnoreCase(EXECUTE)) {
executeTrade(t);
} else if (status.equalsIgnoreCase(PENDING)) {
pendingTrade(t);
}
}
这是一种很粗糙的处理方法,在Java 7中,我们可以利用增强的switch语句,使用String作为参数
public void processTrade(Trade t) {
String status = t.getStatus();
switch (status) {
case NEW:
newTrade(t);
break;
case EXECUTE:
executeTrade(t);
break;
case PENDING:
pendingTrade(t);
break;
default:
break;
}
}
在上面的程序中,状态字段总是使用String.equals() 方法和case标签进行比较。
Automatic resource management
像连接、文件、I/O流一些的资源都需要开发者编写代码手动的关闭,通常我们使用try-finally块负责关闭各自的资源。目前常用的实现如下:
public void oldTry() {
try {
fos = new FileOutputStream("movies.txt");
dos = new DataOutputStream(fos);
dos.writeUTF("Java 7 Block Buster");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fos.close();
dos.close();
} catch (IOException e) {
// log the exception
}
}
}
然而,Java 7中引入了另一个很酷的功能,自动管理资源。 它操作非常简单。 我们要做的是在try中声明资源,如下:
try(resources_to_be_cleant){
// your code
}
前面的方法oldtry可以使用新功能改写成如下:
public void newTry() {
try (FileOutputStream fos = new FileOutputStream("movies.txt");
DataOutputStream dos = new DataOutputStream(fos)){
dos.writeUTF("Java 7 Block Buster");
}catch (IOException e) {
// log the exception
}
}
上面的代码,也代表此功能的另一个方面:与多个资源的工作。 FileOutputStream和DataOutputStream资源都包含在try语句中一前一后,每一个资源分别由分号分隔(;)。我们不需要去手动的废除或关闭资源,它们会在控制推出try语言块后自动的在后台关闭。需要被自动关闭的资源必须实现java.lang.AutoCloseable接口。
任何实现AutoCloseable接口的资源都会被作为自动资源管理的对象, AutoCloseable是java.io.Closeable 接口的父类,只有close()一个方法,当程序控制推出try语言块时,该方法会被jvm调用。
数字文字用下划线
数值文字是眼睛过滤器。如果给你一个带有十个零的数,我相信你一定会像我一样的开始计算零的个数。识别一个百万甚至千万的数是很容易出错,而且繁琐的。在Java7中可以再确定的地方引入强调符号,例如你可以像下面的方式声明一千:
int thousand = 1_000;
或者一百万
int million = 1_000_000
注意:此版本也引入了binary 二进制文字,开发人员再也不必将它们转换为十六进制了,例如“0B1”。
改进的异常处理
在异常处理方面也有一些改进。Java7引入了多重捕获功能(multi-catch),可以再一个catch块中捕获多种不同类型的异常。
例如,你有一个方法会抛出三个异常,按之前的处理,你需要像下面这样分别处理各个异常:
public void oldMultiCatch() {
try {
methodThatThrowsThreeExceptions();
} catch (ExceptionOne e) {
// log and deal with ExceptionOne
} catch (ExceptionTwo e) {
// log and deal with ExceptionTwo
} catch (ExceptionThree e) {
// log and deal with ExceptionThree
}
}
在catch块中捕获一个又一个的异常,看起来非常杂乱。如代码所示捕获一堆异常,效率非常低下,而且容易出错。 Java 7中带来了一种新的语言变化,来解决这个丑小鸭。 来看看方法的改进版本oldMultiCatch方法如下:
public void newMultiCatch() {
try {
methodThatThrowsThreeExceptions();
} catch (ExceptionOne | ExceptionTwo | ExceptionThree e) {
/ log and deal with all Exceptions
}
}
多个异常在一个catch块中捕获,不同的异常之间用(|)操作符分隔开。使用这种方法你就不需要写许多的捕获异常块。而且,如果你有许多属于不同类型的异常,你可以使用多个multi-catch块, 下面的代码片段说明了这一点:
public void newMultiMultiCatch() {
try {
methodThatThrowsThreeExceptions();
} catch (ExceptionOne e) {
// log and deal with ExceptionOne
} catch (ExceptionTwo | ExceptionThree e) {
// log and deal with ExceptionTwo and ExceptionThree
}
}
在上述案例中,ExceptionTwo和ExceptionThree属于不同层次,所以你会想用一个catch块的不同方法来处理它们。
新的文件系统 API (NIO 2.0)
使用过Java IO的人可能任然记得框架带来的麻烦。 跨操作系统或者多文件系统的无缝使用从来都不一件容易的事情。像删除文件、重命名一类的动作在很多时候都会引起不可意料的结果。 使用符号链接是另一个问题。 总地来说,API需要大修。
为了解决上述Java的IO问题,Java 7的推出了新的API。NIO 2.0带来了许多新的改进,也引入了新的类,方便开发人员处理多文件系统问题。
文件路径
新java.nio.file的包中包含Path, Paths ,FileSystem,FileSystems和其他如类和接口。
Path是一个简单的引用文件的路径。 它是等价(更多的功能) java.io.File 。 下面的代码片断演示了如何获得“Temp”文件夹的路径引用:
public void pathInfo() {
Path path = Paths.get("c:\\Temp\\temp");
System.out.println("Number of Nodes:" + path.getNameCount());
System.out.println("File Name:" + path.getFileName());
System.out.println("File Root:" + path.getRoot());
System.out.println("File Parent:" + path.getParent());
}
控制台会输出如下内容:
Number of Nodes:2 File Name:temp.txt File Root:c:\ File Parent:c:\Temp
删除一个文件或目录只需要简单的调Files类的delete方法(注意复数)。 Files类公开了两个delete方法,其中一个会抛出NoSuchFileException 另一个则不会。
下面的删除方法调用抛出NoSuchFileException的,所以你必须处理它:
Files.delete(path);
如果文件或目录不存在,Files.deleteIfExists(path) 不会抛出异常。
您可以使用Files.copy(..)和Files.move(..)等通用方法对文件系统进行操作。 同样,使用createSymbolicLink(..)方法创建符号链接。
文件更改通知
在JDK 7,我最喜欢的改善之一,是增加了文件更改通知。 这一直是期待已久的的功能,终于雕刻成NIO 2.0。WatchService API使你会在对象(目录或文件)变化后收到事件通知。
实现API所涉及的步骤是:
· 创建一个WatchService 。 这项服务包括一个持有WatchKeys的队列
· 用这个WatchService注册要监视的目录/文件
· 注册时,指定您希望收到的事件的类型(创建,修改或删除事件)
· 开始一个无限循环监听事件
· 当事件发生时,一个WatchKey被放入队列
· 消耗WatchKey ,并调用它的查询
通过一个例子。 我们创建一个DirPolice Java程序,其责任是检测一个特定的目录。 步骤如下:
1、创建WatchService对象:
WatchService watchService = FileSystems.getDefault().newWatchService();
2、获取到你监视的目录的路径引用。 我建议你把这个目录的参数化,所以你不要硬编码文件名;
path = Paths.get("C:\\Temp\\temp\\");
3、下一步是用WatchService为该目录注册所有类型的事件:
dirToWatch.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
这是java.nio.file.StandardWatchEventKind 事件类型
4、启动无限循环,并开始接受的事件:
while(true){
WatchKey key = watchService.take(); // this would return you keys
…
}
5、通过对关键事件的运行:
for(WatchEvent<?> event : key.pollEvents()) {
Kind<?> kind = event.kind();
System.out.println("Event on " + event.context().toString() + " is " + kind);
}
例如,如果你修改或删除这个临时文件夹,你会在控制台看到入下输出:
Event on temp is ENTRY_MODIFY Event on temp is ENTRY_DELETE
DirPolice相关方法源代码如下( 下载完整的源代码 ):
try {
watchService = FileSystems.getDefault().newWatchService();
path.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
} catch (IOException e) {
System.out.println("IOException"+ e.getMessage());
}
/**
* The police will start making rounds
*/
private void doRounds() {
WatchKey key = null;
while(true) {
try {
key = watchService.take();
for(WatchEvent<?> event : key.pollEvents()) {
Kind<?> kind = event.kind();
System.out.println("Event on " + event.context().toString() + " is " + kind);
}
} catch (InterruptedException e) {
System.out.println("InterruptedException: "+e.getMessage());
}
boolean reset = key.reset();
if(!reset)
break;
}
}
Fork and Join
在java程序中有效的使用并行内核一直是一个挑战,有一些框架将任务分派到多核上执行,然后合并返回的结果集。Java7纳入这个功能,提供一个fork/join框架。
总地来说,Fork-Join 把待处理的任务逐级分解成小任务,直到所有任务都已经足够简单不需要再分解。它就像一个分治算法。这个框架中的一个重要的概念是最好没有工作线程处于空闲状态。 它实现了工作-窃取算法,空闲线程会从繁忙的工作线程处“窃取”任务。
支持fork-join机制的核心类是ForkJoinPool和ForkJoinTask 。 ForkJoinPool基本上是一个ExecutorService的特别实现类,执行我们上面谈到的工作窃取算法。
我们创建一个ForkJoinPool的实例,提供目标并行级别——处理器的数量,如下所示:
ForkJoinPool pool = new ForkJoinPool(numberOfProcessors)
numberOfProcessors = Runtime.getRunTime().availableProcessors();
但是,默认的ForkJoinPool实例需要设置并行级别为上面获得相同数量。
需要解决的问题写在ForkJoinTask里。 但是,有两个实现类:RecursiveAction和RecursiveTask,这两个类之间的唯一区别是,前者不返回值,而后者则返回指定类型的对象。
以下是如何创建RecursiveTask或RecursiveAction处理前面提到的问题(我使用 RecursiveAction类)
public class MyBigProblemTask extends RecursiveAction {
@Override
protected void compute() {
. . . // your problem invocation goes here
}
}
你必须覆盖需要提供计算功能的方法。 现在,向Executor提供ForkJoinTask,通过调用ForkJoinPool的invoke方法:
pool.invoke(task);
支持动态语言
Java是一种静态类型的语言 ——变量,方法和返回值的类型检查是在编译时进行。 JVM在运行时执行强类型的字节码,而不必担心找到的类型信息。
还有另一种类型的语言品种 - 动态语言。 Ruby,Python和Clojure的都属于这一类。 类型信息在运行时才能确定。 这是不是可能在Java,因为它不会有任何必要的类型信息。
Java 7中,推出的一项称为invokedynamic的新功能。 这使得虚拟机的变化,纳入非Java语言的要求。已经创建了一个新的软件包, java.lang.invoke , MethodHandle , CallSite和其他人组成的,如类,扩展动态语言的支持。
原文地址