函数式编程
函数式编程一般结合集合使用,Guava对集合的操作进行了抽象,共有两种:
- 变换(transform)
- 过滤(filter)
如何实现函数式
Java是静态语言,JDK8之前不支持lambda。如何实现函数式将一个函数(闭包)作为参数传入另一个方法中?只能只用一个接口封装一下函数了,需要使用的时候,new一个接口的匿名实现类,这样模拟一个闭包。
对于变换 和过滤操作,Guava实现了两个接口:
- Function
- Predicate
分别用于变换和过滤操作。
问题
有这样的一个List,"ZHANG", "Chen", "LI", "wang", "ZHAO",求字符串全部为大写的长度,返回一个集合。
传统写法:
@Test
public void test5() {
List<String> list = Lists.newArrayList("ZHANG", "Chen", "LI", "wang", "ZHAO");
List<Integer> length = Lists.newArrayList();
for (String tmp : list) {
if (CharMatcher.JAVA_UPPER_CASE.matchesAllOf(tmp)) {
length.add(tmp.length());
}
}
System.out.println(length.toString());
}
Guava非fluent方式:
@Test
public void test3() {
List<String> list = Lists.newArrayList("ZHANG", "Chen", "LI", "wang", "ZHAO");
ArrayList<Integer> integers = Lists.newArrayList(Iterables.transform(Iterables.filter(list, new Predicate<String>() {
@Override
public boolean apply(String input) {
//CharMathcher本身也是一个Predicate
return CharMatcher.JAVA_UPPER_CASE.matchesAllOf(input);
}
}), new Function<String, Integer>() {
@Override
public Integer apply(String input) {
return input.length();
}
}));
System.out.println(integers.toString());
}
Guava fluent方式:
@Test
public void test4() {
List<String> list = Lists.newArrayList("ZHANG", "Chen", "LI", "wang", "ZHAO");
ArrayList<Integer> integers = Lists.newArrayList(FluentIterable.from(list).filter(new Predicate<String>() {
@Override
public boolean apply(String input) {
return CharMatcher.JAVA_UPPER_CASE.matchesAllOf(input);
}
}).transform(new Function<String, Integer>() {
@Override
public Integer apply(String input) {
return input.length();
}
}));
System.out.println(integers.toString());
}
可以看到fluent方式filter和transform之间是没有嵌套的。看上去Guava的函数式方式代码比传统的方式更长,这纯粹是因为Java不支持lambda,每次都需要new的Function和Predicate匿名内部类导致的。
看下如果支持lambda的Java8上的代码:
@Test
public void test4_() {
List<String> list = Lists.newArrayList("ZHANG", "Chen", "LI", "wang", "ZHAO");
ArrayList<Integer> integers = Lists.newArrayList(FluentIterable.from(list)
.filter(input -> CharMatcher.JAVA_UPPER_CASE.matchesAllOf(input))
.transform(input->input.length()));
System.out.println(integers.toString());
}
使用了lambda,代码就简洁了很多。变换和过滤可以看成两种操作符,这和RxJava很像,不过RxJava做的更通用,表现在:
- Guava的操作符通常作用于集合数据,RxJava通过Observable可以从任何地方获取数据。
- RxJava操作符更多,更灵活。
RxJava版的代码
@Test
public void test2_() {
List<String> list = Lists.newArrayList("ZHANG", "Chen", "LI", "wang", "ZHAO");
List<Integer> lengths = Observable.from(list).filter(new Func1<String, Boolean>() {
@Override
public Boolean call(String s) {
return CharMatcher.JAVA_UPPER_CASE.matchesAllOf(s);
}
}).map(new Func1<String, Integer>() {
@Override
public Integer call(String s) {
return s.length();
}
}).toList().toBlocking().single();
System.out.println(lengths);
}
RxJava所有封装函数的接口都叫FuncX,根据参数的不同有Func1、Func2...Func9。
注意点
Guava的函数式做的并不“智能”,对于上述问题,传统写法只要扫描集合一次,但Guava先filter后transform的方式需要扫描集合两次。 据说RxJava不存在这个问题,原理没有细研究过。可以猜猜,应该是先扫描了有哪些操作符,然后进行了优化。