数据处理函数编程之Guava
最近在开发宝贝化项目过程中,遇到一个问题:同一个车型对应多种价格,需要做过滤处理,保留统一车型最低的价格;看似很简单的问题,但如何优雅的编程,让代码更加简洁,性能更好。对于有代码洁癖的人来说,想到了guava强大的函数处理功能以及Jdk8 stream语法;项目结束之后,想把这个guava函数编程应用总结分享一下
- guava的应用场景
- guava排序功能
- guava过滤功能
- jdk8的lambda表达式
- jdk8的Stream语法
guava的应用场景
前后端分离技术体系redux的应用场景有过这么一句话:当你不知道什么时候用到redux的时候,那就是不需要它。其实在java这种面向对象的编程思想,很少会考虑到函数式编程,主要在于函数式编程不符合面向对象的思想,难以理解,甚至有时候代码阅读非常困难,对于代码的维护那是一件灾难性的事件。所以一般场景不太建议大家用函数式编程。但有些场景比如复杂的数据处理,算法运算等等,这些可以考虑用到guava的函数编程思想。
排序功能
排序器Ordering是guava流畅风格排序器Comparator的模板实现,用它可以构建复杂的集合排序功能
《一》常见的排序器
方法 | 功能 |
---|---|
natural() | 对排序类型自然排序:数字大小、日期先后顺序等 |
usingToString() | 对字符串进行字典排序 |
arbitrary() | 无序,每次排序的结果都不相同 |
实现自定义排序器,通常可以使用Ordering.from(Comparator)的方式,或者实现Ordering模板构造
Comparator<String> vr = (s1, s2) -> BigDecimal.valueOf(Double.valueOf(s1)).compareTo(BigDecimal.valueOf(Double.valueOf(s2)));
TreeMultimap<Long, String> keyMap = TreeMultimap.create(Ordering.natural(), Ordering.from(vr));
示例:
List<String> tlist = Lists.newArrayList(ImmutableList.of("welcome","to","guava","java","lambda"));
System.out.println("排序前:" + JSON.toJSONString(tlist));
//排序前:["welcome","to","guava","java","lambda"]
tlist = Ordering.usingToString().sortedCopy(tlist);
System.out.println("排序后" + JSON.toJSONString(tlist));
//排序后["guava","java","lambda","to","welcome"]
《二》链式调用方法:通过链式调用,可以由给定的排序器衍生出其它排序器
方法 | 功能 |
---|---|
reverse() | 获取语义相反的排序器 |
nullsFirst() | 把null值排在最前面 |
nullsLast() | 把null值排在最后面 |
compound(Comparator) | 合成另一个比较器,以处理当前排序器中的相等情况 |
onResultOf(Function) | 对集合中元素调用Function,再按返回值用当前排序器排序 |
示例:
对PriceInfo这个类,对车型carType自然排序,null值排在前面(纯属为了说明用法)
@Data
public class PriceInfo {
/**
* 车型
*/
private Long carType;
/**
* 价格
*/
private String price;
}
实现方式:
Function<PriceInfo, Long> sortCarTypeFunction = new Function<PriceInfo, Long>() {
@Override
public Long apply(PriceInfo priceInfo) {
return priceInfo.getCarType();
}
};
Ordering<PriceInfo> carTypeOrdering = Ordering.natural().reverse().nullsFirst().onResultOf(sortCarTypeFunction);
过滤功能
我们可以使用com.google.common.collect.Iterables和com.google.common.base.Predicates类来过滤例子中的列表
List<String> filterList = Lists.newArrayList("welcome", "to", "guava", "java", "lambda");
Predicate<String> lessThenPredicate = new Predicate<String>() {
@Override
public boolean apply(String s) {
return s.length() > 5;
}
};
Iterable<String> filterResult = Iterables.filter(filterList, Predicates.or((Predicates.or(Predicates.equalTo("guava"), Predicates.equalTo("java"))), lessThenPredicate));
Preconditions.checkArgument(Lists.newArrayList(filterResult).containsAll(Lists.newArrayList("guava", "java", "to")), "集合包含:[guava,welcome,lambda,java]");
Jdk8的Stream语法以及Lambda表达式应用
API地址:https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
jdk8引入stream语法主要是为了弥补函数式编程的缺陷,实际上也是借鉴了guava的函数编程风格;stream和I/O不同,它更像具有Iterable的集合类,但行为和集合类又有所不同。整体来讲Stream API包含构建、中间操作、终端操作;
《一》Stream构建
- 使用Stream静态方法来创建Stream:主要有of、generator、iterate方法
- 通过Collection子类获取Stream
《二》Stream的中间操作
- filter: 对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素;
- map: 对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble。这三个方法也比较好理解,比如mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗;
- limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;
《三》Stream的终端操作
- collect:可变汇聚方法:
List<PriceInfo> result = list.stream().filter(priceInfo -> {
Long carType = priceInfo.getCarType();
if (null != keyMap.get(carType) && keyMap.get(carType).size() > 1) {
return keyMap.get(carType).first().equals(priceInfo.getPrice());
}
return true;
}).collect(Collectors.toList());
- reduce操作:比如count、sum等函数
附:对同一个车型进行过滤,保留最低价格,代码示例:
PriceInfo p1 = new PriceInfo();
p1.setCarType(2L);
p1.setPrice("23");
PriceInfo p2 = new PriceInfo();
p2.setCarType(3L);
p2.setPrice("20");
PriceInfo p3 = new PriceInfo();
p3.setCarType(2L);
p3.setPrice("19");
PriceInfo p4 = new PriceInfo();
p4.setCarType(2L);
p4.setPrice("30");
List<PriceInfo> list = Lists.newArrayList(ImmutableList.of(p1, p2, p3, p4));
Comparator<String> vr = (s1, s2) -> BigDecimal.valueOf(Double.valueOf(s1)).compareTo(BigDecimal.valueOf(Double.valueOf(s2)));
TreeMultimap<Long, String> keyMap = TreeMultimap.create(Ordering.natural(), Ordering.from(vr));
list.stream().forEach(priceInfo -> {
keyMap.put(priceInfo.getCarType(), priceInfo.getPrice());
});
List<PriceInfo> result = list.stream().filter(priceInfo -> {
Long carType = priceInfo.getCarType();
if (null != keyMap.get(carType) && keyMap.get(carType).size() > 1) {
return keyMap.get(carType).first().equals(priceInfo.getPrice());
}
return true;
}).collect(Collectors.toList());
更多Stream功能了解:https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/