Java Stream 流
一、引入流
1、一个案例引入
看一个使用 Stream
(Java8)和不使用 Stream
(Java7)代码量的区别。
这里需要筛选出一份菜单中卡路里<400的菜的名字。
public class Code_01_Java7AndJava8Compare {
public static void main(String[] args) {
// 返回 热量<400 的菜肴 的 名称, 返回结果按照从低到高排序, Java7 的写法
System.out.println(java7());
System.out.println(java8());
}
static List<String> java7(){
List<Dish> lowCaloricDishes = new ArrayList<>();
for (Dish d : Dish.menu) {
if (d.getCalories() < 400) {
lowCaloricDishes.add(d);
}
}
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
public int compare(Dish d1, Dish d2) {
return Integer.compare(d1.getCalories(), d2.getCalories());
}
});
List<String> lowCaloricDishesName = new ArrayList<>();
for (Dish d : lowCaloricDishes) {
lowCaloricDishesName.add(d.getName());
}
return lowCaloricDishesName;
}
static List<String> java8(){
List<String> lowCaloricDishesName =
Dish.menu.stream()
.filter(d -> d.getCalories() < 400)
.sorted(Comparator.comparing(Dish::getCalories))
.map(Dish::getName)
.collect(Collectors.toList());
return lowCaloricDishesName;
}
}
2、流简介
- 流简短的定义:是数据渠道,用于操作数据源(集合,数组等) 所生成的元素序列;
- 元素序列 — 就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序
值。因为集合是数据结构,所以它的主要目的是以特定的时间/ 空间复杂度存储和访问元
素(如 ArrayList 与 LinkedList) 。但流的目的在于表达计算,比如你前面见到的filter、 sorted 和 map
。集合讲的是数据,流讲的是计算。 - 源 — 流会使用一个提供数据的源,如集合、数组或输入/输出资源;
- 数据处理操作 — 流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中
的常用操作,如filter、 map、 reduce、 find、 match、 sort
等;
- 元素序列 — 就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序
- 流的重要的特点
- 流水线 — 很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大
的流水线;流水线的操作可以看作对数据源进行数据库式查询; - 内部迭代 — 与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的;
- ①
Stream
自己不会存储元素;②Stream
不会改变原对象,相反,他们会返回一个持有结果的新Stream
;③Stream 操作是延迟执行的,这意味着他们会等到需要结果的时候才执行;
- 流水线 — 很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大
3、流与集合
1)、只能遍历一次
请注意,和迭代器类似,流只能遍历一次
- 遍历完之后,我们就说这个流已经被消费掉了;
- 你可以从原始数据源那里再获得一个新的流来重新遍历一遍,就像迭代器一样(这里假设它是集
合之类的可重复的源,如果是 I/O 通道就没戏了);
2)、外部迭代与内部迭代
- 使用
Collection
接口需要用户去做迭代(比如用for-each
) ,这称为外部迭代; - 相反, Streams 库使用内部迭代;
外部迭代和内部迭代的区别:
4、流操作
主要分为两类操作: 中间操作和终端操作。
1)、中间操作
中间操作就是产生的结果(仍然是一个流)。
诸如 filter
或 sorted
等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。
// 除非流水线上触发一个终端操作,否则中间操作不会执行任何处理
// 流的延迟性质。
public class Code_02_StreamDelayFeature {
public static void main(String[] args) {
List<String> names =
Dish.menu.stream()
.filter(d -> {
System.out.println("filtering" + d.getName());
return d.getCalories() > 300;
})
.map(d -> {
System.out.println("mapping" + d.getName());
return d.getName();
})
.limit(3)
.collect(Collectors.toList());
System.out.println(names);
// // 终端操作
// System.out.println("-------Terminal Operation------");
// Dish.menu.stream().forEach(System.out::println);
}
}
看运行结果,可以发现 Stream
是有延迟的性质。
2)、终止操作
终止操作会从流的流水线生成结果。其结果是任何不是流的值,比如 List、 Integer,甚至 void。
3)、流的使用步骤
流的流水线背后的理念类似于构建器模式。
三个基本步骤:
- 创建 Stream : 需要一个数据源(如:集合,数组),获取一个流;
- 中间操作: 一个中间操作链,对数据源的数据进行处理;
- 终止操作(终端操作): 一个终止操作,执行中间操作链,并产生结果;
二、使用流
1、构建流
构建的流的方式有:
- 从 Collection 中构建;
- 从值
value
(Stream.of()
) 中构建; - 从数组中构建(
Arrays.stream()
); - 从文件中构建;
- 由函数生成:创建无限流;
创建的几种方法的示例代码:
/** 创建流的几种方法 */
public class Code_03_CreateStream {
public static void main(String[] args) throws IOException {
PrintStream out = System.out;
out.println("--------fromCollection--------");
fromCollection().forEach(x -> out.print(x + " ")); // 从 Collection 中创建 Stream
out.println("\n" + "-------fromValues---------");
fromValues().forEach(x -> out.print(x + " ")); // 从 Collection 中创建 Stream
out.println("\n" + "--------fromArrays--------");
fromArrays().forEach(x -> out.print(x + " ")); // 从 Collection 中创建 Stream
out.println("\n" + "--------fromFile--------");
fromFile().forEach(out::println); // 从函数中创建
out.println("\n" + "--------fromIterate--------");
fromIterate().forEach(x -> out.print(x + " ")); // 从函数中创建
out.println("\n" + "--------fromGenerate--------");
fromGenerate().forEach(x -> out.print(x + " ")); // 从函数中创建
out.println("\n" + "--------fromCustom--------");
fromCustom().forEach(x -> out.print(x + " "));
out.println("\n" + "----------------");
}
static Stream<String> fromCollection() {
List<String> list = Arrays.asList("aa", "bb", "cc");
return list.stream();
}
static Stream<String> fromValues() {
return Stream.of("aa", "bb", "cc");
}
static Stream<String> fromArrays() {
String[] str = {"aa", "bb", "cc"};
return Arrays.stream(str);
}
static Stream<String> fromFile() throws IOException {
Path path = Paths.get("/home/zxzxin/Main.java");
Stream<String> stream = Files.lines(path);
return stream;
}
static Stream fromIterate() {
return Stream.iterate(0, n -> n + 2).limit(5); // 函数创建的无限流
}
static Stream<Double> fromGenerate(){
return Stream.generate(Math::random).limit(5);
}
// 创建 Custom 的流 (CusSupplier)
static Stream<Custom>fromCustom(){
return Stream.generate(new CusSupplier()).limit(5);
}
static class CusSupplier implements Supplier<Custom> {
private int index = 0;
private Random random = new Random(System.currentTimeMillis());
@Override
public Custom get() {
index = random.nextInt(100);
return new Custom(index, "name-" + index);
}
}
static class Custom {
private int id;
private String name;
public Custom(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Obj{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}
}
输出:
--------fromCollection--------
aa bb cc
-------fromValues---------
aa bb cc
--------fromArrays--------
aa bb cc
--------fromFile--------
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(new BufferedInputStream(System.in));
PrintStream out = System.out;
}
}
--------fromIterate--------
0 2 4 6 8
--------fromGenerate--------
0.18018050075496417 0.948721748467966 0.37983036182518304 0.679145483357325 0.21520045208568783
--------fromCustom--------
Obj{name='name-73', id=73} Obj{name='name-84', id=84} Obj{name='name-14', id=14} Obj{name='name-79', id=79} Obj{name='name-51', id=51}
----------------
2、filter、limit、skip、map、flatMap
- filter : 该操作会接受一个谓词(一个返回 boolean 的函数)(
Predicate
) 作为参数,并返回一个包括所有符合谓词的元素的流; - limit : 流支持
limit(n)
方法,该方法会返回一个不超过给定长度的流; - skip : 流还支持
skip(n)
方法,返回一个扔掉了前 n 个元素的流; - map : 流支持 map 方法,它会接受一个函数(
Function
) 作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”); - flatMap (扁平化):
flatmap()
方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接
起来成为一个流;
测试代码:
public class Code_04_StreamOperations1 {
static PrintStream out;
public static void main(String[] args){
out = System.out;
out.println( "-------filterTest---------");
filterTest();
out.println("\n" + "-------limitTest---------");
limitTest();
out.println("\n" + "-------skipTest---------");
skipTest();
out.println("\n" + "-------mapTest---------");
mapTest();
out.println("\n" + "-------flatMapTest---------");
flatMapTest();
}
static void filterTest(){
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
// 山选出偶数且没有重复
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct() // 去重
.forEach(x -> out.print(x + " "));
out.println();
}
static void limitTest(){
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream().
limit(3). // 取前 3 个
forEach(x -> out.print(x + " "));
out.println();
}
static void skipTest(){
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream().
skip(3). // 跳过前 3 个
forEach(x -> out.print(x + " "));
out.println();
}
// map 里面需要的是 Function
static void mapTest(){
// 例子 1
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream().
map(i -> i * 2). // 跳过前 3 个
collect(Collectors.toList()).
forEach(x -> out.print(x + " "));
out.println();
// 例子 2
List<String> words = Arrays.asList("aa", "bbb", "cccc", "ddddd"); // 长度分别为 2, 3, 4, 5
words.stream()
.map(String::length)
.collect(Collectors.toList())
.forEach(x -> out.print(x + " "));
out.println();
}
// 将 words 去重输出字符
static void flatMapTest(){
String[] words = {"Hello", "World"};
// {h, e, l, l, o}, {w, o, r, l, d}
Stream<String[]> stream = Arrays.stream(words).map(x -> x.split(""));
Stream<String> stringStream = stream.flatMap(s -> Arrays.stream(s));
stringStream.distinct().forEach(x -> out.print(x + " "));
out.println();
}
}
输出:
-------filterTest---------
2 4
-------limitTest---------
1 2 1
-------skipTest---------
3 3 2 4
-------mapTest---------
2 4 2 6 6 4 8
2 3 4 5
-------flatMapTest---------
H e l o W r d
3、match、find、reduce
- match:查看元素是否匹配(返回 boolean),包括
allMatch(), anyMatch()、noneMatch()
; - find :
isPresent()
将在 Optional 包含值的时候返回 true, 否则返回 false;ifPresent(Consumer<T> block)
会在值存在的时候执行给定的代码块;T get()
会在值存在时返回值,否则抛出一个 NoSuchElement 异常;T orElse(T other)
会在值存在时返回值,否则返回一个默认值;Optional<T> of(T value)
: 通过 value 构造一个 Optional;
public class Code_05_StreamOperations2 {
static PrintStream out;
public static void main(String[] args) {
out = System.out;
out.println("-------matchTest---------");
matchTest();
out.println("\n" + "-------findTest---------");
findTest();
out.println("\n" + "-------reduceTest---------");
reduceTest();
}
static void matchTest(){
List<Integer> arr = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
out.println(arr.stream().allMatch(i -> i > 10));
out.println(arr.stream().anyMatch(i -> i > 6));
out.println(arr.stream().noneMatch(i -> i < 0));
}
static void findTest(){
List<Integer> arr = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
Optional<Integer> any = arr.stream().filter(i -> i % 2 == 0).findAny();
out.println(any.get());
Optional<Integer> first = arr.stream().filter(i -> i % 2 == 0).findFirst();
first.ifPresent(out::println);
out.println(first.get()); //没有就抛出 NoSuchElementException
out.println(first.orElse(-1)); // 如果 first 为空就输出-1
System.out.println(first.filter(i -> i == 2).get()); // Optional 还会产生一个 stream
System.out.println(find(arr, -1, i -> i > 10)); // 自己写的一个防止空指针的,而 Optional 中有一个已经存在的
}
static int find(List<Integer> values, int defaultValue, Predicate<Integer> predicate){
for(int val : values){
if(predicate.test(val))
return val;
}
return defaultValue;
}
// reduce 也是一个 terminal 的操作
static void reduceTest(){
List<Integer> arr = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
System.out.println(arr.stream().reduce(0, (a, b) -> a + b)); //计算数组的和 ,有初始值就是 Integer
arr.stream().reduce((a, b) -> a + b).ifPresent(out::println); // 没有初始值就是 Optional
// 提取所有的偶数相乘
int res = arr.stream().filter(x -> x%2 == 0).reduce(1, (a, b) -> a*b);
Optional.of(res).ifPresent(out::println);
}
}
输出:
-------matchTest---------
false
true
true
-------findTest---------
2
2
2
2
2
-1
-------reduceTest---------
28
28
48
4、数值流
Java 8 引入了三个原始类型特化流接口来解决这个问题: IntStream、 DoubleStream 和 LongStream
,分别将流中的元素特化为 int、 long 和 double
,从而避免了暗含的装箱成本。这些特化的原因并不在于流的复杂性,而是装箱造成的复杂性——即类似 int 和 Integer 之间的效率差异。
映射方法:
- 映射到数值流
- 将流转换为特化版本的常用方法是 mapToInt、 mapToDouble 和 mapToLong;
- 例如 mapToInt 返回一个 IntStream(而不是一个
Stream<Integer>
);
- 转换回对象流
- 使用
boxed()
方法; - 用处: 例如,IntStream 上的操作只能产生原始整数。
例子(勾股数):
public class Code_06_NumericStream {
public static void main(String[] args) {
System.out.println("-------example1---------");
example1();
System.out.println("\n" + "-------example2---------");
example2();
System.out.println("\n" + "-------example3---------");
example3();
}
static void example1(){
List<Integer> arr = Arrays.asList(1, 3, 2, 5, 3, 3, 4);
Stream<Integer> integerStream = arr.stream().filter(i -> i.intValue() > 3);
Integer res = integerStream.reduce(0, Integer::sum);
IntStream intStream = arr.stream().mapToInt(i -> i.intValue());
int res2 = intStream.filter(i -> i > 3).sum();
System.out.println(res + " " + res2); // 一样的,但是转换成 IntStream 效率更高
}
// 产生 a = 5 勾股数
static void example2(){
int a = 5;
Stream<int[]> triples1 = IntStream.rangeClosed(1, 100)
.filter(b -> Math.sqrt(a * a + b * b) % 1 == 0)
.boxed()
.map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});
triples1.forEach(t ->
System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
}
// 产生 100 以内的所有勾股数
static void example3(){
Stream<int[]> triples2 =
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100) // a 也是 100 内随机产生的
.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a * a + b * b)})
);
triples2.limit(10).
forEach(t ->
System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
}
}
输出:
-------example1---------
9 9
-------example2---------
5, 12, 13
-------example3---------
3, 4, 5
5, 12, 13
6, 8, 10
7, 24, 25
8, 15, 17
9, 12, 15
9, 40, 41
10, 24, 26
11, 60, 61
12, 16, 20
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

上一篇: JVM 内存区域与内存管理
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论