说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢? 在Java 8中,得益于Lambda所带 来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。 几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作 而当我们需要对集合中的元 素进行操作的时候,除了必需的添加、删除、获取外,典型的就是集合遍历
循环遍历的弊端
Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),比如for循环:
- for循环的语法就是“怎么做”
- for循环的循环体才是“做什么”
循环遍历的弊端:遍历麻烦,需要创建新集合,多遍到的数据在加入新集合,反复操作增加了工作量
Stream的更优写法
下面来看一下借助Java 8的Stream API,什么才叫优雅:
import java.util.ArrayList;public class Test {public static void main(String[] args) {ArrayList<String> list = new ArrayList<String>();list.add("一缕清风");list.add("Kenguba");list.add("庆古八");list.stream().filter(s->s.startsWith("一")).filter(s->s.length()>3).forEach(System.out::println); // 一缕清风}}//JavaScript中var array = ["一缕清风", "Kenguba", "庆古八"]array.filter(s => s.startsWith("一")).filter(s => s.length > 3).forEach(s => console.log(s))
流式思想概述
Stream(流)是一个来自数据源的元素队列
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算
- 数据源 流的来源。 可以是集合,数组 等
整体来看,流式思想类似于工厂车间的“生产流水线”
这张图中展示了过滤、映射、跳过、计数等多步操作
- 这里的 filter、map、skip 都是在对函数模型进行操作,集合元素并没有真正被处理。
- 只有当终结方法 count 执行的时候
整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性
和以前的Collection操作不同,Stream操作还有两个基础的特征:
- Pipelining
- 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)
- 内部迭代
- 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法
⚠️注意: 请暂时忘记对传统IO流的固有印象!
获取一个流的几种常用的方式
Java 8新加入的常用的流接口。(这并不是一个函数式接口)java.util.stream.Stream<T>
所有的 Collection 集合 都可以通过 stream 默认方法获取流
new ArrayList<>().stream();new LinkedList<>().stream();new HashSet<>().stream();new HashMap<>().keySet().stream();new HashMap<>().values().stream();new HashMap<>().entrySet().stream();
数组 :可以用 Stream 接口的静态方法 of 获取对应的流
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);Stream.of(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });Stream.of(new String[] { "1", "2", "3", "4", "5", "6", "7", "8", "9" });
常用方法,流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种
- 延迟方法
- 返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法
- 终结方法
- 返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用
- 终结方法包括 count 和 forEach 方法
Stream 方式
forEach 逐一处理
会将每一个流元素交给该函数进行处理,它是终结方法,只要写了它,后面就再没有返回流了
虽然方法名字叫 forEach ,但是与for循环中的“for-each”不同
该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理void forEach(Consumer<? super T> action);//java.util.function.Consumer<T>接口是一个消费型接口Consumer<T>接口 中包含一个接口方法 void accept(T t),意为消费一个指定泛型的数据@FunctionalInterfacepublic interface Consumer<T> {void accept(T t);default Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> { accept(t); after.accept(t); };}}
import java.util.function.Consumer;import java.util.stream.Stream;public class Test {public static void main(String[] args) {Stream.of(1, 2, 3).forEach(new Consumer<Integer>() {@Overridepublic void accept(Integer integer) {System.out.println(integer);}});Stream.of(1, 2, 3).forEach(s -> System.out.println(s));Stream.of(1, 2, 3).forEach(System.out::println);}}
count 统计个数
正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来数一数其中的元素个数,它是终结方法,只要写了它,后面就再没有返回流了
该方法返回一个long值代表元素个数(不再像旧集合那样是int值)
long count();
import java.util.stream.Stream;
public class Test {
public static void main(final String[] args) {
Stream<String> ss = Stream.of("张无忌", "张三丰", "周芷若").filter(s -> s.startsWith("张"));
System.out.println(ss.count());
}
}
filter 过滤
可以通过 filter 方法将一个流转换成另一个子集流
该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件
Stream<T> filter(Predicate<? super T> predicate)
java.util.stream.Predicate 函数式接口
其中唯一的抽象方法为
boolean test(T t);
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
public class Test {
public static void main(final String[] args) {
Stream.of("张无忌", "赵敏").filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张");
}
}).forEach(new Consumer<String>() {
@Override
public void accept(final String s) {
System.out.println(s);
}
});
Stream.of("张无忌", "赵敏").filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
Stream.of("张无忌", "赵敏").filter(s -> s.startsWith("张")).forEach(System.out::println);
}
}
map 映射
如果需要将流中的元素映射到另一个流中,可以使用 map 方法
该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
java.util.stream.Function 函数式接口,其中唯一的抽象方法为
R apply(T t);
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
public class Test {
public static void main(final String[] args) {
Stream.of("1", "2", "3", "4", "5").map(new Function<String, Object>() {
@Override
public Object apply(String s) {
return Integer.parseInt(s);
}
}).forEach(new Consumer<Object>() {
@Override
public void accept(Object o) {
System.out.println(o);
}
});
Stream.of("1", "2", "3", "4", "5").map(s -> Integer.parseInt(s)).forEach(s -> {
System.out.println(s + 1);
});
Stream.of("1", "2", "3", "4", "5").map(Integer::parseInt).forEach(System.out::println);
}
}
limit 取用前几个
limit 方法可以对流进行截取,只取用前n个
Stream<T> limit(long maxSize);
import java.util.stream.Stream;
public class Test {
public static void main(final String[] args) {
Stream<String> ss = Stream.of("张无忌", "张三丰", "周芷若").limit(2);
System.out.println(ss.count());
}
}
skip 跳过前几个
如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
Stream<T> skip(long n);
import java.util.stream.Stream;
public class Test {
public static void main(final String[] args) {
Stream<String> ss = Stream.of("张无忌", "张三丰", "周芷若").skip(2);
System.out.println(ss.count());
}
}
concat 组合
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
import java.util.stream.Stream;
public class Test {
public static void main(final String[] args) {
Stream<String> stream1 = Stream.of("五华县");
Stream<String> stream2 = Stream.of(new String[] { "横陂镇" });
Stream.concat(stream1, stream2).forEach(System.out::println);
}
}
⚠️注意: 这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的
集合元素处理(Stream方式)
import java.util.*;
import java.util.stream.Stream;
class Person {
public String name;
public Person(String name) {
this.name = name;
}
}
public class Test {
public static void main(final String[] args) {
ArrayList<String> one = new ArrayList<>();
Collections.addAll(one, "宋远桥", "苏星河", "石破天", "老子", "庄子");
ArrayList<String> two = new ArrayList<>();
Collections.addAll(two, "张无忌", "张三丰", "张天爱", "张二狗");
Stream<String> stringStream1 = one.stream().filter(s -> s.length() == 3).limit(2);
Stream<String> stringStream2 = two.stream().filter(s -> s.startsWith("张")).limit(2);
Stream.concat(stringStream1, stringStream2).map(name -> new Person(name)).forEach(System.out::println);
}
}
