本文为社区首发签约文章,14天内阻止转载,14天后未获授权阻止转载,侵权必究!
我们在前面几篇关于 Java 集结结构中 List、Set、Map 这些容器的文章中,已经给我们演示过一些 Stream 操作了,这篇文章给我们详细整理一下。
前文回忆
Java 做项目能用到 List 的场景,这篇总结全了
有 List 了为什么还要有 Set?Java 容器 Set 的中心通关攻略
Java 做项目能用到 Map 哪些功用?这篇总结全了
因为 Stream 供应的操作过多,本节内容是 Stream API 中常用操作的学习和了解,下面会专门再有一篇文章介绍在项目开发中那些高频运用的,利用 Stream 处理方针集结的运用示例。
本文大纲如下:
Java 的 Stream API 供应了一种处理方针集结的函数式方法。 Stream 是和 Lambda 表达式等其他几个函数式编程特性一起在 Java 8 被引进的。这个篇教程将解释 Stream API 供应的这些函数式方法是如何作业的,以及怎样运用它们。
留心,Java 的 Stream API 与 Java IO 的 InputStream 和 OutputStream 没有任何关系,不要因为名字相似构成误解。 InputStream 和 OutputStream 是与字节约有关,而 Java 的 Stream API 用于处理方针流。
Stream 的界说
Java 的 Stream 是一个可以对其元素进行内部迭代的组件,这意味着它可以自己迭代其元素。相反地,当我们运用 Collection 的迭代功用,例如,从 Collection 获取Iterator 或许运用 Iterable 接口 的 forEach 方法这些方法进行迭代时,我们有必要自己结束集结元素的迭代逻辑。
当然集结也支撑获取 Stream 结束迭代,这些我们在介绍集结结构的相关章节都介绍过。
流处理
我们可以将 Listener 方法或许叫处理器方法附加到 Stream 上。当 Stream 在内部迭代元素时,将以元素为参数调用这些处理器。Stream 会为流中的每个元素调用一次处理器。所以每个处理器方法都可以处理 Stream 中的每个元素,我们把这称为流处理。
流的多个处理器方法可以构成一个调用链。链上的前一个处理器处理流中的元素,回来的新元素会作为参数传给链中的下一个处理器处理。当然,处理器可以回来相同的元素或新元素,详细取决于处理器的目的和用途。
怎样获取流
有许多方法获取 Stream ,一般最常见的是从 Collection 方针中获取 Stream。下面是一个从 List 方针获取 Stream 的比如。
List<String> items = new ArrayList<String>();
items.add("one");
items.add("two");
items.add("three");
Stream<String> stream = items.stream();
集结方针都结束了 Collection 接口,所以通过接口里界说的 stream 方法获救获取到由集结元素构成的 Steam。
流处理的构成
在对流进行处理时,不同的流操作以级联的方法构成处理链。一个流的处理链由一个源(source),0 到多个中心操作(intermediate operation)和一个结束操作(terminal operation)结束。
- 源:源代表 Stream 中元素的来历,比如我们上面看到的集结方针。
- 中心操作:中心操作,在一个流上添加的处理器方法,他们的回来作用是一个新的流。这些操作是推迟实行的,在结束操作发起后才会初步实行。
- 结束操作:结束流操作是发起元素内部迭代、调用全部处理器方法并毕竟回来作用的操作。
概念听起来有点模糊,我们通过流处理的比如再了解一下。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamExamples {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("ONE");
stringList.add("TWO");
stringList.add("THREE");
Stream<String> stream = stringList.stream();
long count = stream
.map((value) -> value.toLowerCase())
.count();
System.out.println("count = " + count);
}
}
map() 方法的调用是一个中心操作。它只是在流上设置一个 Lambda 表达式,将每个元素转化为小写方法。而对 count() 方法的调用是一个结束操作。此调用会在内部发起迭代,初步流处理,这将导致每个元素都转化为小写然后计数。
将元素转化为小写实际上并不影响元素的计数。转化部分只是作为 map() 是一个中心操作的示例。
流的中心操作
Stream API 的中心(非结束)流操作是转化或许过滤流中元素的操作。当我们把中心操作添加到流上时,我们会得到一个新的流作为作用。下面是一个添加到流上的中心操作的示例,它的实行作用会产生一个新的流。
List<String> stringList = new ArrayList<>();
stringList.add("ONE");
stringList.add("TWO");
stringList.add("THREE");
Stream<String> stream = stringList.stream();
Stream<String> stringStream =
stream.map((value) -> value.toLowerCase());
上面比如中,流上添加的 map() 调用,此调用实际上回来一个新的 Stream 实例,该实例标明原始字符串流使用了 map 操作后的新流。 只能将单个操作添加到给定的 Stream 实例上。假设需求将多个操作链接在一起,则只能将第二个操作使用于第一个操作产生的 Stream 实例上。
Stream<String> stringStream1 =
stream.map((value) -> value.toLowerCase());
Stream<String> stringStream2 =
stringStream1.map((value) -> value.toUpperCase());
留心第二个 map() 调用是如安在第一个 map() 调用回来的 Stream 上进行调用的。
我们一般是将 Stream 上的全部中心操作串联成一个调用链:
Stream<String> stream1 = stream
.map((value) -> value.toLowerCase())
.map((value) -> value.toUpperCase())
.map((value) -> value.substring(0,3));
以 map方法为代表流间操作方法的参数,是一个函数式接口,我们可以直接用 Lambda 表达式作为这些操作的参数。所以在介绍 Lambda 的那一节我们也说过,Lambda 一般是和流操作就结合起来用的。
**参阅–Java 的函数式接口: **tutorials.jenkov.com/java-functi…
下面我们说一下常用的流的中心操作。
map
map() 方法将一个元素转化(或许叫映射)到另一个方针。例如,一个字符串列表,map() 可以将每个字符串转化为小写、大写或原始字符串的子字符串,或彻底不同的东西。
List<String> list = new ArrayList<String>();
Stream<String> stream = list.stream();
Stream<String> streamMapped = stream.map((value) -> value.toUpperCase());
filter
filter() 用于从 Stream 中过滤掉元素。 filter 方法接受一个 Predicate (也是一个函数式接口),filter() 为流中的每个元素调用 Predicate。假设元素要包含在 filter() 回来作用的流中,则 Predicate 应回来 true。假设不应包含该元素,则 Predicate 应回来 false。
Stream<String> longStringsStream = stream.filter((value) -> {
// 元素长度大于等于3,回来true,会被保留在 filter 产生的新流中。
return value.length() >= 3;
});
比如 Stream 实例使用了上面这个 filter 后,filter 回来的作用流里只会包含长度不小于 3 的元素。
flatMap
flatMap方法接受一个 Lambda 表达式, Lambda 的回来值有必要也是一个stream类型,flatMap方法毕竟会把全部回来的stream合并。map 与 flatMap 方法很像,都是以某种方法转化流中的元素。假设需求将每个元素转化为一个值,则运用 map 方法,假设需求将每个元素转化为多个值组成的流,且毕竟把全部元素的流合并成一个流,则需求运用 flatMap 方法。 在作用上看是把本来流中的每个元素进行了“展平”
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamFlatMapExamples {
public static void main(String[] args) {
List<String> stringList = new ArrayList<String>();
stringList.add("One flew over the cuckoo's nest");
stringList.add("To kill a muckingbird");
stringList.add("Gone with the wind");
Stream<String> stream = stringList.stream();
stream.flatMap((value) -> {
String[] split = value.split(" ");
return Arrays.asList(split).stream();
}).forEach((value) -> System.out.println(value));
}
}
在上面的比如中,每个字符串元素被拆分成单词,变成一个 List,然后从这个 List 中获取并回来流,flatMap 方法毕竟会把这些流合并成一个,所以最终用流结束操作 forEach 方法,遍历并输出了每个单词。
One
flew
over
the
cuckoo's
nest
To
kill
a
muckingbird
Gone
with
the
wind
distinct
distinct() 会回来一个仅包含原始流中不同元素的新 Stream 实例,任何重复的元素都将会被去掉。
List<String> stringList = new ArrayList<String>();
stringList.add("one");
stringList.add("two");
stringList.add("three");
stringList.add("one");
Stream<String> stream = stringList.stream();
List<String> distinctStrings = stream
.distinct()
.collect(Collectors.toList());
System.out.println(distinctStrings);
在这个比如中,元素 “one” 在一初步的流中出现了两次,原始流使用 distinct 操作生成的新流中将会丢掉掉重复的元素,只保留一个 “one” 元素。所以这个比如最终的输出是:
[one, two, three]
limit
limit 操作会截断原始流,回来最多只包含给定数量个元素的新流。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamLimitExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<String>();
stringList.add("one");
stringList.add("two");
stringList.add("three");
stringList.add("one");
Stream<String> stream = stringList.stream();
stream.limit(2)
.forEach( element -> System.out.println(element));
}
}
这个比如中,因为对原始流运用了 limit(2) 操作,所以只会回来包含两个元素的新流,随后运用 forEach 操作将它们打印了出来。程序毕竟将会输出:
one
two
peek
peek() 方法是一个以 Consumer (java.util.function.Consumer,Consumer 代表的是消费元素但不回来任何值的方法) 作为参数的中心操作,它回来的流与原始流相同。当原始流中的元素初步迭代时,会调用 peek 方法中指定的 Consumer 结束对元素进行处理。
正如 peek 操作称号的意义相同,peek() 方法的目的是检查流中的元素,而不是转化它们。跟其他中心操作的方法相同,peek() 方法不会发起流中元素的内部迭代,流需求一个结束操作才干初步内部元素的迭代。
peek() 方法在流处理的 DEBUG 上的使用甚广,比如我们可以利用 peek() 方法输出流的中心值,便当我们的调试。
Stream.of("one", "two", "three","four").filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
上面的比如会输出以下调试信息。
Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR
流的结束操作
Stream 的结束操作通常会回来单个值,一旦一个 Stream 实例上的结束操作被调用,流内部元素的迭代以及流处理调用链上的中心操作就会初步实行,当迭代结束后,结束操作的回来值将作为整个流处理的回来值被回来。
long count = stream
.map((value) -> value.toLowerCase())
.map((value) -> value.toUpperCase())
.map((value) -> value.substring(0,3))
.count();
Stream 的结束操作 count() 被调用后整个流处理初步实行,最终将 count() 的回来值作为作用回来,结束流操作的实行。这也是为什么把他们命名成流的结束操作的原因。
上面比如,使用的中心操作 map 对流处理的作用并没有影响,这儿只是做一下演示。
下面我们把常用的流结束操作说一下。
anyMatch
anyMatch() 方法以一个 Predicate (java.util.function.Predicate 接口,它代表一个接收单个参数并回来参数是否匹配的函数)作为参数,发起 Stream 的内部迭代,并将 Predicate 参数使用于每个元素。假设 Predicate 对任何元素回来了 true(标明满意匹配),则 anyMatch() 方法的作用回来 true。假设没有元素匹配 Predicate,anyMatch() 将回来 false。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamAnyMatchExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<String>();
stringList.add("One flew over the cuckoo's nest");
stringList.add("To kill a muckingbird");
stringList.add("Gone with the wind");
Stream<String> stream = stringList.stream();
boolean anyMatch = stream.anyMatch((value) -> value.startsWith("One"));
System.out.println(anyMatch);
}
}
上面例程的运转作用是 true , 因为流中第一个元素便是以 “One” 开始的,满意 anyMatch 设置的条件。
allMatch
allMatch() 方法同样以一个 Predicate 作为参数,发起 Stream 中元素的内部迭代,并将 Predicate 参数使用于每个元素。假设 Predicate 为 Stream 中的全部元素都回来 true,则 allMatch() 的回来作用为 true。假设不是全部元素都与 Predicate 匹配,则 allMatch() 方法回来 false。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamAllMatchExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<String>();
stringList.add("One flew over the cuckoo's nest");
stringList.add("To kill a muckingbird");
stringList.add("Gone with the wind");
Stream<String> stream = stringList.stream();
boolean allMatch = stream.allMatch((value) -> value.startsWith("One"));
System.out.println(allMatch);
}
}
上面的例程我们把流上用的 anyMatch 换成了 allMatch ,作用可想而知会回来 false,因为并不是全部元素都是以 “One” 开始的。
noneMatch
Match 系列里还有一个 noneMatch 方法,顾名思义,假设流中的全部元素都与作为 noneMatch 方法参数的 Predicate 不匹配,则方法会回来 true,不然回来 false。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamNoneExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("abc");
stringList.add("def");
Stream<String> stream = stringList.stream();
boolean noneMatch = stream.noneMatch((element) -> {
return "xyz".equals(element);
});
System.out.println("noneMatch = " + noneMatch); //输出 noneMatch = true
}
}
collect
collect() 方法被调用后,会发起元素的内部迭代,并将流中的元素收集到集结或方针中。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamCollectExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("One flew over the cuckoo's nest");
stringList.add("To kill a muckingbird");
stringList.add("Gone with the wind");
Stream<String> stream = stringList.stream();
List<String> stringsAsUppercaseList = stream
.map(value -> value.toUpperCase())
.collect(Collectors.toList());
System.out.println(stringsAsUppercaseList);
}
}
collect() 方法将收集器 — Collector (java.util.stream.Collector) 作为参数。在上面的示例中,运用的是 Collectors.toList() 回来的 Collector 结束。这个收集器把流中的全部元素收集到一个 List 中去。
count
count() 方法调用后,会发起 Stream 中元素的迭代,并对元素进行计数。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamExamples {
public static void main(String[] args) {
List<String> stringList = new ArrayList<String>();
stringList.add("One flew over the cuckoo's nest");
stringList.add("To kill a muckingbird");
stringList.add("Gone with the wind");
Stream<String> stream = stringList.stream();
long count = stream.flatMap((value) -> {
String[] split = value.split(" ");
return Arrays.asList(split).stream();
}).count();
System.out.println("count = " + count); // count = 14
}
}
上面的例程中,首先创建一个字符串 List ,然后获取该 List 的 Stream,为其添加了 flatMap() 和 count() 操作。 count() 方法调用后,流处理将初步迭代 Stream 中的元素,处理过程中字符串元素在 flatMap() 操作中被拆分为单词、合并成一个由单词组成的 Stream,然后在 count() 中进行计数。所以毕竟打印出的作用是 count = 14。
findAny
findAny() 方法可以从 Stream 中找到单个元素。找到的元素可以来自 Stream 中的任何方位。且它不供应从流中的哪个方位获取元素的确保。
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class StreamFindAnyExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("one");
stringList.add("two");
stringList.add("three");
stringList.add("one");
Stream<String> stream = stringList.stream();
Optional<String> anyElement = stream.findAny();
if (anyElement.isPresent()) {
System.out.println(anyElement.get());
} else {
System.out.println("not found");
}
}
}
findAny() 方法会回来一个 Optional,意味着 Stream 可能为空,因而没有回来任何元素。我们可以通过 Optional 的 isPresent() 方法检查是否找到了元素。
Optional 类是一个可以为 null 的容器方针。假设值存在则 isPresent() 方法会回来true,调用get()方法会回来容器中的方针,不然抛出失常:NoSuchElementException
findFirst
findFirst() 方法将查找 Stream 中的第一个元素,跟 findAny() 方法相同,也是回来一个 Optional,我们可以通过 Optional 的 isPresent() 方法检查是否找到了元素。
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class StreamFindFirstExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("one");
stringList.add("two");
stringList.add("three");
stringList.add("one");
Stream<String> stream = stringList.stream();
Optional<String> anyElement = stream.findFirst();
if (anyElement.isPresent()) {
System.out.println(anyElement.get());
} else {
System.out.println("not found");
}
}
}
forEach
forEach() 方法我们在介绍 Collection 的迭代时介绍过,其时首要是拿它来迭代 List 的元素。它会发起 Stream 中元素的内部迭代,并将 Consumer (java.util.function.Consumer, 一个函数式接口,上面介绍过) 使用于 Stream 中的每个元素。 留心 forEach() 方法的回来值是 void。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamExamples {
public static void main(String[] args) {
List<String> stringList = new ArrayList<String>();
stringList.add("one");
stringList.add("two");
stringList.add("three");
Stream<String> stream = stringList.stream();
stream.forEach(System.out::println);
}
}
留心,上面例程中 forEach 的参数我们直接用了Lambda 表达式引证方法的简写方法。
min
min() 方法回来 Stream 中的最小元素。哪个元素最小是由传递给 min() 方法的 Comparator 接口结束来确定的。
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class StreamMinExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("abc");
stringList.add("def");
Stream<String> stream = stringList.stream();
// 作为 min 方法参数的Lambda 表达式可以简写成 String::compareTo
// Optional<String> min = stream.min(String::compareTo);
Optional<String> min = stream.min((val1, val2) -> {
return val1.compareTo(val2);
});
String minString = min.get();
System.out.println(minString); // abc
}
}
min() 方法回来的是一个 Optional ,也便是它可能不包含作用。假设为空,直接调用 Optional 的 get() 方法将抛出 失常–NoSuchElementException。比如我们把上面的 List 添加元素的两行代码注释掉后,运转程序就会报
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.util.Optional.get(Optional.java:135)
at com.example.StreamMinExample.main(StreamMinExample.java:21)
所以最好先用 Optional 的 ifPresent() 判断一下是否包含作用,再调用 get() 获取作用。
max
与 min() 方法相对应,max() 方法会回来 Stream 中的最大元素,max() 方法的参数和回来值跟 min() 方法的也都相同,这儿就不再过多论述了,只需求把上面求最小值的方法替换成求最大值的方法 max() 即可。
Optional<String> min = stream.max(String::compareTo);
reduce
reduce() 方法,是 Stream 的一个聚合方法,它可以把一个 Stream 的全部元素按照聚合函数聚合成一个作用。reduce()方法接收一个函数式接口 BinaryOperator 的结束,它界说的一个apply()方法,担任把前次累加的作用和本次的元素进行运算,并回来累加的作用。
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class StreamReduceExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("One flew over the cuckoo's nest");
stringList.add("To kill a muckingbird");
stringList.add("Gone with the wind");
Stream<String> stream = stringList.stream();
Optional<String> reduced = stream.reduce((value, combinedValue) -> combinedValue + " + " + value);
// 写程序的时分记得别忘了 reduced.ifPresent() 检查作用里是否有值
System.out.println(reduced.get());
}
}
reduce() 方法的回来值同样是一个 Optional 类的方针,所以在获取值前别忘了运用 ifPresent() 进行检查。
streadm 结束了多个版别的reduce() 方法,还有可以直接回来元素类型的版别,比如运用 reduce 结束整型Stream的元素的求和
import java.util.ArrayList;
import java.util.List;
public class IntegerStreamReduceSum {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
intList.add(10);
intList.add(9);
intList.add(8);
intList.add(7);
Integer sum = intList.stream().reduce(0, Integer::sum);
System.out.printf("List 求和,总和为%s\n", sum);
}
}
toArray
toArray() 方法是一个流的结束操作,它会发起流中元素的内部迭代,并回来一个包含全部元素的 Object 数组。
List<String> stringList = new ArrayList<String>();
stringList.add("One flew over the cuckoo's nest");
stringList.add("To kill a muckingbird");
stringList.add("Gone with the wind");
Stream<String> stream = stringList.stream();
Object[] objects = stream.toArray();
不过 toArray 还有一个重载方法,允许传入指定类型数组的结构方法,比如我们用 toArray 把流中的元素收集到字符串数组中,可以这么写:
String[] strArray = stream.toArray(String[]::new);
流的拼接
Java 的Stream 接口包含一个名为 concat() 的静态方法,它可以将两个流连接成一个。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamConcatExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("One flew over the cuckoo's nest");
stringList.add("To kill a muckingbird");
stringList.add("Gone with the wind");
Stream<String> stream1 = stringList.stream();
List<String> stringList2 = new ArrayList<>();
stringList2.add("Lord of the Rings");
stringList2.add("Planet of the Rats");
stringList2.add("Phantom Menace");
Stream<String> stream2 = stringList2.stream();
Stream<String> concatStream = Stream.concat(stream1, stream2);
List<String> stringsAsUppercaseList = concatStream
.collect(Collectors.toList());
System.out.println(stringsAsUppercaseList);
}
}
从数组创建流
上面关于 Stream 的比如我们都是从 Collection 实例的 stream() 方法获取的集结包含的全部元素的流,除了这种方法之外,Java 的 Stream 接口中供应了一个名为 of 的静态方法,能支撑从单个,多个方针或许数组方针快速创建流。
import java.util.stream.Stream;
public class StreamExamples {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("one", "two", "three");
Stream<String> stream2 = Stream.of(new String[]{"one", "two"});
System.out.println(stream1.count()); // 输出3
System.out.println(stream2.count()); // 输出2
}
}
总结
上面我们把 Stream 的两大类操作:流的中心操作、流的结束操作都有哪些方法给我们列举了一遍,让我们对 Stream 能结束的操作有了大致的形象。不过为了说明这些操作用的都是非常简略的比如,流操作的数据也都是简略类型的,首要的目的是让我们能更快速地了解 Stream 的各种操作使用在数据上后,都有什么作用。
下一篇我会演示一些在项目开发中我们会高频用到的,运用 Stream 结束各种杂乱操作的示例,让我们做项目的时分可以直接进行参阅,进一步提升你用 Java 编程、开发项目的体会。