【编者按】在以前文章中,咱们介绍了 Java 8和Scala的Lambda表达式对比。在本文,将进行 Hussachai Puripunpinyo Java 和 Scala 对比三部曲的第二部分,主要关注 Stream 和 Collection,本文由 OneAPM 工程师编译整理。html
首先,为你们作一个简短的介绍,collection 是有限的数据集,而 stream 是数据的序列集,能够是有限的或无限的。java
Streams API 是 Java 8 中新发布的 API,主要用于操做 collection 和 streaming 数据。Collections API 会改变数据集状态,而 Streams API 则不会。例如,当你调用Collections.sort(list)时,该方法会对传入的参数进行排序,而调用list.stream().sorted() 则会复制一份数据进行操做,保持原数据不变。你能够在这里得到更多关于 API 数据流的信息数据库
如下是笔者从 Java 8 文档中摘出的 collections 和 streams 之间的比较。强烈建议你们阅读 完整版。api
Streams 和 collections 有如下几点区别:缓存
2.本质是函数。对 Stream 对象操做能获得一个结果,可是不会修改原始数据。服务器
Laziness-seeking(延迟搜索):Stream 的不少操做如 filter、map、sort 和 duplicate removal(去重)能够延迟实现,意思是咱们只要检查到知足要求的元素就能够返回。数据结构
多是不受限制的:Streams 容许 Client 取足够多的元素直到知足某个条件为止。而 Collections 不能这么作。oracle
消耗的。Steam 中的元素在 steam 生存期内只能被访问一次。函数
Java 和 Scala 均可以很简单地同时计算 collection 中的值。在 Java 中,你只需调用parallelStream()* 或者 stream().parallel(),而不是stream()。在 Scala 中,在调用其余方法以前,必须先调用 par()函数。并且能够经过添加 parallelism 来提升程序的性能。不幸的是,大多数时间它的执行速度都很是慢。事实上,parallelism 是一个很容易被误用的功能。 点这阅读这有趣的文章性能
在 JavaDoc 中, parallelStream()方法的介绍是:可能返回一个并行的 stream(collection做为数据源),因此它也可能返回一个串行 stream。( 有人作过关于该API的研究)
图像标题
Java 的 Stream API 是延后执行的。这意味着,没有指定一个终结操做(好比 collect() 方法调用),那么全部的中间调用(好比 filter 调用)是不会被执行的。延迟的流处理主要是为了优化 stream API 的执行效率。好比对一个数据流进行过滤、映射以及求和运算,经过使用延后机制,那么全部操做只要遍历一次,从而减小中间调用。同时,延后执行容许每一个操做只处理必要的数据。相反,Scala 的 collections 是即时处理的。这样是否意味着,在测试中,Java Stream API始终优于 Scala ?若是只比较 Java 的 Stream API 和 Scala的 Collection API,那么Java Stream API 的确优于 Scala Collection API。但在 Scala 中有更多的选择。经过简单地调用toStream(),就能够将一个 Collection 转换成一个 Stream,或者可使用 view (一种提供延后处理能力的 Collection)来处理数据集合。
下面粗略介绍下 Scala 的 Stream 和 View 特性
Scala 的 Stream
Scala 的 Stream 和 Java 的有所不一样。在 Scala Stream 中,无需调用终结操做去取得Stream 的结果。Stream 是一个继承 Abstractseq、 Linearseq和 GenericTraversableTemplate trait的抽象类。因此,你能够把Stream看成 SEQ。
若是你不熟悉 Scala,能够将 Seq 看成 Java 里的 List。(Scala 中的 List 不是一个接口)。
这里需知道 Streams 中的元素都是延迟计算的,正由于此,Stream可以计算无限数据流。若是要计算集合中的全部元素,Stream 和 List 有相同的性能。一旦计算出结果,数值将被缓存。 Stream 有一个 force 函数,可以强制评估 stream 再返回结果。注意,不要在无限流中调用该函数,也不要强制该 API 处理整个 stream 的操做,好比 size()、tolist()、foreach() 等,这些操做在 Scala 的 Stream 中都是隐式的。
在 Scala Stream 中实现 Fibonacci 数列。
def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b) val fib1 = fibFrom(0, 1) //0 1 1 2 3 5 8 … val fib5 = fibFrom(0, 5) //0 5 5 10 15 … //fib1.force //Don’t do this cause it will call the function infinitely and soon you will get the OutOfMemoryError //fib1.size //Don’t do this too with the same reason as above. fib1.take(10) //Do this. It will take the first 10 from the inifite Stream. fib1.take(20).foreach(println(_)) //Prints 20 first numbers
::
是 collection 中经常使用的链接数据的方法。而 #::
表示是链接数据可是是延迟执行的(Scala中的方法名都很随意)。
Scala 的 View
再次重申,Scala 的 collection 是一个严格 collection,而 view 是非严格的。View 是基于一个基础 collection 的 collection,其中全部的转换都会延迟执行。经过调用 view 函数能够将严格 collection 转换成 view,也能够经过调用 force 方法转换回来。View 并不缓存结果,每次调用时才会执行转换。就像数据库的 View,但它是虚拟 collection。
建立一个数据集。
public class Pet { public static enum Type { CAT, DOG } public static enum Color { BLACK, WHITE, BROWN, GREEN } private String name; private Type type; private LocalDate birthdate; private Color color; private int weight; ... }
假设有一个宠物集,接下来会利用该集合详细说明。
过滤器
要求:从集合过滤一只胖乎乎的宠物,胖乎乎的定义是体重超过 50 磅,还想获得一个在 2013年1月1日出生的宠物名单。下面的代码片断显示了如何以不一样的方式实现该滤波器的工做。
Java 方法1:传统方式
//Before Java 8 List<Pet> tmpList = new ArrayList<>(); for(Pet pet: pets){ if(pet.getBirthdate().isBefore(LocalDate.of(2013, Month.JANUARY, 1)) && pet.getWeight() > 50){ tmpList.add(pet); } }
这种方式在命令式语言中十分常见。首先,必须建立一个临时集合,而后遍历全部元素,存储知足条件的元素到临时集中。的确有点绕口,但其结果和效率都很是不错。但本人不得不扫兴地说,传统方法比 Streams API 更快。不过,彻底不用担忧性能问题,由于代码的简洁比轻微的性能增益更重要。
Java 方法2:Streams API
//Java 8 - Stream pets.stream() .filter(pet -> pet.getBirthdate().isBefore(LocalDate.of(2013, Month.JANUARY, 1))) .filter(pet -> pet.getWeight() > 50) .collect(toList())
以上代码表示,使用 Streams API 过滤集合中的元素。之因此故意两次调用过滤函数,是想代表 Streams 的 API 设计就像一个 Builder pattern。在 Builder pattern 调用构建方法以前,能够将各类方法串联起来。在 Streams API 中,构建方法被称为终结操做,非终结操做的叫作中间操做。终结操做可能不一样于构造函数,由于它在 Streams API 中只能被调用一次。但还有不少可以使用的终结操做,好比 collect、count、min、max、iterator、toArray。这些操做会产生结果,而终端操做会消耗值,例如 forEach。那么,你认为传统方法和 Streams API 哪个的可读性更强?
Java 方法3:Collections API
//Java 8 - Collection pets.removeIf(pet -> !(pet.getBirthdate().isBefore(LocalDate.of(2013,Month.JANUARY, 1)) && pet.getWeight() > 50)); //Applying De-Morgan's law. pets.removeIf(pet -> pets.get(0).getBirthdate().toEpochDay() >= LocalDate.of(2013, Month.JANUARY, 1).toEpochDay() || pet.getWeight() <= 50);
这种方法是最简短的。可是,它修改了原始集合,而前面的方法不会。removeif 函数将Predicate<T>(函数接口)做为参数。Predicate 是一个行为参数,它只有一个名为 test 抽象方法,只须要一个对象并返回布尔值。注意,这里必须使用“!”取反,或者能够应用 De Morgan 定理,使得代码看起来像二次声明。
Scala 方法:Collection、View和Stream
//Scala - strict collection pets.filter { pet => pet.getBirthdate.isBefore(LocalDate.of(2013, Month.JANUARY, 1))} .filter { pet => pet.getWeight > 50 } //List[Pet] //Scala - non-strict collection pets.views.filter { pet => pet.getBirthdate.isBefore(LocalDate.of(2013, Month.JANUARY, 1))} .filter { pet => pet.getWeight > 50 } //SeqView[Pet] //Scala - stream pets.toStream.filter { pet => pet.getBirthdate.isBefore(LocalDate.of(2013, Month.JANUARY, 1))} .filter { pet => pet.getWeight > 50 } //Stream[Pet]
Scala 的解决方案相似于 Java 的 Streams API。但首先,必须调用 view 函数把严格集转向非严格集,而后再用 tostream 函数把严格集转成一个 stream。
接下来直接上代码。
分组
经过元素的一个属性对起所在集合作 group。结果是 Map<T, List<T>>,其中T是一个泛型类型。
要求:经过类型对宠物分组,诸如狗,猫等等。
注意:groupingBy 是 java.util.stream.Collectors 的静态的 helper method。
排序
根据属性对集合中的元素排序。结果会是任何类型的集合,根据配置来维持元素顺序。
要求:需按照类型、名字和颜色排序。
映射
将给定函数应用在集合元素中。根据定义的函数不一样,其返回的结果类型也不一样。
要求:需将宠物转化成字符串,以%s — name: %s, color: %s
的格式。
寻找第一个
返回第一个能与指定 predicate 匹配的值。
要求:找一个名为Handsome
的宠物。不管有多少个Handsome
,只取第一个。
这个问题有点棘手。不知道你是否注意,在 Scala 中笔者所使用的是 find 函数而不是 filter ?若是用 filter 代替 find,它就会计算集合中全部元素,由于 scala collection 是严格的。可是,在 Java 的 Streams API 中你能够放心使用 filter,由于它会计算须要的第一个值,并不会计算全部元素。这就是延迟执行的好处!
接下来,向你们介绍 scala 中更多集合延迟执行的实例。咱们假定 filter 老是返回 true,而后再取第二个值。将会是什么结果呢?
pets.filter { x => println(x.getName); true }.get(1) --- (1)
pets.toStream.filter { x => println(x.getName); true }.get(1) -- (2)
如上所示,(1)式将会打印出集合中全部宠物的名字,而(2)式则只输出前2个宠物的名字。这就是 lazy collection 的好处,老是延迟计算。
pets.view.filter { x => println(x.getName); true }.get(1) --- (3)
(3)式和(2)式会有同样的结果吗?错!它的结果和(1)是同样的,你知道为何吗?
经过比较 Java 和 Scala 中的一些共同的操做方法 ——filter、group、map 和 find;很明显 Scala 的方法比 Java 更简洁。你更喜欢哪个呢?哪个的可读性更强?
在文章的下一个部分,咱们将比较哪一种方式更快。敬请期待!
原文连接: https://dzone.com/articles/java-8-vs-scalapart-ii-streams-api
OneAPM for Java 可以深刻到全部 Java 应用内部完成应用性能管理和监控,包括代码级别性能问题的可见性、性能瓶颈的快速识别与追溯、真实用户体验监控、服务器监控和端到端的应用性能管理。想阅读更多技术文章,请访问 OneAPM 官方博客。