《JAVA8开发指南》为什么你需要关注 JAVA8

本章包含

  • 代码的可读性
  • 多核
  • JAVA8特性的快速指南

JAVA8:为什么你需要关注?

JAVA已经更新了!在 2014 年 3 月,JAVA发布了新版本-JAVA8,JAVA8 引入的一些新特性可能会改变你日常中基本的编码方式。但不用担心,这本简洁的指南会带着你掌握一些要领,现在你就可以开始阅读。

第一章列举了 JAVA8 中增加的主要功能概况。接下来的两章则关注 JAVA8 的主要特性: lambda 表达式 和streams(流)。

驱动 JAVA8 改进的两大动机:

  • 代码可读性
  • 更加简化的多核支持

代码可读性

JAVA 是比较繁琐的,这导致了可读性的降低。换句话说,它需要很多代码才能表达一个简单的概念。

举个例子:给一个票据列表按数值递减排序。在 JAVA8 之前,你需要写这样写代码:

Collections.sort(invoices,new Comparator<Invoice>(){
public int compare(Invoice inv1,Invoice inv2){
return Double.compare(inv2.getAmount(),inv1.getAmount());
}
});

像这种编码方式,你需要关注很多关于如何排序的小细节。换句话说,它很难对上面陈述的问题(票据排序问题)用一个简单的解决方案来描述。你需要创建一个 Comparator(比较器) 对象来定义如何对两个票据进行比较。为了实现比较,你需要提供一个 compare() 方法的实现。在阅读这段代码的时候,你必须花费较多而的时间来理解实现的细节而不是理解实际的问题描述。

在 JAVA8 中,你可以用下面这段代码来进行重构:

invoices.sort(comparingDouble(Invoice::getAmount).reversed());

现在,问题描述得清晰明了。(不要担心新的语法,我将会进行一个简短的介绍)。这就是你为什么应该关注 JAVA8 的原因,它带来了新的语言特色和API的更新,能让你写出更加简洁可读的代码。

此外,JAVA8 引入了一种新的API,叫做 Streams API。它可以让你写出可读性强的代码来处理数据。Streams API 支持几种内建的操作,以更简单的方式来处理数据。例如,在商业运营环境中,你可能希望产生一份日结报告用来过滤和聚合多个部门的票据信息。幸运的是,通过 Streams API,你可以不用去担心如何实现这种查询。这种方法和你使用SQL相似,事实上,在SQL中你可以制定一个查询而不用去关心它内部的实现。例如,假设你想要找出所有票据中数据大于1000的票据单号:

SELECT id FROM invoices WHERE amount > 1000

通常把这种查询的书写风格称作为声明式编程。这就是你将用 Streams API 解决这个问题的方式:

List<Integer> ids = invoices.stream()
.filter(inv->inv.getAmount > 1000 )
.map(Invoice::getId)
.collect(Collections.toList());

现在不要关注这些代码的细节,在第 3 章你会更深入地了解 Streams API。现在,把 Streams API 看作是一种新的抽象概念,以更好的可读性来处理数据的查询。

多核

JAVA8 中第二个大的改动就是多核处理时所不可缺少的。在过去,你的电脑只有一个处理单元。要想更快地运行一个应用程序通常意味着提升处理单元的性能。不幸的是,处理单元的处理速度已经不再提升。今天,绝大多数的电脑和移动设备都有多个处理单元(简称核)在并行工作。应用程序应该利用不同的处理单元来提升性能。JAVA 应用程序中经典的实现方式就是利用线程。不幸的是使用线程往往是困难和容易出错的,一般是为专家准备的。JAVA8 中的 Streams API 可以让你很简单地对数据查询进行并发处理。例如,你仅仅需要用 parallelStream() 替代 stream() 即可实现先前代码的并发运行:

List<Integer> ids = invoices
.parallelStream()
.filter(inv->inv.getAmount > 1000 )
.map(Invoice::getId)
.collect(Collections.toList());

在第 3 章,我会探讨使用 parallel streams 的细节及其最佳实现。

JAVA8特性的快速指南

这部分会提供 JAVA8 一些主要的新特性的概述,并附带一些代码例子,向你展示一些可用的概念。接下来的两章会重点描述 JAVA8 的两大重要特性:lambda 表达式 和 streams。

lambda 表达式

lambda 表达式让你用一种简洁的方式去避免一大块的代码。例如,你需要一个线程来执行一个任务。你可以创建一个 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();

在第 2 章,你将会学习关于 lambda 表达式的更多重要的细节。

方法引用

方法引用联合 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 ArraysList<>();
for(Invoice inv:invoices){
if(inv.getTitle().contains(“Training”)){
trainingInvoices.add(inv);
}
}
Collections.sort(trainingInvoices,new Comparator<Invoice>(){
public int compare(Invoice inv1,Invoice inv2){
return inv2.getAmount().compareTo(inv1.getAmount());
}
});
List<Integer> invoiceIds = new Arrays<>();
for(Invoice inv : trainingInvoices){
invoiceIds.add(inv.getId());
}

JAVA8 引进了一种新的抽象概念叫做 Stream ,可以让你以一种声明式的方式进行数据的处理。在 JAVA8 中你可以使用 streams 去重构之前的代码,就像这样:

List<Integer> invoiceIds = invoices.stream()

.filter(inv -> inv.getTitle().contains(“Training”))
.sort(comparingDouble(Invoice::getAmount).reversed())
.map(Invoice::getId)
.collect(Collections.toList());

另外,你可以通过使用集合中 parallelStream 方法取代 stream 方式来明确的并发执行一个 stream(现在不要关注这段代码的实现细节,你将会在第3 章中学到更多关于 Streams API 的知识)。

增强接口

JAVA8 中对接口进行了两大改造,使其可以在接口中声明具体的方法。

第一、JAVA8 引入了默认方法,它可以让你在接口声明的方法中增加实现体,作为一种将 JAVA API 演变为向后兼容的机制。例如,你会看到在 JAVA8 的 List 接口中现在支持一种排序方法,像下面这么定义的:

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

默认方法也可以当做一种多重继承的机制来提供服务。事实上,在 JAVA8 之前,类已经可以实现多接口。现在,你可以从多个不同的接口中继承其默认方法。注意,为了防止出现类似 C++ 中的继承问题(例如钻石问题),JAVA8 定义了明确的规则。

第二、接口现在也可以拥有静态方法。它和定义一个接口,同时用一个内部类定义一个静态方法去进行接口的实例化是同一种机制。例如,JAVA 中有 Collection 接口和 定义了通用静态方法的 Collections 类,现在这些通用的静态也可以放在接口中。例如,JAVA8 中的 stream 接口是这样定义静态方法的:

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

新的日期时间 API

JAVA8 引入了一套新的日期时间 API ,修复了之前旧的 Date 和 Calendar 类的许多问题。这套新的日期时间 API 包含两大主要原则:

领域驱动设计

新的日期时间 API 采用新的类来精确地表达多种日期和时间的概念。例如,你可以用 Period 类去表达一个类似于 “2个月零3天(63天)”,用 ZonedDateTime 去表达一个带有时间区域的时间。每一个类提供特定领域的方法且采用流式风格。因此,你可以通过方法链写出可读性更强的代码。例如,接下来的这段代码会展示如何创建一个 LocalDateTime 对象而且增加 2小时30分:

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

不变性

Date(日期) 和 Calendar(日历)的其中一个问题就是他们是非线程安全的。此外,开发者使用 Dates (日期) 作为他们的API的一部分时,Dates(日期)的值可能会被意外的改变。为了避免这种潜在的BUG,在新的日期时间 API 中的所有类都是不可变的。

也就是说,在新的日期时间 API 中,你不能改变对象的状态,取而代之的是,你调用一个方法会返回一个带有更新的值的新对象。下面的这段代码列举了多种在新的日期时间 API 中可用的方法:

ZoneId london = ZoneId.of(“Europe/London”);
LocalDate july4 = LocalDate.of(2014,Month.JULY,4);
LocalTime early = LocalTime.parse(“08:05″);
ZonedDateTime flightDeparture = ZonedDateTime.if(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);
ZonedDateTime now = ZonedDateTime.now();
Duration timeHere = Duration.between(touchDown,now);
System.out.println(timeHere);

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

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

JAVA8 中引入了一种新的方式来进行程序的异步操作,即使用一个新类 CompletableFuture 。它是旧的 Future 类的改进版,这种灵感来自于类似 Streams 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

JAVA8 中引入了一个新的类叫做 Optional。灵感来自于函数式编程语言,它的引入是为了当值为空或缺省时你的代码库能容许更好的模型。

把它当作是一种单值容器,这种情况下如果没有值则为空。Optional  已经在可供选择的集合框架(比如 Guava)中可用,但现在它作为 JAVA API 的一部分,可用于JAVA中。Optional 的另一个好处是它可以帮助你避免空指针异常。事实上,Optional 定义了方法强制你去明确地检查值存在还是缺省。下面这段代码就是一个例子:

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

如果 getEventWithId(10) 返回 NULL,那么代码就会抛出 NullPointerException(空指针异常)。如果 getLocation() 返回 NULL,它也会抛出 NullPointerException(空指针异常)。换句话说,如果任何一个方法返回 NULL,就会抛出 NullPointerException(空指针异常)。你可以采用防御性的检查来避免这种异常,就像下面这样:

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

在这段代码中,一个事件可能会有一个与之关联的地点。然而,一个地点总是会与一个与之关联的城市。不幸的是,它通常容易忘记去检查值是否为 NULL 。此外,这段代码太详细而且难于跟踪。使用 Optional 你可以用更加简洁清晰的方式去重构这段代码,就像这样:

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

在任何时候,如果方法返回一个空的 Optional 对象,你就会得到默认值 TBC。

转载自 并发编程网 - ifeve.com

时间: 2024-09-08 20:10:03

《JAVA8开发指南》为什么你需要关注 JAVA8的相关文章

《JAVA8开发指南》第二章采用Lambda表达式(二)

注意事项 你会经常看到在接口类上标有@FunctionalInterface的注解.它和标签@Override很相似,表示方法是可以被覆盖的.本文中,@FunctionalInterface标签用来写在文档中,表示接口是一个函数接口.编译器也将在接口注解不符合函数接口定义的时候抛错. 你也将发现一些新的函数接口,比如在包java.util.function里面的Function<T, R>和Supplier<T>,你可以通过这些来使用任意形式的lambda表达式. 方法参数 方法参

《JAVA8开发指南》使用流式操作

本章中,你将学习到怎样使用Stream API进行开发.首先,你将会了解Stream API背后的机制,什么是流以及流的用处.其次,你将学习到一系列的流式操作.流式数据处理模型以及能让你写出更复杂数据查询的流式集合操作.接下来是如何应用流式操作的例子.最后,你将学习到并行流. 为什么需要流式操作 集合API是Java API中最重要的部分.基本上每一个java程序都离不开集合.尽管很重要,但是现有的集合处理在很多方面都无法满足需要. 一个原因是,许多其他的语言或者类库以声明的方式来处理特定的数据

《Java8开发指南》翻译邀请

Java 8: Why Should You Care? Adopting Lambda Expressions. Adopting Streams 转载自 并发编程网 - ifeve.com

Simple JSON开发指南_java

Simple JSON是Google开发的Java JSON解析框架,基于Apache协议. json-simple的主页:http://www.jb51.net/softs/455885.html 下载的文件是:json_simple.jar 例子1:很方便的方式,使用JSONValue System.out.println("=======decode======="); String s="[0,{\"1\":{\"2\":{\&

Java8简明指南

Java8简明指南 欢迎来到Java8简明指南.本教程将一步一步指导你通过所有新语言特性.由短而简单的代码示例,带你了解如何使用默认接口方法,lambda表达式,方法引用和可重复注解.本文的最后你会熟悉最新的API的变化如Stream,Fcuntional,Map API扩展和新的日期API.   接口的默认方法 在Java8中,利用default关键字使我们能够添加非抽象方法实现的接口.此功能也被称为扩展方法,这里是我们的第一个例子: interface Formula { double ca

Java8系列 - Java8简明指南

Java8简明指南 欢迎来到Java8简明指南.本教程将一步一步指导你通过所有新语言特性.由短而简单的代码示例,带你了解如何使用默认接口方法,lambda表达式,方法引用和可重复注解.本文的最后你会熟悉最新的API的变化如Stream,Fcuntional,Map API扩展和新的日期API. 接口的默认方法 在Java8中,利用default关键字使我们能够添加非抽象方法实现的接口.此功能也被称为扩展方法,这里是我们的第一个例子: interface Formula { double calc

关于《Swift开发指南》背后的那些事

时间轴(倒叙)2014年8月底在图灵出版社的大力支持下,全球第一本全面.系统.科学的,包含本人多年经验的呕心沥血之作<Swift开发指南>(配有同步视频课程和同步练习)全线重磅推出2014年7月5日苹果宣布Swift语言二十天后,<Swift开发指南>第一稿交予图灵出版社2014年6月9日苹果宣布Swift语言三天后,启动<Swift开发指南>撰写2014年6月2日凌晨1点(北京时间:)在苹果开发者大会WWDC 2014上,苹果宣布了全新的iOS及OS X平台开发语言S

“Cucumber行为驱动开发指南”能带给我们什么

介绍 或许你已经了解到了软件开发中一个头疼的事,就是如何产生正确的需求和围绕这些需求如何有效地进行软件开发?但又不知如何着手? 或许你已经了解到了一些相关的理论知识来解决这个难题,如:行为驱动开发(BDD),验收测试驱动开发(ATDD),实例化需求(Specification By Example),但却发现很难消化所有的信息? 或许你已经建立了一套相关的自动化测试,但总觉得在为测试而测试,没有解决实际问题,有点脱钩? 或许你已经开始着手建立自动化测试来做保障,但对那么多的工具无从选择? 也或许

Knockout应用开发指南 第三章:绑定语法(3)

原文:Knockout应用开发指南 第三章:绑定语法(3) 12   value 绑定 目的 value绑定是关联DOM元素的值到view model的属性上.主要是用在表单控件<input>,<select>和<textarea>上. 当用户编辑表单控件的时候, view model对应的属性值会自动更新.同样,当你更新view model属性的时候,相对应的元素值在页面上也会自动更新. 注:如果你在checkbox或者radio button上使用checked绑定