就像魔术棒一样,中间操作将一个Stream转换为另一个Stream。 这些操作可以无穷无尽的方式组合在一起,以可读有效的方式执行从简单到高度复杂的任务。
本文是五分之二,其中还有一个GitHub存储库,其中包含每个单元的说明和练习。
- 第1部分:创建流
- 第2部分:中级操作
- 第三部分:终端操作
- 第4部分:数据库流
- 第5部分:使用流创建数据库应用程序
中级业务
中间操作充当应如何转换Stream元素的声明性(功能性)描述,它们共同构成了元素流经的管道。 该行结尾的内容自然取决于管道的设计方式。
与机械流水线相反,Stream流水线中的中间操作可能(*)呈现新的Stream,该流可能取决于先前阶段中的元素。 对于map操作(我们将在稍后介绍),新的Stream甚至可能包含其他类型的元素。
(*)严格来说,不要求执行中间操作来创建新的Stream。 相反,它可以更新其内部状态,或者,如果中间操作未进行任何更改(例如.skip(0)
),则返回上一级的现有Stream。
要大致了解管道的外观,请回想一下上一篇文章中使用的示例:
List<String> list = Stream.of( "Monkey" , "Lion" , "Giraffe" , "Lemur" )
.filter(s -> s.startsWith( "L" ))
.map(String::toUpperCase)
.sorted()
.collect(toList()); System.out.println(list);
[LEMUR, LION]
现在,我们将继续详细解释这些操作和其他操作的含义。
根据我们的经验, filter()
是Stream API最有用的操作之一。 它使您可以将Stream缩小为适合特定条件的元素。 此类标准必须表示为Predicate
(导致boolean
值的函数),例如lambda。 以下代码的目的是查找以字母“ L”开头的字符串,并丢弃其他字符串。
Stream<String> startsWithT = Stream.of(
"Monkey" , "Lion" , "Giraffe" , "Lemur" )
.filter(s -> s.startsWith( "L" ));
startsWithT: [Lion, Lemur]
有一些非常简单但功能强大的操作,提供了一种根据元素在Stream中的位置选择或丢弃元素的方法。 这些操作中的第一个是limit(n)
,它基本上按照其说的进行操作–它创建一个新流,该流仅包含应用该流的前n个元素。 下面的示例说明了如何将四只动物的流简化为仅“猴子”和“狮子”。
Stream<String> firstTwo = Stream.of(
"Monkey" , "Lion" , "Giraffe" , "Lemur" )
.limit( 2 );
firstTwo: [Monkey, Lion]
同样,如果只对线下的某些元素感兴趣,则可以使用.skip(n)
-operation。 如果将skip(2)
应用于动物流,则会留下尾巴两个元素“长颈鹿”和“狐猴”。
Stream<String> firstTwo = Stream.of(
"Monkey" , "Lion" , "Giraffe" , "Lemur" )
.skip( 2 );
lastTwo: [Giraffe, Lemur]
在某些情况下,流中每个元素只需要出现一次即可。 无需手动筛选出任何重复项,而是为此目的存在了一个指定的操作-distinct distinct()
。 它将使用Object::equals
检查是否相等,并返回仅包含唯一元素的新Stream。 这类似于集合。
Stream<String> uniqueAnimals = Stream.of(
"Monkey" , "Lion" , "Giraffe" , "Lemur" , "Lion" )
.distinct();
uniqueAnimals: [“Monkey”, “Lion”, “Giraffe”, “Lemur”]
有时,元素的顺序很重要,在这种情况下,我们希望控制事物的排序方式。 最简单的方法是使用排序操作,该操作将以自然顺序排列元素。 对于以下字符串,这表示字母顺序。
Stream<String> alphabeticOrder = Stream.of(
"Monkey" , "Lion" , "Giraffe" , "Lemur" )
.sorted();
alphabeticOrder: [Giraffe, Lemur, Lion, Monkey]
有时只能以自然顺序排序可能会有些局限。 幸运的是,可以应用自定义Comparator
来检查元素的某些属性。 例如,我们可以按照字符串的长度顺序对它们进行排序:
Stream<String> lengthOrder = Stream.of(
"Monkey" , "Lion" , "Giraffe" , "Lemur" )
.sorted(Comparator.comparing(String::length));
lengthOrder: [Lion, Lemur, Monkey, Giraffe]
我们可以应用于Stream的最通用的操作之一是map()
。 通过将Stream的元素映射到另一个值或类型,它可以将其转换为其他元素。 这意味着此操作的结果可以是任何类型R
的Stream。 下面的示例执行从String
到String
的简单映射,将所有大写字母替换为它们的小写字母。
Stream<String> lowerCase = Stream.of(
"Monkey" , "Lion" , "Giraffe" , "Lemur" )
.map(String::toLowerCase);
lowerCase: [monkey, lion, giraffe, lemur]
map-operation的三种特殊实现方式仅限于将元素映射到基本类型int
, double
和double
long
。
.mapToInt(); .mapToDouble(); .mapToLong();
因此,这些操作的结果始终对应于IntStream
, DoubleStream
或LongStream
。 下面,我们演示如何使用.mapToInt()
将动物映射到其名称的长度:
IntStream lengths = Stream.of(
"Monkey" , "Lion" , "Giraffe" , "Lemur" )
.mapToInt(String::length);
lengths: [ 6 , 4 , 7 , 5 ]
注意: String::length
等于lambda s -> s.length()
。 我们更喜欢前一种表示法,因为它使代码更简洁易读。
尽管本文功能非常强大,但我们将很难理解它的最后一项操作。 它是有关map()
操作,但是,而不是采取一个Function
,其从型变为T
到返回类型R
它需要一个Function
,其从型变为T
并返回一个Stream
的R
。 然后将这些“内部”流平展为生成的流,从而将内部流的所有元素串联在一起。
Stream<Character> chars = Stream.of(
"Monkey" , "Lion" , "Giraffe" , "Lemur" )
.flatMap(s -> s.chars().mapToObj(i -> ( char ) i));
chars: [M, o, n, k, e, y, L, i, o, n, G, i, r, a, f, f, e, L, e, m, u, r]
如果您尚未克隆关联的GitHub存储库,我们建议您现在进行克隆。 本文的内容足以解决名为MyUnit2Intermediate
的第二个单元。 相应的Unit2Intermediate
接口包含JavaDocs,它们描述MyUnit2MyIntermediate
方法的预期实现。
public interface Unit2Intermediate {
/**
* Return a Stream that contains words that are
* longer than three characters. Shorter words
* (ie words of length 0, 1, 2 and 3)
* shall be filtered away from the stream.
* <p>
* A Stream of
* ["The", "quick", "quick", "brown", "fox",
* "jumps", "over", "the", "lazy", "dog"]
* would produce a Stream of the elements
* ["quick", "quick", "brown", "jumps",
* "over", "lazy"]
*/
Stream<String> wordsLongerThanThreeChars(Stream<String> stream);
提供的测试(例如Unit2MyIntermediateTest
)将充当自动分级工具,让您知道您的解决方案是否正确。
下一篇
在下一篇文章中,我们将继续进行终端操作,并探索如何收集,计数或分组管道的结果元素。 在此之前–祝您编程愉快!
s
Per Minborg和Julia Gustafsson