2020-08-04

Java 8实战 书摘
许多人都熟悉事件处理器和回调函数,即注册一个对象,它包含会在事件发生时使用的一个方法。Lambda使人更容易在Java中广泛应用这种思想。

其次,流被设计成无需同时将所有的数据调入内存(甚至根本无需计算),这样就可以处理无法装入计算机内存的流数据了。但Java 8可以对流做一些集合所不能的优化操作,例如,它可以将对同一个流的若干操作组合起来,从而只遍历一次数据,而不是花很大代价去多次遍历它。更妙的是,Java可以自动将流操作并行化(集合可不行)。

“还有函数式编程,这又是什么?”就像面向对象编程一样,它是另一种编程风格,其核心是把函数作为值

如默认方法、新的Optional 类、CompletableFuture ,以及新的日期和时间API。

表达复杂数据处理查询可以使用的流操作。我们会谈到很多模式,如筛选、切片、查找、匹配、映射和归约。
2020-03-17
第10章谈到了新的java.util.Optional 类,它能让你设计出更好的API,并减少空指针异常。
第 1 章 为什么要关心Java 8
2020-03-23
inventory.sort(comparing(Apple::getWeight)); ←─本书中第一段Java 8的代码!
2020-03-23
Java 8对硬件也有影响:平常我们用的CPU都是多核的——你的笔记本电脑或台式机上的处理器可能有四个CPU内核,甚至更多。但是,绝大多数现有的Java程序都只使用其中一个内核,其他三个都闲着,或只是用一小部分的处理能力来运行操作系统
2020-03-23
在Java 8之前,专家们可能会告诉你,必须利用线程才能使用多个内核。问题是,线程用起来很难,也容易出现错误。从Java的演变路径来看,它一直致力于让并发编程更容易、出错更少。Java 1.0里有线程和锁,甚至有一个内存模型——这是当时的最佳做法,但事实证明,不具备专门知识的项目团队很难可靠地使用这些基本模型。Java 5添加了工业级的构建模块,如线程池和并发集合。Java 7添加了分支/合并(fork/join)框架,使得并行变得更实用,但仍然很困难。而Java 8对并行有了一个更简单的新思路,不过你仍要遵循一些规则,本书中会谈到。
2020-03-23
我们用两个例子(它们有更简洁的代码,且更简单地使用了多核处理器)
2020-03-23
Stream API
2020-03-23
向方法传递代码的技巧
2020-03-23
接口中的默认方法
2020-03-23
Java 8提供了一个新的API(称为“流”,Stream),它支持许多处理数据的并行操作,其思路和在数据库查询语言中的思路类似——用更高级的方式表达想要的东西,而由“实现”(在这里是Streams库)来选择最佳低级执行机制。这样就可以避免用synchronized 编写代码,这一代码不仅容易出错,而且在多核CPU上执行所需的成本也比你想象的要高。
2020-03-23
多核CPU的每个处理器内核都有独立的高速缓存。加锁需要这些高速缓存同步运行,然而这又需要在内核间进行较慢的缓存一致性协议通信。
2020-03-23
函数式编程
1.1.3 用行为参数化把代码传递给方法
2020-03-23
Stream 就是一系列T 类型的项目。你现在可以把它看成一种比较花哨的迭代器。Stream API的很多方法可以链接起来形成一个复杂的流水线,就像先前例子里面链接起来的Unix命令一样。
2020-03-23
Java 8可以透明地把输入的不相关部分拿到几个CPU内核上去分别执行你的Stream 操作流水线——这是几乎免费 的并行,用不着去费劲搞Thread 了。
2020-03-23
用行为参数化把代码传递给方法
2020-03-23
但是在Java 8之前,你没法把这个方法传给另一个方法
2020-03-23
Java 8增加了把方法(你的代码)作为参数传递给另一个方法的能力
1.2 Java中的函数
2020-03-23
编程语言中的函数 一词通常是指方法 ,尤其是静态方法
1.2.1 方法和Lambda作为一等公民
2020-03-23
Scala和Groovy等语言的实践已经证明,让方法等概念作为一等值可以扩充程序员的工具库,从而让编程变得更容易
2020-03-23
,让方法作为值也构成了其他若干Java 8功能(如Stream )的基础。
2020-03-23
Java 8的第一个新功能是方法引用
2020-03-23
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
2020-03-23
?你已经有了函数isHidden ,因此只需用Java 8的方法引用 :: 语法(即“把这个方法作为值”)将其传给listFiles 方法
2020-03-23
方法不再是二等值了。与用对象引用 传递对象类似(对象引用是用new 创建的),在Java 8里写下File::isHidden 的时候,你就创建了一个方法引用 ,你同样可以传递它
2020-03-23
。只要方法中有代码(方法中的可执行部分),那么用方法引用就可以传递代码
2020-03-23
除了允许(命名)函数成为一等值外,Java 8还体现了更广义的将函数作为值 的思想,包括Lambda4 (或匿名函数)。比如,你现在可以写(int x) -> x + 1 ,表示“调用时给定参数x ,就返回x + 1值的函数
1.3 流
2020-03-23
此外,我们很难一眼看出来这些代码是做什么的,因为有好几个嵌套的控制流指令。
有了Stream API,你现在可以这样解决这个问题了:
2020-03-23
Map<Currency, List> transactionsByCurrencies =
transactions.stream()
.filter((Transaction t) -> t.getPrice() > 1000) ←─筛选金额较高的交易
.collect(groupingBy(Transaction::getCurrency)); ←─按货币分组
2020-03-23
和Collection API相比,Stream API处理数据的方式非常不同。用集合的话,你得自己去做迭代的过程。你得用for-each 循环一个个去迭代元素,然后再处理元素。我们把这种数据迭代的方法称为外部迭代 。相反,有了Stream API,你根本用不着操心循环的事情。数据处理完全是在库内部进行的。我们把这种思想叫作内部迭代
2020-03-23
使用集合的另一个头疼的地方是,想想看,要是你的交易量非常庞大,你要怎么处理这个巨大的列表呢?单个CPU根本搞不定这么大量的数据,但你很可能已经有了一台多核电脑。理想的情况下,你可能想让这些CPU内核共同分担处理工作,以缩短处理时间。理论上来说,要是你有八个核,那并行起来,处理数据的速度应该是单核的八倍。
1.4 默认方法
2020-03-23
默认方法
2020-03-23
Java 8中加入默认方法主要是为了支持库设计师,让他们能够写出更容易改进 的接口。这一点会在第9章中详谈。这一方法很重要,因为你会在接口中遇到越来越多的默认方法,但由于真正需要编写默认方法的程序员相对较少,而且它们只是有助于程序改进,而不是用于编写任何具
2020-03-23
但这里有个问题:在Java 8之前,List 并没有stream 或parallelStream 方法,它实现的Collection 接口也没有,因为当初还没有想到这些方法嘛!可没有这些方法,这些代码就不能编译。换作你自己的接口的话,最简单的解决方案就是让Java 8的设计者把stream 方法加入Collection 接口,并加入ArrayList 类的实现。
2020-03-23
可要是这样做,对用户来说就是噩梦了。有很多的替代集合框架都用Collection API实现了接口。但给接口加入一个新方法,意味着所有的实体类都必须为其提供一个实现。语言设计者没法控制Collections 所有现有的实现,这下你就进退两难了:你如何改变已发布的接口而不破坏已有的实现呢?
2020-03-23
Java 8的解决方法就是打破最后一环——接口如今可以包含实现类没有提供实现的方法签名了!那谁来实现它呢?缺失的方法主体随接口提供了(因此就有了默认实现),而不是由实现类提供。
2020-03-23
这就给接口设计者提供了一个扩充接口的方式,而不会破坏现有的代码。Java 8在接口声明中使用新的default 关键字来表示这一点。
2020-03-23
例如,在Java 8里,你现在可以直接对List 调用sort 方法。它是用Java 8 List 接口中如下所示的默认方法实现的,它会调用Collections.sort 静态方法:
1.5 来自函数式编程的其他好思想
2020-03-23
default void sort(Comparator<? super E> c) {
Collections.sort(this, c);
}
2020-03-23
这意味着List 的任何实体类都不需要显式实现sort ,而在以前的Java版本中,除非提供了sort 的实现,否则这些实体类在重新编译时都会失败。
2020-03-23
不过慢着,一个类可以实现多个接口,不是吗?那么,如果在好几个接口里有多个默认实现,是否意味着Java中有了某种形式的多重继承?是的,在某种程度上是这样。我们在第9章中会谈到,Java 8用一些限制来避免出现类似于C++中臭名昭著的菱形继承问题 。
2020-03-23
前几节介绍了Java中从函数式编程中引入的两个核心思想:将方法和Lambda作为一等值,以及在没有可变共享状态时,函数或方法可以有效、安全地并行执行。前面说到的新的Stream API把这两种思想都用到了。
2020-03-23
我把它叫作我的“价值亿万美金的错误”。就是在1965年发明了空引用……我无法抗拒放进一个空引用的诱惑,仅仅是因为它实现起来非常容易。
2020-03-23
在Java 8里有一个Optional 类,如果你能一致地使用它的话,就可以帮助你避免出现NullPointer 异常。它是一个容器对象,可以包含,也可以不包含一个值。Optional 中有方法来明确处理值不存在的情况,这样就可以避免NullPointer 异常了。换句话说,它使用类型系统,允许你表明我们知道一个变量可能会没有值。我们会在第10章中详细讨论Optional 。
2020-03-23
第二个想法是(结构)模式匹配
第 2 章 通过行为参数化传递代码
2020-03-23
行为参数化 就是可以帮助你处理频繁变更的需求的一种软件开发模式。
2.2 行为参数化
2020-03-23
行为参数化
2020-03-23
我们把它称为谓词 (即一个返回boolean 值的函数)。让我们定义一个接口来对选择标准建模
2020-03-23
public class AppleHeavyWeightPredicate implements ApplePredicate{ ←─仅仅选出重的苹果
public boolean test(Apple apple){
return apple.getWeight() > 150;
}
}
public class AppleGreenColorPredicate implements ApplePredicate{ ←─仅仅选出绿苹果
public boolean test(Apple apple){
return “green”.equals(apple.getColor());
}
}
2020-03-23
你可以把这些标准看作filter 方法的不同行为。你刚做的这些和“策略设计模式”1 相关,它让你定义一族算法,把它们封装起来(称为“策略”),然后在运行时选择一个算法。在这里,算法族就是ApplePredicate ,不同的策略就是AppleHeavyWeightPredicate 和AppleGreenColorPredicate 。
第四次尝试:根据抽象条件筛选
2020-03-23
第四次尝试:根据抽象条件筛选
2020-03-23

  1. 传递代码/行为
    2.3 对付啰嗦
    2020-03-23
     行为参数化:用谓词筛选苹果
    2.3.2 第五次尝试:使用匿名类
    2020-03-23
    费这么大劲儿真没必要,能不能做得更好呢?Java有一个机制称为匿名类 ,它可以让你同时声明和实例化一个类。它可以帮助你进一步改善代码,让它变得更简洁。但这也不完全令人满意。
    2020-03-23
    匿名类 和你熟悉的Java局部类(块中定义的类)差不多,但匿名类没有名字。它允许你同时声明并实例化一个类。换句话说,它允许你随用随建。
    2020-03-24
    但匿名类还是不够好。第一,它往往很笨重
    2020-03-24
    因为它占用了很多空间
    2.3.3 第六次尝试:使用Lambda表达式
    2020-03-24
    在理想的情况下,我们想鼓励程序员使用行为参数化模式,因为正如你在前面看到的,它让代码更能适应需求的变化
    2.3.4 第七次尝试:将List类型抽象化
    2020-03-24
    在通往抽象的路上,我们还可以更进一步。目前,filterApples 方法还只适用于Apple 。你还可以将List 类型抽象化
    第 3 章 Lambda表达式
    2020-03-24
    Lambda表达式
    3.1 Lambda管中窥豹
    2020-03-24
    可以把Lambda表达式 理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表
    2020-03-24
    传递 ——Lambda表达式可以作为参数传递给方法或存储在变量中。
    2020-03-24
    我们刚刚展示给你的Lambda表达式有三个部分
    2020-03-24
    参数列表 ——这里它采用了Comparator 中compare 方法的参数,两个Apple 。
    2020-03-24
    箭头 ——箭头-> 把参数列表与Lambda主体分隔开。
    2020-03-24
    Lambda主体 ——比较两个Apple 的重量。表达式就是Lambda的返回值了
    2020-03-24
    。Lambda没有return语句,因为已经隐含了return
    2020-03-24
    第三个Lambda表达式具有两个int类型的参数而没有返回值(void返回)。注意Lambda表达式可以包含多行语句,这里是两行
    2020-03-24
    第四个Lambda表达式没有参数, 返回一个int
    2020-03-24
    (1) 这个Lambda没有参数,并返回void 。它类似于主体为空的方法:public void run() {} 。
    (2) 这个Lambda没有参数,并返回String 作为表达式。
    (3) 这个Lambda没有参数,并返回String (利用显式返回语句)。
    (4) return 是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示:(Integer i) -> {return “Alan” + i;} 。
    (5)“Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号和分号,如下所示:(String s) -> “Iron Man” 。或者如果你喜欢,可以使用显式返回语句,如下所示:(String s)->{return “IronMan”;} 。
    3.2 在哪里以及如何使用Lambda
    2020-03-24
    在哪里可以使用Lambda表达式
    3.2.1 函数式接口
    2020-03-24
    函数式接口
    2020-03-24
    因为Predicate 仅仅定义了一个抽象方法:
    2020-03-24
    一言以蔽之,函数式接口 就是只定义一个抽象方法的接口。
    2020-03-24
    接口现在还可以拥有默认方法 (即在类没有对方法进行实现时,其主体为方法提供默认实现的方法)。哪怕有很多默认方法,只要接口只定义了一个抽象方法 ,它就仍然是一个函数式接口。
    2020-03-24
    只有Adder 是函数式接口。
    SmartAdder 不是函数式接口,因为它定义了两个叫作add 的抽象方法(其中一个是从Adder 那里继承来的)。
    Nothing 也不是函数式接口,因为它没有声明抽象方法。
    2020-03-24
    用函数式接口可以干什么呢?Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例 (具体说来,是函数式接口一个具体实现 的实例)。
    3.2.2 函数描述符
    2020-03-24
    函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符 。例如,Runnable 接口可以看作一个什么也不接受什么也不返回(void )的函数的签名,因为它只有一个叫作run 的抽象方法,这个方法什么也不接受,什么也不返回(void )
    2020-03-24
    。但是他们选择了现在这种方式,因为这种方式自然且能避免语言变得更复杂。此外,大多数Java程序员都已经熟悉了具有一个抽象方法的接口的理念(例如事件处理)。
    3.3 把Lambda付诸实践:环绕执行模式
    2020-03-24
    如果你去看看新的Java API,会发现函数式接口带有@FunctionalInterface 的标注
    2020-03-24
    这个标注用于表示该接口会设计成一个函数式接口。如果你用@FunctionalInterface 定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误
    2020-03-24
    环绕执行 (execute around)模式
    3.3.2 第2步:使用函数式接口来传递行为
    2020-03-24
    使用函数式接口来传递行为
    3.4.1 Predicate
    2020-03-24
    使用函数式接口
    2020-03-24
    函数式接口的抽象方法的签名称为函数描述符
    2020-03-24
    Java 8的库设计师帮你在java.util.function 包中引入了几个新的函数式接口。我们接下来会介绍Predicate 、Consumer 和Function
    3.4.2 Consumer
    2020-03-24
    java.util.function.Consumer 定义了一个名叫accept 的抽象方法,它接受泛型T 的对象,没有返回(void )。你如果需要访问类型T 的对象,并对其执行某些操作,就可以使用这个接口
    3.4.3 Function
    2020-03-24
    Function
    2020-03-24
    java.util.function.Function<T, R> 接口定义了一个叫作apply 的方法,它接受一个泛型T 的对象,并返回一个泛型R 的对象。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)
    2020-03-24
    但这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。
    2020-03-24
    Java 8中的常用函数式接口
    2020-03-24
    IntPredicate,LongPredicate,
    DoublePredicate
    2020-03-24
    IntConsumer,LongConsumer, DoubleConsumer
    3.6.1 管中窥豹
    2019-07-23
    如何构建方法引用
    2019-07-23
    写expensiveTransaction::getValue
    2019-07-23
    表达式(String s) -> s.toUppeCase() 可以写作String::toUpperCase
    2019-07-23
    ,Lambda表达式()->expensiveTransaction.getValue() 可以写作expensiveTransaction::getValue
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FlyingZCC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值