java7新特性 - 高飞网
586 人阅读

java7新特性

2017-07-28 02:09:46

    java7里有很多面向开发者的新特性,比如switch语句中可以使用字符串,多异常处理,资源try语句,新文件系统API,JVM扩展,动态语言支持,并行处理的fork和join框架,以及其他社区中提到的概念。

    下面我列出了这些新特性,并予以示例。

语法增强

    通过Coin项目,Java7包含了一些新的语言特性。这些特性对开发者非常适用。


菱形操作符(Diamond Operator)

    可能你会遇到这样的场景,你的IDE为泛型自动补全类型。例如,如果我们用泛型声明一个Map,我们这样写代码:

Map<String, List<Trade>> trades = new TreeMap<String, List<Trade>> ();

    这种声明让我们感到不爽,因为我们必须在赋值=的两侧都声明类型,即使右侧看起来有些多余。难道编译器不能通过左侧的声明推测右侧吗?直到java7之前是不可以的。在Java7中,可以这样写:

Map<String, List<Trade>> trades = new TreeMap<> ();

    是不是酷?你不用再为实例化的整个列表指定类型了,而只要用一个<>符号,这叫菱形操作符。注意,如果不声明<>,比如trades = new TreeMap()这样,将会报告一个类型安全警告。


switch语句中使用字符串

    Switch语句可以使用基本类型或枚举,在java7中引入了另外一种类型——String。

    假设现在有一个业务,是根据它的状态做一些处理,到目前为止我们使用的都是if-else语句

String status = "new";
if (status.equalsIgnoreCase(NEW)) {
	System.out.println("new...");
} else if (status.equalsIgnoreCase(EXECUTE)) {
	System.out.println("execute...");
} else if (status.equalsIgnoreCase(PENDING)) {
	System.out.println("pending...");
}

    这样的代码很粗糙。好在java7中,我们可以利用swich语句,将String类型做为一个入参:

String status = "new";
switch (status) {
case NEW:
	System.out.println("new...");
	break;
case EXECUTE:
	System.out.println("execute...");
	break;
case PENDING:
	System.out.println("pending...");
	break;
default:
	break;
}

    而switch支持String,只不过是Java编译一个语法糖,其实现可参考:。


自动资源管理

    诸如连接Connection、文件File,输入输出流Input/OutStreams等,都应在标准代码里由开发者手工地关闭。通常地,使用try-finally语句块来关闭相应的资源。看下面的代码,创建资源、使用并最后关闭。

InputStream in = null;
try {
	in = TryWithResource.class.getResourceAsStream("hello");
	byte b[] = new byte[in.available()];
	in.read(b);
	System.out.println(new String(b));
} catch (IOException e) {
	e.printStackTrace();
} finally {
	try {
		in.close();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

    然而,在Java7中引入了另外一种超酷的特性来自动管理资源。同时使用也非常简单,只要在try中声明资源即可。如下:

try(resources_to_be_cleant){
    //your code
}

之前的例子可以写为:

try (InputStream in = TryWithResource.class.getResourceAsStream("hello")) {
	byte b[] = new byte[in.available()];
	in.read(b);
	System.out.println(new String(b));
} catch (IOException e) {
	e.printStackTrace();
}

如果是多个资源,资源声明之间,使用;隔开:

try (InputStream in = TryWithResource.class.getResourceAsStream("hello");
        DataInputStream di = new DataInputStream(in)) {
	byte b[] = new byte[in.available()];
	di.read(b);
	System.out.println(new String(b));
} catch (IOException e) {
	e.printStackTrace();
}

    在这个现象背后,我们要知道,资源之所以能自动关闭,是因为资源继承了java.lang.AutoCloseable接口,该接口中有一个close()方法,提供给JVM用来关闭资源。

/**
 * A resource that must be closed when it is no longer needed.
 *
 * @author Josh Bloch
 * @since 1.7
 */
public interface AutoCloseable {
    void close() throws Exception;
}


带下划线的数值字面量

    数值字面量用肉眼来定义的。而且我确认,你会像我一样对0进行计数,如果你拿到一个数字,说,10个0.这非常容易出错,如果是百万甚至十亿,就更难识别了,除非你从右到左计数位置。java7以后就不用这样了,因为它引入了下划线来标识这样的位置。例如,如果想声明1000,可以这样写:

int a = 1_000;

或者1百万:

int b = 1_000_000;

此外,二进制、八进制、十六进制也支持这种计数法。

int c = 0b1111_1111;
int d = 03_7_7;
int h = 0xf_f;


改善异常处理

    在异常处理领域有一系统的改善。在Java7中,引入了多捕获功能(multi-catch functionality),只有一条捕获语句块,就能捕获多个类型的异常。例如下面的方法有三个异常,目前我们的代码是这样的。

try {
    threeException();
} catch (ExceptionOne e) {
    e.printStackTrace();
} catch (ExceptionTwo e) {
    e.printStackTrace();
} catch (ExceptionThree e) {
    e.printStackTrace();
}

    在catch语句块中,一个个地捕获无尽的异常看起来非常的杂乱,我甚至看到过捕获一打异常的情况。这样既低效,又易出错。来看一下改善后的异常块

try {
    threeException();
} catch (ExceptionOne | ExceptionTwo | ExceptionThree e) {
    e.printStackTrace();
}

    没错,只需要用"|"符号,就可以在一条异常块中同时捕获多个异常了。以这种方式,就不用费劲地写一打异常的捕获了。然后,如果碰到不同类型的异常,也可以用附加异常处理(multi multi-catch)。比如:

try {
	threeException();
} catch (ExceptionOne e) {
	e.printStackTrace();
} catch (ExceptionTwo | ExceptionThree e) {
	e.printStackTrace();
}

    用上面的方式,第二种和第三种异常可以一起处理。


新文件系统API(NIO 2.0)

    使用JavaIO的人可能对框架造成的苦恼记忆犹新。它总是难以跨平台使用。比如delete或rename方法,在多数情况下会出现意外。使用符号连接是另外一个问题,本质上,API需要升级。

    为了解决JavaIO以上的问题,Java7中引入了很多新的API。

    NIO2.0带着很多改良出现,同时也引入了新的类来简化开发者在使用式文件系统的工作。


使用路径(working with Path)

    新的IO包是java.io.file,其中包括Path、Paths、FileSystem、FileSystems等。

    Path接口是对文件路径的简单引用。它等价于java.io.File。

Path path = Paths.get("C:/imagetest/2w");
System.out.println(path.getNameCount());//路径中的文件名个数
System.out.println(path.getFileName());//文件名称
System.out.println(path.getRoot());//根
System.out.println(path.getParent());//父目录

输出为:

2
2w
C:\
C:\imagetest

    删除文件或目录也非常简单,使用Files工具类,其中有两个方法可以删除文件:

Files.delete(path)
Files.deleteIfExists(path)

    另外还有其他的易用工具类,如Files.copy,Files.move()等。

文件修改通知(File change notifications)

    java7中我最为喜欢的改进就是添加上文件修改通知。这已经是一个期待已久的特性了,最终在NIO2.0中实现了。WatchService 类的API可以用来接收特定主题(目录或文件上的)的事件。步骤如下:

  1. 创建WatchService,该对象持有WatchKeys的一个队列
  2. 向WatchService注册想要监听的目录或文件
  3. 当注册时,指定想要接收的事件类型,如创建、修改或删除事件
  4. 开启一个无限循环来监听事件
  5. 当事件发生时,WatchKey将被放置到队列中
  6. 消费WatchKey并处理它。
public class FileChangeNotification {
	public static void main(String[] args) throws IOException, InterruptedException {
		WatchService watchService = FileSystems.getDefault().newWatchService();
		Path path = Paths.get("c:/io2test");
		path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY,
		        StandardWatchEventKinds.ENTRY_DELETE);
		while (true) {
			WatchKey watchKey = watchService.take();
			for (WatchEvent<?> event : watchKey.pollEvents()) {
				Kind<?> kind = event.kind();
				WatchEvent<Path> event0 = (WatchEvent<Path>) event;
				if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
					System.out.println(event0.context().getFileName() + " is Create");
				} else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
					System.out.println(event0.context().getFileName() + " is modify");
				} else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
					System.out.println(event0.context().getFileName() + " is delete");
				}
			}
			boolean reset = watchKey.reset();
			if (!reset) {
				break;
			}
		}

	}
}


Fork/Join框架

    在Java应用中能有效地使用多核心一直是个挑战。极少有原生的框架,可以将任务分发到多核心并将结果聚合起来。但在Java7中,已经将这种fork/join框架作为特性引入了。

    基本的Fork-Join框架是把任务拆分成更小的任务,直到任务简单得无法再分为止,使之可以不用再拆分就可以解决。这像是一种分而治之的算法(divide-and-conquer algorithm)。这其中的一个重要的理念就是理想情况下,没有空闲的线程。它实现了一种工作窃取算法(work-steaing),闲置的线程将从繁忙的线程中窃取任务。

    支持Fork-Join机制的核心类是ForkJoinPool和ForkJoinTask。而ForkJoinPool是ExecutorService的特殊实现,实现了上面提到的工作窃取算法。

    首先,创建一个ForkJoinPool实例,通过指针并发级别——使用处理数量:

int numberOfProcessors = Runtime.getRuntime().availableProcessors();
ForkJoinPool pool = new ForkJoinPool(numberOfProcessors)

    而ForkJoinPool的默认构造方法,使用的就是上面的线程数:

public ForkJoinPool() {
        this(Runtime.getRuntime().availableProcessors(),
             defaultForkJoinWorkerThreadFactory, null, false);
    }

    现在的问题就是解决怎么用ForkJoinTask写代码的问题了。幸好,该类有两个现成的实现:RecursiveAction和RecursiveTask,这两者仅有的不同是,前者不返回值,而后者返回。

    下面的代码创建一个RecursiveAction或RecursiveTask。用以计算1+2+3+...+100的和:

public class ForkJoinTest {

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		int numberOfProcessors = Runtime.getRuntime().availableProcessors();
		ForkJoinPool pool = new ForkJoinPool(numberOfProcessors);
		ForkJoinTask<Integer> result = pool.submit(new CountTask(1, 100));
		Integer sum = result.get();
		System.out.println(sum);
	}
}

class CountTask extends RecursiveTask<Integer> {

	private static final long serialVersionUID = 4032729308473196427L;
	private static final int THRESHOLD = 20;// 阈值

	int start, end;

	public CountTask(int start, int end) {
		this.start = start;
		this.end = end;
	}

	@Override
	protected Integer compute() {
		int sum = 0;
		if (end - start <= THRESHOLD) {// 任务足够小
			for (int i = start; i <= end; i++) {
				sum += i;
			}
			System.out.println(Thread.currentThread().getName() + ":" + start + "," + end);
		} else {
			int mid = (start + end) / 2;
			CountTask leftTask = new CountTask(start, mid);
			CountTask rightTask = new CountTask(mid + 1, end);
			leftTask.fork();
			rightTask.fork();

			int leftSum = leftTask.join();
			int rightSum = rightTask.join();
			sum = leftSum + rightSum;
		}
		return sum;
	}
}

输出:

ForkJoinPool-1-worker-5:1,13
ForkJoinPool-1-worker-3:14,25
ForkJoinPool-1-worker-3:26,38
ForkJoinPool-1-worker-2:39,50
ForkJoinPool-1-worker-4:51,63
ForkJoinPool-1-worker-1:64,75
ForkJoinPool-1-worker-3:89,100
ForkJoinPool-1-worker-4:76,88
5050

动态语言支持

    Java是一种静态类型语言——一种在编译时检查类型、方法和返回值的语言。JVM在运行时执行这种强类型的字节码,不必担心去查找类型信息。

    世上还有另外一种语言——动态类型语言。例如Ruby、Python和Clojure。这些语言的类型,直到运行时才会确定。这在java中是不可能的,因为这没有任何类型的信息。

    Java中现在也有一种运行动态语言的压力,即使在JVM中可能运行这种语言(使用反射),但这没有任何约束和限制。

    在Java7中,一种叫做动态执行的新特性被引入了。这使得JVM可以合并非Java语言。在新包java.lang.invoke中,包含了一些类如MethodHandle、CallSite和其他类,这些已被创建用于支持动态语言。


参考:A look at Java 7's new features


还没有评论!
54.224.216.155