最近项目里有这么一段代码,我在做 code-review 的时候,觉得可以使用 Java8 StreamAPI 简化一下。
这里先看一下代码(不是源码,一些敏感信息被我用其他类替代了):
private static Set<String> doSomething1(String input) { Set<String> target = new HashSet<>(); if (input != null) { for (Pojo pojo : source) { if (input.equals(pojo.getStr1())) { target.add(pojo.getStr2()); } } } return target; }
其中 source 是一个 Set<Pojo> 的共享变量,Pojo 是自定义对象:
private static class Pojo { String str1; String str2; Pojo(String str1, String str2) { this.str1 = str1; this.str2 = str2; } public String getStr1() { return str1; } public String getStr2() { return str2; } }
这个 doSomething1() 方法的功能很简单:
- 声明一个新的 Set<String> 结构 target。
- 遍历 source。
- 将 input 依次与 source 中每个元素的 str1 属性进行比对。
- 若比对成功,将元素的 str2 属性加入 target。
- 遍历结束,返回 target。
从单纯的 Coding 角度而言,doSomething1() 方法实现得已经足够简便,但是使用 Java8 StreamAPI 可以仅仅使用一行代码的情况下完成这些操作。
仔细分析以上的操作,可以归纳为三个步骤:
- 过滤数据 -> input.equals(pojo.getStr1()
- 提取数据 -> pojo.getStr2()
- 收集数据 -> target.add(pojo.getStr2())
这三个操作分别对应了 StreamAPI 的 filter,map,collect,因此可以简化成这样:
private static Set<String> doSomething2(String input) { return source.stream().filter(pojo -> input.equals(pojo.getStr1())).map(Pojo::getStr2).collect(Collectors.toSet()); }
功能测试
private static Set<Pojo> source = new HashSet<>(); public static void main(String[] args) { prepareData(); for (String s : doSomething1("1")) { System.out.println(s); } for (String s : doSomething2("1")) { System.out.println(s); } } private static void prepareData() { Pojo pojo1 = new Pojo("1", "2"); Pojo pojo2 = new Pojo("3", "4"); source.add(pojo1); source.add(pojo2); }
输出如下:
发现两者是等价的。
性能测试
一个应用程序的优秀与否,不在于代码的漂亮程度,而在于性能。
public static void main(String[] args) { prepareData(); long t3 = System.nanoTime(); doSomething2("123441"); long t4 = System.nanoTime(); printTime(t3, t4); long t1 = System.nanoTime(); doSomething1("123441"); long t2 = System.nanoTime(); printTime(t1, t2); } private static void printTime(long t1, long t2) { long t = t2 - t1; double factor = Math.pow(10, 9); System.out.println(t / factor); } private static void prepareData() { final int scale = 100000000; Random r = new Random(); for (int i = 0; i < scale; ++i) { String str1 = r.nextInt(scale) + ""; String str2 = r.nextInt(scale) + ""; source.add(new Pojo(str1, str2)); } }
测试结果:
可以发现,使用 StreamAPI 大大降低了程序的效率,当数据量足够大的时候,这个比例会不断缩小。
我们可以这样理解:StreamAPI 针对的是相对大数据的操作,为一个数据量较小的 Collection 起一个 Stream 非常得不偿失,有种大炮打蚊子的感觉。
最后的内存溢出,是在 prepareData 的时候,和处理数据没有关系。
最后贴上大神的测试:Java8 Lambda表达式和流操作如何让你的代码变慢5倍
所以流操作,慎用!