浅析 Google Guava
# 浅析 Google Guava
来源:极客时间《设计模式之美》 (opens new window)专栏笔记
# 1. Google Guava 介绍
Google Guava 是 Google 公司内部 Java 开发工具库的开源版本。Google 内部的很多 Java 项目都在使用它。它提供了一些 JDK 没有提供的功能,以及对 JDK 已有功能的增强功能。其中就包括:集合(Collections)、缓存(Caching)、原生类型支持(Primitives Support)、并发库(Concurrency Libraries)、通用注解(Common Annotation)、字符串处理(Strings Processing)、数学计算(Math)、I/O、事件总线(EventBus)等等。
JDK 的全称是 Java Development Kit。它本身就是 Java 提供的工具类库。那现在请你思考一下,既然有了 JDK,为什么 Google 还要开发一套新的类库 Google Guava?是否是重复早轮子?两者的差异化在哪里?
# 2. 如何发现通用的功能模块
实际上,做业务开发也会涉及很多非业务功能的开发,比如ID 生成器、性能计数器、EventBus、DI 容器、限流框架、幂等框架、灰度组件。关键在于要有善于发现、善于抽象的能力,并且具有扎实的设计、开发能力,能够发现这些非业务的、可复用的功能点,并且从业务逻辑中将其解耦抽象出来,设计并开发成独立的功能模块。
在业务开发中,跟业务无关的通用功能模块,常见的一般有三类:类库(library)、框架(framework)、功能组件(component)等。
Google Guava 属于类库,提供一组 API 接口。EventBus、DI 容器属于框架,提供骨架代码,能让业务开发人员聚焦在业务开发部分,在预留的扩展点里填充业务代码。ID 生成器、性能计数器属于功能组件,提供一组具有某一特殊功能的 API 接口,有点类似类库,但更加聚焦和重量级,比如,ID 生成器有可能会依赖 Redis 等外部系统,不像类库那么简单。
不管是类库、框架还是功能组件,这些通用功能模块有两个最大的特点:复用和业务无关。Google Guava 就是一个典型的例子。
如果没有复用场景,那也就没有了抽离出来,设计成独立模块的必要了。如果与业务有关又可复用,大部分情况下会设计成独立的系统(比如微服务),而不是类库、框架或功能组件。所以如果代码与业务无关并且可能会被复用,那就可以考虑将它独立出来,开发成类库、框架、功能组件等通用功能模块。
# 3. 如何开发通用的功能模块
作为通用的类库、框架、功能组件,通常希望开发出来之后,不仅仅是自己项目使用,还能用在其他团队的项目中,甚至可以开源出来供更多人所用,这样才能发挥它更大的价值,构建自己的影响力。
产品意识:所以对于这些类库、框架、功能组件的开发,不能闭门造车,要把它们当作“产品”来开发。这个产品是一个“技术产品”,目标用户是“程序员”,解决的是他们的“开发痛点”。要多换位思考,站在用户的角度上,来想他们到底想要什么样的功能。对于一个技术产品来说,尽管 Bug 少、性能好等技术指标至关重要,但是否易用、易集成、易插拔、文档是否全面、是否容易上手等,这些产品素质也非常重要,甚至还能起到决定性作用。
具体到 Google Guava,它是一个开发类库,目标用户是 Java 开发工程师,解决用户主要痛点是,相对于 JDK,提供更多的工具类,简化代码编写,比如,它提供了用来判断 null 值的 Preconditions 类;Splitter、Joiner、CharMatcher 字符串处理类;Multisets、Multimaps、Tables 等更丰富的 Collections 类等等。
它的优势有这样几点:第一,由 Google 管理、长期维护,经过充分的单元测试,代码质量有保证;第二,可靠、性能好、高度优化,比如 Google Guava 提供的 Immutable Collections 要比 JDK 的 unmodifiableCollection 性能好;第三,全面、完善的文档,容易上手,学习成本低。
服务意识:别的团队使用我们开发出来的技术产品,要有抽出大量时间答疑、充当客服角色的心理准备。
代码质量意识:开发这种被多处复用的通用代码,对代码质量的要求更高些,因为这些项目的影响面更大,一旦出现 bug,会牵连很多系统或其他项目。如果开源影响就更大。所以,这类项目的代码质量一般都很好,开发这类项目对代码能力的锻炼更有大。
具体到 Google Guava,它是 Google 员工开发的,单元测试很完善,注释写得很规范,代码写得也很好,可以说是学习 Google 开发经验的一手资料。
尽管开发这些通用功能模块更加锻炼技术,但也不要重复造轮子,能复用的尽量复用。建议初期先把这些通用的功能作为项目的一部分来开发。不过,在开发的时候做好模块化工作,将它们尽量跟其他模块划清界限,通过接口、扩展点等松耦合的方式跟其他模式交互。等到时机成熟了,再将它从项目中剥离出来。因为之前模块化做的好,耦合程度低,剥离出来的成本也就不会很高。
# 4. Builder 模式在 Guava 中的应用
常用的缓存系统有 Redis、Memcache 等。但如果要缓存的数据比较少,完全没必要在项目中独立部署一套缓存系统。毕竟系统都有一定出错的概率,项目中包含的系统越多,那组合起来,项目整体出错的概率就会升高,可用性就会降低。同时,多引入一个系统就要多维护一个系统,项目维护的成本就会变高。
取而代之,可以在系统内部构建一个内存缓存,跟系统集成在一起开发、部署。那如何构建内存缓存呢?可以基于 JDK 提供的类,比如 HashMap,从零开始开发内存缓存。不过从零开发一个内存缓存,涉及的工作就会比较多,比如缓存淘汰策略等。为了简化开发可以使用 Google Guava 提供的现成的缓存工具类 com.google.common.cache.*。
public class CacheDemo {
public static void main(String[] args) {
Cache<String, String> cache = CacheBuilder.newBuilder()
.initialCapacity(100)
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
cache.put("key1", "value1");
String value = cache.getIfPresent("key1");
System.out.println(value);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
Cache 对象是通过 CacheBuilder 这样一个 Builder 类来创建的。为什么要由 Builder 类来创建 Cache 对象呢?
构建一个缓存需要配置 n 多参数,比如过期时间、淘汰策略、最大缓存大小等等。相应地,Cache 类就会包含 n 多成员变量。需要在构造函数中设置这些成员变量的值,但又不是所有的值都必须设置,设置哪些值由用户来决定。为了满足这个需求就需要定义多个包含不同参数列表的构造函数。
为了避免构造函数的参数列表过长、不同的构造函数过多,一般有两种解决方案。其中一个解决方案是使用 Builder 模式;另一个方案是先通过无参构造函数创建对象,然后再通过 setXXX() 方法来逐一设置需要的设置的成员变量。
为什么 Guava 选择第一种而不是第二种解决方案呢?使用第二种解决方案是否也可以呢?答案是不行的。查看 CacheBuilder 类中的 build() 函数代码可知:
public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
this.checkWeightWithWeigher();
this.checkNonLoadingCache();
return new LocalManualCache(this);
}
private void checkNonLoadingCache() {
Preconditions.checkState(this.refreshNanos == -1L, "refreshAfterWrite requires a LoadingCache");
}
private void checkWeightWithWeigher() {
if (this.weigher == null) {
Preconditions.checkState(this.maximumWeight == -1L, "maximumWeight requires weigher");
} else if (this.strictParsing) {
Preconditions.checkState(this.maximumWeight != -1L, "weigher requires maximumWeight");
} else if (this.maximumWeight == -1L) {
logger.log(Level.WARNING, "ignoring weigher specified without maximumWeight");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
必须使用 Builder 模式的主要原因是,在真正构造 Cache 对象的时候,必须做一些必要的参数校验,也就是 build() 函数中前两行代码要做的工作。如果采用无参默认构造函数加 setXXX() 方法的方案,这两个校验就无处安放了。而不经过校验,创建的 Cache 对象有可能是不合法、不可用的。
# 5. Wrapper 模式在 Guava 中的应用
在 Google Guava 的 collection 包路径下,有一组以 Forwarding 开头命名的类。
其中 ForwardingCollection 部分代码如下:
@GwtCompatible
public abstract class ForwardingCollection<E> extends ForwardingObject implements Collection<E> {
protected ForwardingCollection() {
}
protected abstract Collection<E> delegate();
public Iterator<E> iterator() {
return this.delegate().iterator();
}
public int size() {
return this.delegate().size();
}
@CanIgnoreReturnValue
public boolean removeAll(Collection<?> collection) {
return this.delegate().removeAll(collection);
}
public boolean isEmpty() {
return this.delegate().isEmpty();
}
public boolean contains(Object object) {
return this.delegate().contains(object);
}
@CanIgnoreReturnValue
public boolean add(E element) {
return this.delegate().add(element);
}
@CanIgnoreReturnValue
public boolean remove(Object object) {
return this.delegate().remove(object);
}
public boolean containsAll(Collection<?> collection) {
return this.delegate().containsAll(collection);
}
@CanIgnoreReturnValue
public boolean addAll(Collection<? extends E> collection) {
return this.delegate().addAll(collection);
}
@CanIgnoreReturnValue
public boolean retainAll(Collection<?> collection) {
return this.delegate().retainAll(collection);
}
public void clear() {
this.delegate().clear();
}
public Object[] toArray() {
return this.delegate().toArray();
}
//...省略部分代码...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
以及 ForwardingCollection 的子类 AddLoggingCollection :
public class AddLoggingCollection<E> extends ForwardingCollection<E> {
private static final Logger logger = LoggerFactory.getLogger(AddLoggingCollection.class);
private Collection<E> originalCollection;
public AddLoggingCollection(Collection<E> originalCollection) {
this.originalCollection = originalCollection;
}
@Override
protected Collection delegate() {
return this.originalCollection;
}
@Override
public boolean add(E element) {
logger.info("Add element: " + element);
return this.delegate().add(element);
}
@Override
public boolean addAll(Collection<? extends E> collection) {
logger.info("Size of elements to add: " + collection.size());
return this.delegate().addAll(collection);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
在上面的代码中,AddLoggingCollection 是基于代理模式实现的一个代理类,它在原始 Collection 类的基础之上,针对“add”相关的操作,添加了记录日志的功能。
代理模式、装饰器、适配器模式可以统称为 Wrapper 模式,通过 Wrapper 类二次封装原始类。它们的代码实现也很相似,都可以通过组合的方式,将 Wrapper 类的函数实现委托给原始类的函数来实现。
public interface Interf {
void f1();
void f2();
}
public class OriginalClass implements Interf {
@Override
public void f1() { //... }
@Override
public void f2() { //... }
}
public class WrapperClass implements Interf {
private OriginalClass oc;
public WrapperClass(OriginalClass oc) {
this.oc = oc;
}
@Override
public void f1() {
//...附加功能...
this.oc.f1();
//...附加功能...
}
@Override
public void f2() {
this.oc.f2();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
如果不使用这个 ForwardinCollection 类,而是让 AddLoggingCollection 代理类直接实现 Collection 接口,那 Collection 接口中的所有方法,都要在 AddLoggingCollection 类中实现一遍,而真正需要添加日志功能的只有 add() 和 addAll() 两个函数,其他函数的实现,都只是类似 Wrapper 类中 f2() 函数的实现那样,简单地委托给原始 collection 类对象的对应函数。
为了简化 Wrapper 模式的代码实现,Guava 提供一系列缺省的 Forwarding 类。用户在实现自己的 Wrapper 类的时候,基于缺省的 Forwarding 类来扩展,就可以只实现自己关心的方法,其他不关心的方法使用缺省 Forwarding 类的实现,就像 AddLoggingCollection 类的实现那样。
# 6. Immutable 模式在 Guava 中的应用
Immutable 模式,中文叫作不变模式,它并不属于经典的 23 种设计模式,但作为一种较常用的设计思路,可以总结为一种设计模式来学习。
一个对象的状态在对象创建之后就不再改变,这就是所谓的不变模式。其中涉及的类就是不变类(Immutable Class),对象就是不变对象(Immutable Object)。在 Java 中,最常用的不变类就是 String 类,String 对象一旦创建之后就无法改变。
不变模式可以分为两类,一类是普通不变模式,另一类是深度不变模式(Deeply Immutable Pattern)。普通的不变模式指的是,对象中包含的引用对象是可以改变的。如果不特别说明,通常我们所说的不变模式,指的就是普通的不变模式。深度不变模式指的是,对象包含的引用对象也不可变。
// 普通不变模式
public class User {
private String name;
private int age;
private Address addr;
public User(String name, int age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
// 只有getter方法,无setter方法...
}
public class Address {
private String province;
private String city;
public Address(String province, String city) {
this.province = province;
this.city= city;
}
// 有getter方法,也有setter方法...
}
// 深度不变模式
public class User {
private String name;
private int age;
private Address addr;
public User(String name, int age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
// 只有getter方法,无setter方法...
}
public class Address {
private String province;
private String city;
public Address(String province, String city) {
this.province = province;
this.city= city;
}
// 只有getter方法,无setter方法..
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
只要这个类满足:所有的成员变量都通过构造函数一次性设置好,不暴露任何 set 等修改成员变量的方法。除此之外,因为数据不变,所以不存在并发读写问题,因此不变模式常用在多线程环境下,来避免线程加锁。所以,不变模式也常被归类为多线程设计模式。
Google Guava 针对集合类(Collection、List、Set、Map…)提供了对应的不变集合类(ImmutableCollection、ImmutableList、ImmutableSet、ImmutableMap…)。刚刚我们讲过,不变模式分为两种,普通不变模式和深度不变模式。Google Guava 提供的不变集合类属于前者,也就是说,集合中的对象不会增删,但是对象的成员变量(或叫属性值)是可以改变的。
Java JDK 也提供了不变集合类(UnmodifiableCollection、UnmodifiableList、UnmodifiableSet、UnmodifiableMap…)。两者提供的不变集合类的区别如下:
public class ImmutableDemo {
public static void main(String[] args) {
List<String> originalList = new ArrayList<>();
originalList.add("a");
originalList.add("b");
originalList.add("c");
List<String> jdkUnmodifiableList = Collections.unmodifiableList(originalList);
List<String> guavaImmutableList = ImmutableList.copyOf(originalList);
//jdkUnmodifiableList.add("d"); // 抛出UnsupportedOperationException
// guavaImmutableList.add("d"); // 抛出UnsupportedOperationException
originalList.add("d");
print(originalList); // a b c d
print(jdkUnmodifiableList); // a b c d
print(guavaImmutableList); // a b c
}
private static void print(List<String> list) {
for (String s : list) {
System.out.print(s + " ");
}
System.out.println();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 7. 函数式编程
# 什么是函数式编程
函数式编程因其编程的特殊性,仅在科学计算、数据处理、统计分析等领域,才能更好地发挥它的优势,它并不能完全替代更加通用的面向对象编程范式。
函数式编程的英文翻译是 Functional Programming。
每个编程范式都有自己独特的地方,这就是它们会被抽象出来作为一种范式的原因。面向对象编程最大的特点是:以类、对象作为组织代码的单元以及它的四大特性。面向过程编程最大的特点是:以函数作为组织代码的单元,数据与方法相分离。
函数式编程最独特的地方在于它的编程思想。函数式编程认为,程序可以用一系列数学函数或表达式的组合来表示。函数式编程是程序面向数学的更底层的抽象,将计算过程描述为表达式。理论上讲是可以的,但并不是所有的程序都适合这么做。函数式编程有它自己适合的应用场景,比如开篇提到的科学计算、数据处理、统计分析等。在这些领域,程序往往比较容易用数学表达式来表示,比起非函数式编程,实现同样的功能,函数式编程可以用很少的代码就能搞定。但是,对于强业务相关的大型业务系统开发来说,费劲吧啦地将它抽象成数学表达式,硬要用函数式编程来实现,显然是自讨苦吃。相反,在这种应用场景下,面向对象编程更加合适,写出来的代码更加可读、可维护。
具体到编程实现,函数式编程跟面向过程编程一样,也是以函数作为组织代码的单元。不过,它跟面向过程编程的区别在于,它的函数是无状态的。简单点讲就是,函数内部涉及的变量都是局部变量,不会像面向对象编程那样,共享类成员变量,也不会像面向过程编程那样,共享全局变量。函数的执行结果只与入参有关,跟其他任何外部变量无关。同样的入参,不管怎么执行,得到的结果都是一样的。这实际上就是数学函数或数学表达式的基本要求。如下代码所示:
// 有状态函数: 执行结果依赖b的值是多少,即便入参相同,多次执行函数,函数的返回值有可能不同,因为b值有可能不同。
int b;
int increase(int a) {
return a + b;
}
// 无状态函数:执行结果不依赖任何外部变量值,只要入参相同,不管执行多少次,函数的返回值就相同
int increase(int a, int b) {
return a + b;
}
2
3
4
5
6
7
8
9
10
总结:不同的编程范式之间并不是截然不同的,总是有一些相同的编程规则。比如,不管是面向过程、面向对象还是函数式编程,它们都有变量、函数的概念,最顶层都要有 main 函数执行入口,来组装编程单元(类、函数等)。只不过,面向对象的编程单元是类或对象,面向过程的编程单元是函数,函数式编程的编程单元是无状态函数。
# Java 对函数式编程的支持
例子:下面代码作用是从一组字符串数组中,过滤出长度小于等于 3 的字符串,并且求得这其中的最大长度。
public class FPDemo {
public static void main(String[] args) {
Optional<Integer> result = Stream.of("f", "ba", "hello")
.map(s -> s.length())
.filter(l -> l <= 3)
.max((o1, o2) -> o1-o2);
System.out.println(result.get()); // 输出2
}
}
2
3
4
5
6
7
8
9
Java 为函数式编程引入了三个新的语法概念:Stream 类、Lambda 表达式和函数接口(Functional Inteface)。Stream 类用来支持通过“.”级联多个函数操作的代码编写方式;引入 Lambda 表达式的作用是简化代码编写;函数接口的作用是让我们可以把函数包裹成函数接口,来实现把函数当做参数一样来使用(Java 不像 C 一样支持函数指针,可以把函数直接当参数来使用)。
Stream 类
要计算这样一个表达式:(3-1)*2+5,代码实现如下:
add(multiply(subtract(3,1),2),5);
1换种更易懂的写法:
subtract(3,1).multiply(2).add(5);
1在 Java 中,“.”表示调用某个对象的方法。为了支持上面这种级联调用方式,让每个函数都返回一个通用的类型:Stream 类对象。在 Stream 类上的操作有两种:中间操作和终止操作。中间操作返回的仍然是 Stream 类对象,而终止操作返回的是确定的值结果。
再来看之前的例子,其中 map、filter 是中间操作,返回 Stream 类对象,可以继续级联其他操作;max 是终止操作,返回的不是 Stream 类对象,无法再继续往下级联处理了。
public class FPDemo { public static void main(String[] args) { Optional<Integer> result = Stream.of("f", "ba", "hello") // of返回Stream<String>对象 .map(s -> s.length()) // map返回Stream<Integer>对象 .filter(l -> l <= 3) // filter返回Stream<Integer>对象 .max((o1, o2) -> o1-o2); // max终止操作:返回Optional<Integer> System.out.println(result.get()); // 输出2 } }
1
2
3
4
5
6
7
8
9Lambda 表达式
Java 引入 Lambda 表达式的主要作用是简化代码编写。下面有三段代码,第一段代码展示了 map 函数的定义,实际上,map 函数接收的参数是一个 Function 接口。第二段代码展示了 map 函数的使用方式。第三段代码是针对第二段代码用 Lambda 表达式简化之后的写法。实际上,Lambda 表达式在 Java 中只是一个语法糖而已,底层是基于函数接口来实现的,也就是第二段代码展示的写法。
// Stream中map函数的定义: public interface Stream<T> extends BaseStream<T, Stream<T>> { <R> Stream<R> map(Function<? super T, ? extends R> mapper); //...省略其他函数... } // Stream中map的使用方法: Stream.of("fo", "bar", "hello").map(new Function<String, Integer>() { @Override public Integer apply(String s) { return s.length(); } }); // 用Lambda表达式简化后的写法: Stream.of("fo", "bar", "hello").map(s -> s.length());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Lambda 表达式包括三部分:输入、函数体、输出。表示出来的话就是下面这个样子:
(a, b) -> { 语句1; 语句2;...; return 输出; } //a,b是输入参数
1Lambda 表达式的写法非常灵活,还有很多简化写法。比如,如果输入参数只有一个,可以省略 (),直接写成 a->{…};如果没有入参,可以直接将输入和箭头都省略掉,只保留函数体;如果函数体只有一个语句,那可以将{}省略掉;如果函数没有返回值,return 语句就可以不用写了。
把之前例子中的 Lambda 表达式,全部替换为函数接口的实现方式:
Optional<Integer> result = Stream.of("f", "ba", "hello") .map(s -> s.length()) .filter(l -> l <= 3) .max((o1, o2) -> o1-o2); // 还原为函数接口的实现方式 Optional<Integer> result2 = Stream.of("fo", "bar", "hello") .map(new Function<String, Integer>() { @Override public Integer apply(String s) { return s.length(); } }) .filter(new Predicate<Integer>() { @Override public boolean test(Integer l) { return l <= 3; } }) .max(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 - o2; } });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25函数接口
上面一段代码中的 Function、Predicate、Comparator 都是函数接口。C 语言支持函数指针,它可以把函数直接当变量来使用。但 Java 没有函数指针这样的语法,所以通过函数接口,将函数包裹在接口中,当作变量来使用。
函数接口就是接口,但要求只包含一个未实现的方法。因为只有这样,Lambda 表达式才能明确知道匹配的是哪个接口。
Java 提供的 Function、Predicate 这两个函数接口的源码如下:
@FunctionalInterface public interface Function<T, R> { R apply(T t); // 只有这一个未实现的方法 default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } static <T> Function<T, T> identity() { return t -> t; } } @FunctionalInterface public interface Predicate<T> { boolean test(T t); // 只有这一个未实现的方法 default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } default Predicate<T> negate() { return (t) -> !test(t); } default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 8. Guava 对函数式编程的增强
Google Guava 并没有提供太多函数式编程的支持,仅仅封装了几个遍历集合操作的接口,如下:
Iterables.transform(Iterable, Function);
Iterators.transform(Iterator, Function);
Collections.transfrom(Collection, Function);
Lists.transform(List, Function);
Maps.transformValues(Map, Function);
Multimaps.transformValues(Mltimap, Function);
...
Iterables.filter(Iterable, Predicate);
Iterators.filter(Iterator, Predicate);
Collections2.filter(Collection, Predicate);
...
2
3
4
5
6
7
8
9
10
11
Google 对于函数式编程的使用还是很谨慎的,认为过度地使用函数式编程,会导致代码可读性变差,强调不要滥用。
之所以对遍历集合操作做了优化,主要是因为函数式编程一个重要的应用场景就是遍历集合。如果不使用函数式编程,只能 for 循环,一个一个的处理集合中的数据。使用函数式编程,可以大大简化遍历集合操作的代码编写,一行代码就能搞定,而且在可读性方面也没有太大损失。