️This article has been over 2 years since the last update.
在Java中,各种大厂的集合类工具如下
- org.apache.commons.collections4
- RxJava
- Guava
- Java8 stream
- Groovy(我个人更推荐)
上面所有的函数均支持泛型,由于Java8与RxJava已经用的太熟了,collections4内部设计不太喜欢,所以下文以Guava为例进行讲解
由于SpringBoot等主流项目中均使用18作为Guava版本,因此本文也用了比较老的18版
目录
[TOC]
关于惰性求值
在函数式编程中,一般有三种结果输出函数式操作后的结果:
- 惰性求值:内部保存着最原始数据与处理函数,List被调用时才会进行执行函数,特点是循环效率最高,但是在进行RPC或者serialization时,结果不可预料,需要保证元素被遍历。每次写线上代码时,用惰性求值都感觉是一个定时炸弹。
- 修改内部值:内部数据在处理函数被遍历时被修改,特点是空间占用最小,但是某些数据结构下比如ArrayList进行filter操作时,时间复杂度爆高。
- 新拷贝:新建一个对象,并通过原始List与函数依次赋值。这个操作最简单安全,一般也在自己的工具类中这样写,但是执行效率较低,空间比较浪费。
1 | //进行函数式变化时,需要新建一个对象 |
目前FP中,比如Haskell、Java8、Guava等函数式内部实现均采用了惰性求值。
Guava的隐患点
Guava作为Google开发的框架,支持简化代码、对RandomAccess进行优化,不过它采取了惰性求值。也就是说在经过函数式转换后的List,保留了原始List数据与映射函数
1 | //Guava中变换后的List |
也就是只有它被读取遍历时,才会进行真正的转换操作。在进行序列化时,可能直接反射搞到原始List,而导致结果仍然是转换前的List,
因此需要注意:
- 序列化时可能结果不可预料,需要函数同样实现序列化接口
- 在变换后的List中,除了list的for循环操作,其它操作(比如size,add)或多或少会导致危险或者性能问题。因此在完成函数式操作流程后,建议返回一个不可变的List或者常规的ArrayList,以免节外生枝。
- 在Guava中,如果你使用SOAP、JSON、Hibernate、ProtocolBuf或者自己定义的传输语法工具进行通信时,你需要测试你的序列化工具是否能够正确转化,即序列化操作本身是否对List进行遍历,比如Gson会使用CollectionTypeAdapterFactory作为List的遍历类,可以看出内部有forLoop的代码,因此在Gson使用惰性求值是安全的。
Guava的函数式操作
map
映射操作,这个是最常见的,用于将一种列表转为另一种列表,在大多数语言中使用map作为函数名称,但是Guava中使用的却是transform
1 | List<Integer> in = Lists.newArrayList(1,3,5,7,9); |
当然我平时更喜欢用
1 | List<String> s = ["aa", "bbb", "333Cs", "11eerS"] |
Functions内有很多现成的工厂,可以翻翻找找用,比如
Functions.toString()
zip
zip操作不支持,这个比较蛋疼,也就是没法用两个List拼装为一个List了
unique
unique是去重操作(guava remove duplicate)
1 | List<String> s = ["11","2222","333","11"] |
如果是多个List混在一起再去重
1 | List<String> s = ["11","2222","333","11"] |
filter
过滤操作同样均是惰性求值,API与Java8类似,使用此函数可以避免在主业务中编写for;break;contine等代码,以简化代码还原的流程。
注意,此函数调用后的List性能极其差,除了for循环操作,其它size、add等操作都是O(N)时间,因此一定要万分小心
过滤出符合条件的List
并没有Lists.filter
的操作,原因如下
The biggest concern here is that too many operations become expensive, linear-time propositions. If you want to filter a list and get a list back, and not just a
Collection
or anIterable
, you can useImmutableList.copyOf(Iterables.filter(list, predicate))
, which “states up front” what it’s doing and how expensive it is.
所以日常代码如下
1 | Collections2.filter(in, new Predicate<Integer>() { |
或者
1 | Iterables.filter(in, new Predicate<Integer>() { |
它们返回的对象都是一个危险的惰性集合。
Predicates内有很多现成的工厂,可以翻翻找找用,比如
Predicates.notNull()
当然还有更推荐使用的FluentIterable.filter
,我就不重复写了
一票否决式过滤
要求集合所有元素满足特定条件,有一个条件不满足就一票否决
1 | boolean isAllAdult = Iterables.all(list, new Predicate<Integer>() { |
一票通过式过滤
有一个条件满足,就返回true
1 | boolean isExist = return Iterables.any(list, new Predicate<Integer>() { |
找出第一个符合条件的元素
从List中找到第一个符合条件的元素T,并返回Optional<T>
对象
1 | Optional<Integer> adult = Iterables.tryFind(in, new Predicate<Integer>() { |
还有另一种带默认值的方法
1 | Integer adult = Iterables.find(in, new Predicate<Integer>() { |
如何安全地生成新拷贝对象
刚刚已经说过了,惰性求值过度优化可能导致奇葩结果。为了安全地传递给下家,可能需要将惰性求值转换为新值。
第一种方法(最推荐),如果想偷懒的话,直接创建一个一定执行遍历操作的对象,将返回一个不可变的数组
1 | FluentIterable.from(in) |
或者下面这样的,但是更丑,结果比较保险,是常见的数组List
1 | ImmutableList.copyOf(...) |
对比Java8的惰性求值,还是Java8更简洁
1 | List<String> o = in.stream().map(new Function<Integer, String>() { |
目前我已经很少使用Guava,主要是换成了Stream与Groovy,可以参考这里