2014年,Oracle发布了Java8新版本。对于Java来讲,这显然是一个具备里程碑意义的版本。尤为是那函数式编程的功能,避开了Java那烦琐的语法所带来的麻烦。java
这能够算是一篇Java8的学习笔记。将Java8一些常见的一些特性做了一个概要的笔记。程序员
为了编写可重用的方法,好比filter,你须要为其指定一个参数,它可以精确地描述过滤条件。虽然Java专家们使用以前的版本也能达到一样的目的(将过滤条件封装成类的一个方法,传递该类的一个实例),但这种方案却很难推广,由于它一般很是臃肿,既难于编写,也不易于维护。数据库
Java 8经过借鉴函数式编程,提供了一种新的方式——经过向方法传递代码片断来解决这一问题。这种新的方法很是方便地提供了两种变体。编程
apple -> apple.getWeight() > 150
Apple::isHeavy
这些值具备相似Function<T, R>、Predicate<T>或者BiFunction<T, U, R>这样的类型,值的接收方能够经过apply、test或其余相似的方法执行这些方法。Lambda表达式自身是一个至关酷炫的概念,不过Java 8对它们的使用方式——将它们与全新的Stream API相结合,最终把它们推向了新一代Java的核心。设计模式
闭包
你可能已经据说过闭包(closure,不要和Clojure编程语言混淆)这个词,你可能会想Lambda是否知足闭包的定义。用科学的说法来讲,闭包就是一个函数的实例,且它能够无限制地访问那个函数的非本地变量。例如,闭包能够做为参数传递给另外一个函数。它也能够访问和修改其做用域以外的变量。如今,Java 8的Lambda和匿名类能够作相似于闭包的事情:它们能够做为参数传递给方法,而且能够访问其做用域以外的变量。但有一个限制:它们不能修改定义Lambda的方法的局部变量的内容。这些变量必须是隐式最终的。能够认为Lambda是对值封闭,而不是对变量封闭。如前所述,这种限制存在的缘由在于局部变量保存在栈上,而且隐式表示它们仅限于其所在线程。若是容许捕获可改变的局部变量,就会引起形成线程不安全的新的可能性,而这是咱们不想看到的(实例变量能够,由于它们保存在堆中,而堆是在线程之间共享的)。
Java 8以前,接口主要用于定义方法签名,如今它们还能为接口的使用者提供方法的默认实现,若是接口的设计者认为接口中声明的某个方法并不须要每个接口的用户显式地提供实现,他就能够考虑在接口的方法声明中为其定义默认方法。数组
对类库的设计者而言,这是个伟大的新工具,缘由很简单,它提供的能力能帮助类库的设计者们定义新的操做,加强接口的能力,类库的用户们(即那些实现该接口的程序员们)不须要花费额外的精力从新实现该方法。所以,默认方法与库的用户也有关系,它们屏蔽了未来的变化对用户的影响。缓存
在接口上添加注解:@FunctionalInterface。便可声明该接口为函数接口。
若是你去看看新的Java API,会发现函数式接口带有@FunctionalInterface的标注。这个标注用于表示该接口会设计成一个函数式接口。若是你用@FunctionalInterface定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示缘由的错误。例如,错误消息多是“Multiple non-overriding abstract methods found in interface Foo”,代表存在多个抽象方法。请注意,@FunctionalInterface不是必需的,但对于为此设计的接口而言,使用它是比较好的作法。它就像是@Override标注表示方法被重写了。安全
Lambdas及函数式接口的例子:数据结构
使用案例 | Lambda例子 | 对应的函数式接口 |
---|---|---|
布尔表达式 | (List<String> list) -> list.isEmpty() |
Predicate<List<String>> |
建立对象 | () -> new Apple(10) |
Supplier<Apple> |
消费一个对象 | (Apple a) ->System.out.println(a.getWeight()) |
Consumer<Apple> |
从一个对象中选择/提取 | (String s) -> s.length() |
Function<String, Integer>或ToIntFunction<String> |
合并两个值 | (int a, int b) -> a * b |
IntBinaryOperator |
比较两个对象 | (Apple a1, Apple a2) ->a1.getWeight().compareTo(a2.getWeight()) |
Comparator<Apple>或BiFunction<Apple, Apple, Integer>或ToIntBiFunction<Apple, Apple> |
要讨论流,咱们先来谈谈集合,这是最容易上手的方式了。Java 8中的集合支持一个新的stream方法,它会返回一个流(接口定义在java.util.stream.Stream里)。你在后面会看到,还有不少其余的方法能够获得流,好比利用数值范围或从I/O资源生成流元素。闭包
那么,流究竟是什么呢?简短的定义就是“从支持数据处理操做的源生成的元素序列”。让咱们一步步剖析这个定义。
此外,流操做有两个重要的特色。
Java现有的集合概念和新的流概念都提供了接口,来配合表明元素型有序值的数据接口。所谓有序,就是说咱们通常是按顺序取用值,而不是随机取用的。那这二者有什么区别呢?
咱们先来打个直观的比方吧。好比说存在DVD里的电影,这就是一个集合(也许是字节,也许是帧,这个无所谓),由于它包含了整个数据结构。如今再来想一想在互联网上经过视频流看一样的电影。如今这是一个流(字节流或帧流)。流媒体视频播放器只要提早下载用户观看位置的那几帧就能够了,这样不用等到流中大部分值计算出来,你就能够显示流的开始部分了(想一想观看直播足球赛)。特别要注意,视频播放器可能没有将整个流做为集合,保存所须要的内存缓冲区——并且要是非得等到最后一帧出现才能开始看,那等待的时间就太长了。出于实现的考虑,你也可让视频播放器把流的一部分缓存在集合里,但和概念上的差别不是一回事。
粗略地说,集合与流之间的差别就在于何时进行计算。集合是一个内存中的数据结构,它包含数据结构中目前全部的值——集合中的每一个元素都得先算出来才能添加到集合中。(你能够往集合里加东西或者删东西,可是无论何时,集合中的每一个元素都是放在内存里的,元素都得先算出来才能成为集合的一部分。)
相比之下,流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计算的。 这对编程有很大的好处。在第6章中,咱们将展现构建一个质数流(2, 3, 5, 7, 11, …)有多简单,尽管质数有无穷多个。这个思想就是用户仅仅从流中提取须要的值,而这些值——在用户看不见的地方——只会按需生成。这是一种生产者-消费者的关系。从另外一个角度来讲,流就像是一个延迟建立的集合:只有在消费者要求的时候才会计算值(用管理学的话说这就是需求驱动,甚至是实时制造)。
与此相反,集合则是急切建立的(供应商驱动:先把仓库装满,再开始卖,就像那些昙花一现的圣诞新玩意儿同样)。以质数为例,要是想建立一个包含全部质数的集合,那这个程序算起来就没完没了了,由于总有新的质数要算,而后把它加到集合里面。固然这个集合是永远也建立不完的,消费者这辈子都见不着了。
操做 | 类型 | 返回类型 | 使用的类型、函数式接口 | 函数描述符 |
---|---|---|---|---|
filter |
中间 | Stream<T> |
Predicate<T> |
T -> boolean |
distinct |
中间(有状态-无界) | Stream<T> |
`` | `` |
skip |
中间(有状态-有界) | Stream<T> |
long |
`` |
limit |
中间(有状态-有界) | Stream<T> |
long |
`` |
map |
中间 | Stream<R> |
Function<T, R> |
T -> R |
flatMap |
中间 | Stream<R> |
Function<T, Stream<R>> |
T -> Stream<R> |
sorted |
中间(有状态-无界) | Stream<T> |
Comparator<T> |
(T, T) -> int |
anyMatch |
终端 | boolean |
Predicate<T> |
T -> boolean |
noneMatch |
终端 | boolean |
Predicate<T> |
T -> boolean |
allMatch |
终端 | boolean |
Predicate<T> |
T -> boolean |
findAny |
终端 | Optional<T> |
`` | `` |
findFirst |
终端 | Optional<T> |
`` | `` |
forEach |
终端 | void |
Consumer<T> |
T -> void |
collect |
终端 | R |
Collector<T, A, R> |
`` |
reduce`` | 终端(有状态-有界) | Optional<T> |
BinaryOperator<T> |
(T, T)-> T |
count |
终端 | long |
`` | `` |
即Collectors
类提供的工厂方法(例如groupingBy)建立的收集器。它们主要提供了三大功能:
Collectors类的静态工厂方法
工厂方法 | 返回类型 | 用于 |
---|---|---|
toList | List<T> | 把流中全部项目收集到一个List |
使用示例:
List<Dish> dishes = menuStream.collect(toList());
工厂方法 | 返回类型 | 用于 |
---|---|---|
toSet | Set<T> | 把流中全部项目收集到一个Set,删除重复项 |
使用示例:
Set<Dish> dishes = menuStream.collect(toSet());
工厂方法 | 返回类型 | 用于 |
---|---|---|
toCollection | Collection<T> | 把流中全部项目收集到给定的供应源建立的集合 |
使用示例:
Collection<Dish> dishes = menuStream.collect(toCollection(),ArrayList::new);
工厂方法 | 返回类型 | 用于 |
---|---|---|
counting | Long | 计算流中元素的个数 |
使用示例:
long howManyDishes = menuStream.collect(counting());
工厂方法 | 返回类型 | 用于 |
---|---|---|
summingInt | Integer | 对流中项目的一个整数属性求和 |
使用示例:
int totalCalories = menuStream.collect(summingInt(Dish::getCalories));
工厂方法 | 返回类型 | 用于 |
---|---|---|
averagingInt | Double | 计算流中项目Integer属性的平均值 |
使用示例:
double avgCalories = menuStream.collect(averagingInt(Dish::getCalories));
工厂方法 | 返回类型 | 用于 |
---|---|---|
summarizingInt | IntSummaryStatistics | 收集关于流中项目Integer属性的统计值,例如最大、最小、总和与平均值 |
使用示例:
IntSummaryStatistics menuStatistics = menuStream.collect(summarizingInt(Dish::getCalories));
工厂方法 | 返回类型 | 用于 |
---|---|---|
joining` | String | 链接对流中每一个项目调用toString方法所生成的字符串 |
使用示例:
String shortMenu = menuStream.map(Dish::getName).collect(joining(", "));
工厂方法 | 返回类型 | 用于 |
---|---|---|
maxBy | Optional<T> | 一个包裹了流中按照给定比较器选出的最大元素的Optional,或若是流为空则为Optional.empty() |
使用示例:
Optional<Dish> fattest = menuStream.collect(maxBy(comparingInt(Dish::getCalories)));
工厂方法 | 返回类型 | 用于 |
---|---|---|
minBy | Optional<T> | 一个包裹了流中按照给定比较器选出的最小元素的Optional,或若是流为空则为Optional.empty() |
使用示例:
Optional<Dish> lightest = menuStream.collect(minBy(comparingInt(Dish::getCalories)));
工厂方法 | 返回类型 | 用于 |
---|---|---|
reducing | 归约操做产生的类型 | 从一个做为累加器的初始值开始,利用BinaryOperator与流中的元素逐个结合,从而将流归约为单个值 |
使用示例:
int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));
工厂方法 | 返回类型 | 用于 |
---|---|---|
collectingAndThen | 转换函数返回的类型 | 包裹另外一个收集器,对其结果应用转换函数 |
使用示例:
int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size));
工厂方法 | 返回类型 | 用于 |
---|---|---|
groupingBy | Map<K, List<T>> | 根据项目的一个属性的值对流中的项目做问组,并将属性值做为结果Map 的键 |
使用示例:
Map<Dish.Type,List<Dish>> dishesByType = menuStream.collect(groupingBy(Dish::getType));
工厂方法 | 返回类型 | 用于 |
---|---|---|
partitioningBy | Map<Boolean,List<T>> | 根据对流中每一个项目应用谓词的结果来对项目进行分区 |
使用示例:
Map<Boolean,List<Dish>> vegetarianDishes = menuStream.collect(partitioningBy(Dish::isVegetarian));
在Java 7以前,并行处理数据集合很是麻烦。第一,你得明确地把包含数据的数据结构分红若干子部分。第二,你要给每一个子部分分配一个独立的线程。第三,你须要在恰当的时候对它们进行同步来避免不但愿出现的竞争条件,等待全部线程完成,最后把这些部分结果合并起来。Java 7引入了一个叫做分支/合并的框架,让这些操做更稳定、更不易出错。
咱们简要地提到了Stream
接口可让你很是方便地处理它的元素:能够经过对收集源调用parallelStream
方法来把集合转换为并行流。并行流就是一个把内容分红多个数据块,并用不一样的线程分别处理每一个数据块的流。这样一来,你就能够自动把给定操做的工做负荷分配给多核处理器的全部内核,让它们都忙起来。
通常而言,想给出任何关于何时该用并行流的定量建议都是不可能也毫无心义的,由于任何相似于“仅当至少有一千个(或一百万个或随便什么数字)元素的时候才用并行流)”的建议对于某台特定机器上的某个特定操做多是对的,但在略有差别的另外一种状况下可能就是大错特错。尽管如此,咱们至少能够提出一些定性意见,帮你决定某个特定状况下是否有必要使用并行流。
源 | 可分解性 |
---|---|
ArrayList | 极佳 |
LinkedList | 差 |
IntStream.range | 极佳 |
Stream.iterate | 差 |
HashSet | 好 |
TreeSet | 好 |
Java 8的库提供了Optional<T>类,这个类容许你在代码中指定哪个变量的值既多是类型T的值,也多是由静态方法Optional.empty表示的缺失值。不管是对于理解程序逻辑,抑或是对于编写产品文档而言,这都是一个重大的好消息,你如今能够经过一种数据类型表示显式缺失的值——使用空指针的问题在于你没法确切了解出现空指针的缘由,它是预期的状况,仍是说因为以前的某一次计算出错致使的一个偶然性的空值,有了Optional以后你就不须要再使用以前容易出错的空指针来表示缺失的值了。
方法 | 描述 |
---|---|
empty | 返回一个空的Optional实例 |
filter | 若是值存在而且知足提供的谓词,就返回包含该值的Optional对象;不然返回一个空的Optional对象 |
flatMap | 若是值存在,就对该值执行提供的mapping函数调用,返回一个Optional类型的值,不然就返回一个空的Optional对象 |
get | 若是该值存在,将该值用Optional封装返回,不然抛出一个NoSuchElementException异常 |
ifPresent | 若是值存在,就执行使用该值的方法调用,不然什么也不作 |
isPresent | 若是值存在就返回true,不然返回false |
map | 若是值存在,就对该值执行提供的mapping函数调用 |
of | 将指定值用Optional封装以后返回,若是该值为null,则抛出一个NullPointerException异常 |
ofNullable | 将指定值用Optional封装以后返回,若是该值为null,则返回一个空的Optional对象 |
orElse | 若是有值则将其返回,不然返回一个默认值 |
orElseGet | 若是有值则将其返回,不然返回一个由指定的Supplier接口生成的值 |
orElseThrow | 若是有值则将其返回,不然抛出一个由指定的Supplier接口生成的异常 |
Java从Java 5版本就提供了Future接口。Future对于充分利用多核处理能力是很是有益的,由于它容许一个任务在一个新的核上生成一个新的子线程,新生成的任务能够和原来的任务同时运行。原来的任务须要结果时,它能够经过get方法等待Future运行结束(生成其计算的结果值)。
咱们知道Future接口提供了方法来检测异步计算是否已经结束(使用isDone方法),等待异步操做结束,以及获取计算的结果。可是这些特性还不足以让你编写简洁的并发代码。好比,咱们很难表述Future结果之间的依赖性;从文字描述上这很简单,“当长时间计算任务完成时,请将该计算的结果通知到另外一个长时间运行的计算任务,这两个计算任务都完成后,将计算的结果与另外一个查询操做结果合并”。可是,使用Future中提供的方法完成这样的操做又是另一回事。这也是咱们须要更具描述能力的特性的缘由,好比下面这些。
CompletableFuture 详解
一个很是有用,不过不那么精确的格言这么说:“Completable-Future对于Future的意义就像Stream之于Collection。”让咱们比较一下这两者。
Java的API提供了不少有用的组件,能帮助你构建复杂的应用。不过,Java API也不老是完美的。咱们相信大多数有经验的程序员都会赞同Java 8以前的库对日期和时间的支持就很是不理想。然而,你也不用太担忧:Java 8中引入全新的日期和时间API就是要解决这一问题。
开始使用新的日期和时间API时,你最早碰到的多是LocalDate类。该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。
你能够经过静态工厂方法of建立一个LocalDate实例。LocalDate实例提供了多种方法来读取经常使用的值,好比年份、月份、星期几等,以下所示。
LocalDate date = LocalDate.of(2014, 3, 18); ←─2014-03-18 int year = date.getYear(); ←─2014 Month month = date.getMonth(); ←─MARCH int day = date.getDayOfMonth(); ←─18 DayOfWeek dow = date.getDayOfWeek(); ←─TUESDAY int len = date.lengthOfMonth(); ←─31 (days in March) boolean leap = date.isLeapYear(); ←─false (not a leap year) //你还可使用工厂方法从系统时钟中获取当前的日期: LocalDate today = LocalDate.now();
LocalTime和LocalDateTime都提供了相似的方法。
做为人,咱们习惯于以星期几、几号、几点、几分这样的方式理解日期和时间。毫无疑问,这种方式对于计算机而言并不容易理解。从计算机的角度来看,建模时间最天然的格式是表示一个持续时间段上某个点的单一大整型数。这也是新的java.time.Instant类对时间建模的方式,基本上它是以Unix元年时间(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的秒数进行计算。
你能够经过向静态工厂方法ofEpochSecond传递一个表明秒数的值建立一个该类的实例。静态工厂方法ofEpochSecond还有一个加强的重载版本,它接收第二个以纳秒为单位的参数值,对传入做为秒数的参数进行调整。重载的版本会调整纳秒参数,确保保存的纳秒分片在0到999 999 999之间。这意味着下面这些对ofEpochSecond工厂方法的调用会返回几乎一样的Instant对象:
Instant.ofEpochSecond(3); Instant.ofEpochSecond(3, 0); Instant.ofEpochSecond(2, 1_000_000_000); ←─2 秒以后再加上100万纳秒(1秒) Instant.ofEpochSecond(4, -1_000_000_000); ←─4秒以前的100万纳秒(1秒)
正如你已经在LocalDate及其余为便于阅读而设计的日期-时间类中所看到的那样,Instant类也支持静态工厂方法now,它可以帮你获取当前时刻的时间戳。咱们想要特别强调一点,Instant的设计初衷是为了便于机器使用。它包含的是由秒及纳秒所构成的数字。因此,它没法处理那些咱们很是容易理解的时间单位。好比下面这段语句:
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
它会抛出下面这样的异常:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field:
DayOfMonth
可是你能够经过Duration和Period类使用Instant,接下来咱们会对这部份内容进行介绍。
目前为止,你看到的全部类都实现了Temporal接口,Temporal接口定义了如何读取和操纵为时间建模的对象的值。以前的介绍中,咱们已经了解了建立Temporal实例的几种方法。很天然地你会想到,咱们须要建立两个Temporal对象之间的duration。Duration类的静态工厂方法between就是为这个目的而设计的。你能够建立两个LocalTimes对象、两个LocalDateTimes对象,或者两个Instant对象之间的duration,以下所示:
Duration d1 = Duration.between(time1, time2); Duration d1 = Duration.between(dateTime1, dateTime2); Duration d2 = Duration.between(instant1, instant2);
因为LocalDateTime和Instant是为不一样的目的而设计的,一个是为了便于人阅读使用,另外一个是为了便于机器处理,因此你不能将两者混用。若是你试图在这两类对象之间建立duration,会触发一个DateTimeException异常。此外,因为Duration类主要用于以秒和纳秒衡量时间的长短,你不能仅向between方法传递一个LocalDate对象作参数。
若是你须要以年、月或者日的方式对多个时间单位建模,可使用Period类。使用该类的工厂方法between,你可使用获得两个LocalDate之间的时长,以下所示:
Period tenDays = Period.between(LocalDate.of(2014, 3, 8), LocalDate.of(2014, 3, 18));
最后,Duration和Period类都提供了不少很是方便的工厂类,直接建立对应的实例;换句话说,就像下面这段代码那样,再也不是只能以两个temporal对象的差值的方式来定义它们的对象。
建立Duration和Period对象
Duration threeMinutes = Duration.ofMinutes(3); Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES); Period tenDays = Period.ofDays(10); Period threeWeeks = Period.ofWeeks(3); Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
Duration类和Period类共享了不少类似的方法:
方法名 | 是不是静态方法 | 方法描述 |
---|---|---|
between | 是 | 建立两个时间点之间的interval |
from | 是 | 由一个临时时间点建立interval |
of | 是 | 由它的组成部分建立interval的实例 |
parse | 是 | 由字符串建立interval的实例 |
addTo | 否 | 建立该interval的副本,并将其叠加到某个指定的temporal对象 |
get | 否 | 读取该interval的状态 |
isNegative | 否 | 检查该interval是否为负值,不包含零 |
isZero | 否 | 检查该interval的时长是否为零 |
minus | 否 | 经过减去必定的时间建立该interval的副本 |
multipliedBy | 否 | 将interval的值乘以某个标量建立该interval的副本 |
negated | 否 | 以忽略某个时长的方式建立该interval的副本 |
plus | 否 | 以增长某个指定的时长的方式建立该interval的副本 |
subtractFrom | 否 | 从指定的temporal对象中减去该interval |