Java 8 新特性概述

Lambda 表达式(Method References)

lambda 表达式使用一种简洁的方式来传递代码片段。举个例子,假如你需要使用一个线程(Thread)执行一个任务。你可能这样做:创建一个Runnable对象,然后将该对象作为参数传递给Thread

	Runnable runnable = new Runnable() {
		@Override
		public void run() {
			System.out.println("Hi");
		}
	};

	new Thread(runnable).start();

另一方面,你可以使用lambda 表达式以这一种更加易读的方式重写之前的代码。

	new Thread(() -> System.out.println("Hi")).start();

方法引用(Method References)

方法引用与lambda 表达式配合使用形成一种新特性。让你选择一个在类中已经存在的方法作为参数传递。举个例子,假如你需要忽略大小写来比较一个字符串列表。常规地我们像这样写代码:

	List<String> strs = Arrays.asList("C", "a", "A", "b");
	Collections.sort(strs, new Comparator<String>() {
		@Override
		public int compare(String s1, String s2) {
			return s1.compareToIgnoreCase(s2);
		}
	});

上面的代码及其冗长啰嗦。毕竟你需要的只是方法compareToIgnoreCase。你可以使用方法引用很明确地表述出,比较大小应该使用定义在String类中的方法compareToIgnoreCase来执行:

	Collections.sort(strs, String::compareToIgnoreCase);

代码String::compareToIgnoreCase就叫做方法引用。它使用特别的语法 ::

流(Streams)

几乎每一个Java程序都会创建和处理集合。我们经常使用集合来归类和处理数据,因此集合对许多编程任务非常重要。然而,使用集合写出的代码相当地冗长和难以并行化。下面的代码阐述了处理集合是多么的麻烦。这段代码主要功能:查找按照清单费用大小排序的培训相关费用清单ID。

	List<Invoice> trainingInvoices = new ArrayList<>();
	for(Invoice inv: invoices) {
		if(inv.getTitle().contains("Training")) {
			trainingInvoices.add(inv);
		}
	}
	Collections.sort(trainingInvoices, new Comparator() {
		public int compare(Invoice inv1, Invoice inv2) {
			return inv2.getAmount().compareTo(inv1.getAmount());
		}
	});
	List<Integer> invoiceIds = new ArrayList<>();
	for(Invoice inv: trainingInvoices) {
		invoiceIds.add(inv.getId());

Java 8 引进了一种新的抽象方式叫做流,它可以让我们声明式地处理数据。在Java 8 中,我们可以使用流(stream)重新上文代码:

	List<Integer> invoiceIds =
	invoices.stream()
			.filter(inv -> inv.getTitle().contains("Training"))
			.sorted(comparingDouble(Invoice::getAmount)
			.reversed())
			.map(Invoice::getId)
			.collect(Collectors.toList());

另外还可以使用集合类中的方法parallelStream来代替方法stream来显式地并行执行数据流。

增强接口(Enhanced Interfaces)

Java 8 中接口增强得益于两种改进方式从而可以在声明方法时附带具体实现。
第一,Java 8 引进默认方法(default methods),它可以让我们在接口中声明方法时附带具体实现代码。这是Java 8 引入用于解决Java API 向后兼容的机制。
比如,我们可以看到Java 8 中的List接口现在已经支持sort方法:

	default void sort(Comparator<? super E> c) {
		Collections.sort(this, c);
	}

从行为方面看,默认方法也可以服务于多重继承服务机制。实际上,在Java 8 之前的版本中,一个类已经可以实现多个接口。现在,你可以继承多个不同的接口的默认方法。注意,Java 8 拥有显式的规则来避免一些在C++中常见的继承问题(例如钻石问题)。

第二,接口现在可以有静态方法(static methods)。在一个常规模式下,我们定义一个接口,与此同时定义一个伴随类,伴随类定义一些静态方法与接口的继承类协同工作。例如,Java中有Collection接口和Collections类,Collections类用来定义工具静态方法。现在这些工具静态方法可以直接定义在接口中。
看具体事例,在Java 8 中Stream接口声明一个静态方法:

	public static <T> Stream<T> of(T... values) {
		return Arrays.stream(values);
	}

新的日期和时间 API(New Date and Time API)

Java 8 中引进了一种新的日期和时间 API 来解决许多旧的类Date和Calendar的典型问题。新的日期和时间围绕两大原则设计:
领域驱动设计(Domain-driven)
新的日期和时间 API 通过产生新的类来代替本身很好地模型化各种意图的日期和时间。例如,你可以使用类Period来表示一个"2个月零3天"的值,使用类ZonedDateTime来表示带有时区的日期时间。每个类都提供了采用流畅方表达的领域特定方法。因此,你可以链接方法来写出更加易读的代码。

例如,下面的代码描述了如何创建一个LocalDateTime对象同时在这个时间基础上增加2小时30分钟。

	LocatedDateTime coffeeBreak = LocalDateTime.now()
											   .plusHours(2)
											   .plusMinutes(30);

不可变(Immutability)
DateCalendar有一个问题就是不是线程安全的。另外,开发人员在使用日期作为API的一部分时偶尔会无预期地改变日期值。为了避免潜在的Bug,在新的日期和时间 API 中的类都是不可变的。换句话说,你将不能改变在新的日期和时间 API 中的对象状态。现在你可以使用一个方法来返回带有更新后值的新对象。

下面的代码列举各种在新的日期和时间 API 中的方法:

	ZoneId london = ZoneId.of("Europe/London");
	LocalDate july4 = LocalDate.of(2014, Month.JULY, 4);
	LocalTime early = LocalTime.parse("08:45");
	ZonedDateTime flightDeparture = ZonedDateTime.of(july4, early, london);
	System.out.println(flightDeparture);
	
	LocalTime from = LocalTime.from(flightDeparture);
	System.out.println(from);
	
	ZonedDateTime touchDown
		= ZonedDateTime.of(july4, 
						   LocalTime.of (11, 35), 
						   ZoneId.of("Europe/Stockholm"));
	Duration flightLength = Duration.between(flightDeparture, touchDown);
	System.out.println(flightLength);
	
	// How long have I been in continental Europe?
	ZonedDateTime now = ZonedDateTime.now();
	Duration timeHere = Duration.between(touchDown, now);
	System.out.println(timeHere);

这段代码会产生类似这样的输出:

	2015-07-04T08:45+01:00[Europe/London]
	08:45
	PT1H50M
	PT269H46M55.736S

CompletableFuture

Java 8 使用一个新类CompletableFuture来处理异步程序。它是针对旧的类Future的提升,它的方法启发于新的流 API 类似的设计选择(例如,声明式风格和流畅地方法链接)。换句话说,你可以声明式地处理和组合多个异步任务。有这样一个事例,并发地查询两个支持汇率计算的价格查询服务的任务。当两个服务都可用并且返回结果时,你可以组合结果以英镑(GBP)形式来计算和打印出来。

	findBestPrice("iPhone6")
		.thenCombine(lookupExchangeRate(Currency.GBP),this::exchange)
		.thenAccept(localAmount -> System.out.printf("It will cost
	you %f GBP\n", localAmount));
	
	private CompletableFuture<Price> findBestPrice(String productName) {
		return CompletableFuture.supplyAsync(() -> priceFinder.findBestPrice(productName));
	}
	
	private CompletableFuture<Double> lookupExchangeRate(Currency localCurrency) {
		return CompletableFuture.supplyAsync(() ->
			exchangeService.lookupExchangeRate(Currency.USD, localCurrency));
	}

Optional

Java 8 引入一个新的类叫做Optional,启发于函数式编程语言(functional programming languages)。当一个值可能存在或者不存在时我们可以使用它更好地构建自己的代码库。可以认为它是一个单一值的容器,这个容器可以包含一个有效值或者一个空值。Optional常见于特殊的集合框架中(例如Guava),但是现在它也成为了Java API 的一部分。Optional还有一个好处就是可以有效地避免空指针异常(NullPointerException)。实际上,通过Optional定义一个方法强制你检查一个值是否存在。看下面的代码:

	getEventWithId(10).getLocation().getCity();

如果getEventWithId返回空(null),那么这段代码就会抛出空指针异常。如果getLocation返回空它还是会抛出空指针异常。也就是说任意一个方法返回空都会抛出空指针异常。你可以采用防御检查来避免这种情况。看下面的代码:

	public String getCityForEvent(int id) {
		Event event = getEventWithId(id);
		if(event != null) {
			Location location = event.getLocation();
			if(location != null) {
				return location.getCity();
			}
		}
		return "TBC";
	}

这段代码中,event可能有一个locationlocation可能有一个city。非常烦人的时我们经常忘记去检查空值。
另外,这段代码相当啰嗦且相比较于下面的代码更加复杂。

	public String getCityForEvent(int id) {
		Optional.ofNullable(getEventWithId(id))
				.flatMap(this::getLocation)
				.map(this::getCity)
				.orElse("TBC");
	}

可以看到使用Optional重构的代码更加简洁明了。
在任何时候,这要Optional返回空时都会使用“TBC”来作为默认值返回。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章