stream 的处理效率会高于 iterator,特别是使用了并行流,在cpu恰好将线程分配到多个核心的条件下(当然parallel stream 底层使用的是 JVM 的 ForkJoinPool,这东西分配线程本身就很玄学),可以达到一个很高的运行效率,然而实际普通业务一般不会有需要迭代高于10000次的计算;
流创建
- Stream.of()
通过 Stream.of() 可以很容易地将一组元素转化为流
Stream.of(new Bubble(1), new Bubble(2), new Bubble(3)).forEach(System.out::println);
Stream.of("a", "b", "c", "d", "e", "f").forEach(System.out::print);
Stream.of(3.14159, 2.718, 1.618).forEach(System.out::println);
- stream()
每个集合也可以通过调用 stream() 方法来产生一个流
List<Bubble> list = Arrays.asList(new Bubble(1), new Bubble(2), new Bubble(3));
list.stream().forEach(System.out::print);
Set<String> set = new HashSet<>(Arrays.asList("a", "b", "c", "d", "e", "f"));
set.stream().forEach(System.out::print);
- Stream.generate()
使用 Stream.generate() 搭配 Supplier 生成 T 类型的流
Stream.generate(Math::random).limit(10).forEach(System.out::print);
- Stream.iterate()
Stream.iterate() 产生的流的第一个元素是种子,然后把种子传递给方法,方法的运行结果被添加到流,并作为下次调用 iterate() 的第一个参数
Stream.iterate(0, n -> n + 1).limit(10).forEach(System.out::print)
使用 Stream.generate() 和 Stream.iterate() 生成的无限流一定要用 limit() 截断
5. Stream.builder()
使用建造者模式创建一个 builder 对象,然后将创建流所需的多个信息传递给它,最后 builder 对象执行创建流的操作
Stream.Builder<String> builder = Stream.builder();
builder.add("a");
builder.add("b");
...
builder.build(); // 创建流
// builder.add("c") // 调用 build() 方法后继续添加元素会产生异常
- Arrays.stream()
Arrays 类中有一个名为 stream() 的静态方法用于把数组转换成流
Arrays.stream(new double[] {3.14159, 2.718, 1.618}).forEach(System.out::print);
Arrays.stream(new int[] {1, 3, 5}).forEach(System.out::print);
Arrays.stream(new long[] {11, 22, 44, 66}).forEach(System.out::print);
// 选择一个子域
Arrays.stream(new int[] {1, 3, 5, 7, 15, 28, 37}, 3, 6).forEach(System.out::print);
最后一次 stream() 的调用有两个额外的参数,第一个参数告诉 stream() 从数组的哪个位置开始选择元素,第二个参数告知在哪里停止
7. IntStream.range()
IntStream 类提供 range() 方法用于生成整型序列的流,编写循环时,这个方法会更加便利
IntStream.range(10, 20).sum(); // 求得 10 - 20 的序列和
IntStream.range(10, 20).forEach(System.out::print); // 循环输出 10 - 20
- 随机数流
Random 类被一组生成流的方式增强了,可以生成一组随机数流
Random rand = new Random(47);
// 产生一个随机流
rand.ints().boxed();
// 控制上限和下限
rand.ints(10, 20).boxed();
// 控制流的大小
rand.ints(2).boxed();
// 控制流的大小和界限
rand.ints(3, 3, 9).boxed();
Random 类除了能生成基本类型 int,long,double 的流,使用 boxed() 操作会自动把基本类型包装为对应的装箱类型
9. 正则表达式
Java8 在 java.util.regex.Pattern 中新增了一个方法 splitAsStream(),这个方法可以根据传入的公式将字符序列转化为流
Pattern.compile("[.,?]+").splitAsStream(“a,b,c,d,e”).forEach(System.out::print);
public class StreamFeaturesTest {
/**
* 流的简单例子
*/
@Test
public void test1() {
List<Integer> list = Stream.of(1, 2, 5, 9, 7, 3).filter(val-> val> 2).sorted().collect(Collectors.toList());
for (Integer item : list) {
System.out.println(item);
}
}
/**
* 流不会改变数据源
*/
@Test
public void test2() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(1);
Assert.assertEquals(3, list.stream().distinct().count());
Assert.assertEquals(4, list.size());
}
/**
* 流不可以重复使用
*/
@Test(expected = IllegalStateException.class)
public void test3() {
Stream<Integer> integerStream = Stream.of(1, 2, 3);
Stream<Integer> newStream = integerStream.filter(val -> val > 2);
integerStream.skip(1);
}
}
中间操作
- 跟踪和调试
peek() 操作的目的是帮助调试,它提供了一种对流中所有元素操作的方法,同时提供一个消费函数,对流中元素进行操作,并返回一个新流。一般不建议这样做,更多的用途应该是无修改地查看流中的元素
Stream.of("a b c d e".split(" ")).map(w -> w + " ").peek(System.out::print);
- 流元素排序
sorted() 可以帮助我们实现对流元素的排序,如果不使用默认的自然排序,则需要传入一个比较器,也可以把 Lambda 函数作为参数传递给 sorted()
Stream.of("a b c d e".split(" ")).sorted(Comparator.reverseOrder())
.map(w -> w + " ").peek(System.out::print);
- 移除元素
distinct() 可用于消除流中的重复元素
new Random(47).ints(5, 20).distinct().limit(7).forEach(System.out::println);
filter(Predicate) 将元素传递给过滤函数,若结果为 true,则保留元素
// 检测质数
Stream.iterate(2, n -> n + 1).filter(i -> i % 2 ==0)
.limit(10).forEach(System.out::print)
- 应用函数到元素
map(Function) 将函数操作应用到输入流的元素,并将返回值传递到输出流
Arrays.stream(new String[] {"12", "23", "34"}).map(s -> "[" + s + "]")
.forEach(System.out::print)
另外还有 mapToInt(ToIntFunction)、mapToLong(ToLongFunction)、mapToDouble(ToDoubleFunction),操作和 map(Function) 相似,只是结果流为各自对应的基本类型
如果在将函数应用到元素的过程中抛出了异常,此时会把原始元素放到输出流
- 组合流
使用 flatMap() 将产生流的函数应用在每个元素上,然后将产生每个流都扁平化为元素
Stream.of(1, 2, 3).flatMap(i -> Stream.of("hello" + i)).forEach(System.out::println);
另外还有 flatMapToInt(Function)、flatMapToLong(Function)、flatMapToDouble(Function),操作和 flatMap() 相似,只是结果元素为各自对应的基本类型
Optional 类
如果在一个空流中尝试获取元素,结果肯定是得到一个异常。我们希望可以得到友好的提示,而不是糊你一脸 NullPointException。Optional 的出现就是为了解决臭名昭著的空指针异常
- findFirst() :返回一个包含第一个元素的 Optional 对象,如果流为空则返回 Optional.empty
- findAny():返回包含任意元素的 Optional 对象,如果流为空则返回 Optional.empty
- max() 和 min():返回一个包含最大值或者最小值的 Optional 对象,如果流为空则返回 Optional.empty
- reduce(Function):将函数的返回值包装在 Optional 中
1. 便利函数
Optional 类本质上是一个容器对象,所谓容器是指:它可以保存类型 T 的值,也可以保存一个 null。此外,Optional 提供了许多有用的方法,可以帮助我们不用显示地进行空值检测:
- ifPresent():是否有值存在,存在放回 true,否则返回 false
- ifPresent(Consumer):当值存在时调用 Consumer,否则什么也不做
- orElse(otherObject):如果值存在则直接返回,否则生成 otherObject
- orElseGet(Supplier):如果值存在则直接返回,否则使用 Supplier 函数生成一个可替代对象
- orElseThrow(Supplier):如果值存在则直接返回,否则使用 Supplier 函数生成一个异常
class OptionalBasics {
static void test(Optional<String> optString) {
if(optString.isPresent())
System.out.println(optString.get());
else
System.out.println("Nothing inside!");
}
public static void main(String[] args) {
test(Stream.of("Epithets").findFirst());
test(Stream.<String>empty().findFirst()); // 生成一个空流
}
}
2.创建 Optional
当我们需要在自己的代码中加入 Optional 时,可以使用下面三个静态方法:
- empty():生成一个空 Optional
- of(value):将一个非空值包装到 Optional 里
- ofNullable(value):针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional 中
3. Optional 对象操作
当我们的流管道生成 Optional 对象,下面三个方法可以使得 Optional 能做更多后续操作:
- filter(Predicate):对 Optional 中的内容应用 Predicate 并将结果返回。如果 Optional 不满足 Predicate,将 Optional 转化为空 Optional 。如果 Optional 已经为空,则直接返回空 Optional
- map(Function):如果 Optional 不为空,应用 Function 于 Optional 中的内容,并返回结果,否则直接返回 Optional.empty
- flatMap(Function):一般应用于已生成 Optional 的映射函数,所以 flatMap() 不会像 map() 那样将结果封装在 Optional 中
终端操作
终端操作将获取流的最终结果,至此我们无法再继续往后传递流。可以说,终端操作总是我们在使用流时所做的最后一件事
- 数组
当我们需要得到数组类型的数据以便于后续操作时,可以使用下述方法产生数组:
toArray():将流转换成适当类型的数组
toArray(generetor):生成自定义类型的数组
- 循环
常见的如 forEach(Consumer),另外还有 forEachOrdered(Consumer),保证按照原始流的顺序操作。第二种形式仅在引入并行流时才有意义。所谓并行流是将流分割为多个,并在不同的处理器上分别执行。由于多处理器并行操作的原因,输出的结果可能会不一样,因此需要用到 forEachOrdered(Consumer)
- 集合
在这里我们只是简单介绍一下常见的 Collectors 示例,实际上它还有一些非常复杂的实现。大多数情况下,java.util.stream.Collectors 中预设的 Collector 就能满足我们的需求
collect(Collector):使用 Collector 收集流元素到结果集合中
collect(Supplier, BiConsumer, BiConsumer):第一个参数创建一个新的结果集合,第二个参数将下一个元素收集到结果集合中,第三个参数用于将两个结果集合合并起来
- 组合
组合意味着将流中所有元素以某种方式组合为一个元素
reduce(BinaryOperator):使用 BinaryOperator 来组合所有流中的元素。因为流可能为空,其返回值为 Optional
reduce(identity, BinaryOperator):功能同上,但是使用 identity 作为其组合的初始值。因此如果流为空,identity 就是结果
Stream.generate(Math::random).limit(10)
.reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1).ifPresent(System.out::println);
返回的结果是 Optional 类型,Lambda 表达式中的第一个参数 fr0 是 reduce 中上一次调用的结果,而第二个参数 fr1 是从流传递过来的值
- 匹配
allMatch(Predicate):如果流的每个元素提供给 Predicate 都返回 true ,结果返回为 true。在第一个 false 时,则停止执行计算
anyMatch(Predicate):如果流的任意一个元素提供给 Predicate 返回 true ,结果返回为 true。在第一个 true 是停止执行计算
noneMatch(Predicate):如果流的每个元素提供给 Predicate 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算
- 查找
findFirst():返回第一个流元素的 Optional,如果流为空返回 Optional.empty
findAny():返回含有任意流元素的 Optional,如果流为空返回 Optional.empty
- 信息
count():流中的元素个数
max(Comparator):根据所传入的 Comparator 所决定的最大元素
min(Comparator):根据所传入的 Comparator 所决定的最小元素
- 数字流信息
average():求取流元素平均值
max() 和 min():数值流操作无需 Comparator
sum():对所有流元素进行求和
public class Apple {
private int id; // 编号
private String color; // 颜色
private int weight; // 重量
private String birthplace; // 产地
public Apple(int id, String color, int weight, String birthplace) {
this.id = id;
this.color = color;
this.weight = weight;
this.birthplace = birthplace;
}
// getter/setter 省略
}
public class StreamTest {
private static final List<Apple> appleStore = Arrays.asList(
new Apple(1, "red", 500, "湖南"),
new Apple(2, "red", 100, "天津"),
new Apple(3, "green", 300, "湖南"),
new Apple(4, "green", 200, "天津"),
new Apple(5, "green", 100, "湖南")
);
public static void main(String[] args) {
appleStore.stream().filter(apple -> apple.getWeight() > 100)
.peek(apple -> System.out.println("通过第1层筛选 " + apple))
.filter(apple -> "green".equals(apple.getColor()))
.peek(apple -> System.out.println("通过第2层筛选 " + apple))
.filter(apple -> "湖南".equals(apple.getBirthplace()))
.peek(apple -> System.out.println("通过第3层筛选 " + apple))
.collect(Collectors.toList());
}
}