Java 8 Stream教程
Stream不同于之前的I/O流,而是Java 8引入的函数式编程。
函数式编程
简单说,”函数式编程”是一种”编程范式”(programming paradigm),也就是如何编写程序的方法论。
它属于”结构化编程”的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。
Stream 简单例子
用一个遍历数组的例子看看Stream的作用。
传统写法
List<String> myList =
Arrays.asList("a1", "a2", "b1", "c2", "c1");
List<String> newList = new ArrayList<String>();
//遍历
for (String name : myList)
{
//筛选出字母开头是c的
if (name.startsWith("c"))
{
//转换成大写
String newName = name.toUpperCase();
newList.add(newName);
}
}
//排列
Collections.sort(newList);
//遍历输出
newList.forEach(System.out::println);
Stream写法
List<String> myList =
Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList
.stream() //开始流操作
.filter(s -> s.startsWith("c")) //筛选出开头字母是C的
.map(String::toUpperCase) //转换成大写
.sorted() //排序
.forEach(System.out::println); //遍历输出
//C1
//C2
Stream操作这里有用到Lambda表达式,大多数的Steam操作都可以用Lamdba表达式。Stream操作简化了很多,主要体现在逻辑清晰,没有过多的if/else判断语句。
Stream的种类
Stream分为sequential Stream(顺序流)和parallel stream(平行流),上面的例子是属于顺序流,通过stream()方法创建的流都是顺序流。
Stream
一般的集合如List和Set都支持Stream操作,但可以不通过创建集合来使用Stream操作,可以通过Stream.of()方法使用,代码如下:
Stream.of("a1", "a2", "a3")
.findFirst()
.ifPresent(System.out::println); // a1
Primitive streams
Stream是处理对象的操作,primitive streams是处理处理传统类型的操作,如IntStream、LongStream 、DoubleStream分别处理对应的类型,这里展示InStream的简单使用:
IntStream.range(1, 4)
.forEach(System.out::println);
// 1
// 2
// 3
Primitive streams和Streams的使用基本一样,只是有一些专门处理基本类型的方法,像上面的range(),还有如下例子:
Arrays.stream(new int[] {1, 2, 3})
.map(n -> 2 * n + 1)
.average()
.ifPresent(System.out::println); // 5.0
average() 方法求平均值,Primitive streams和Stram都有方法讲对象和基本类型互相转换,代码如下:
Stream.of(1.0, 2.0, 3.0)
.mapToInt(Double::intValue)
.mapToObj(i -> "a" + i)
.forEach(System.out::println);
// a1
// a2
// a3
Stream操作的顺序
先看下面例子代码:
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> {
System.out.println("filter: " + s);
return true;
});
该例子实际上是不会输出任何东西,因为Stream没有终端操作,只有中间操作。
再看看加入终端操作后的结果:
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> {
System.out.println("filter: " + s);
return true;
})
.forEach(s -> System.out.println("forEach: " + s));
输出结果:
filter: d2
forEach: d2
filter: a2
forEach: a2
filter: b1
forEach: b1
filter: b3
forEach: b3
filter: c
forEach: c
这里的输出结果可能会让你觉得很诧异,是数组元素按顺序执行完整个流操作,即要第一个元素执行完整个流操作才轮到第二个。
我们可以将元素重新排序然后再执行流操作,通过sort()方法,代码如下:
Stream.of("d2", "a2", "b1", "b3", "c")
.sorted((s1, s2) -> {
System.out.printf("sort: %s; %s\n", s1, s2);
return s1.compareTo(s2);
})
.filter(s -> {
System.out.println("filter: " + s);
return s.startsWith("a");
})
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
})
.forEach(s -> System.out.println("forEach: " + s));
输出结果:
sort: a2; d2
sort: b1; a2
sort: b1; d2
sort: b1; a2
sort: b3; b1
sort: b3; d2
sort: c; b3
sort: c; d2
filter: a2
map: a2
forEach: A2
filter: b1
filter: b3
filter: c
filter: d2
Reusing Streams
Java 8的Stream不能被重复使用直到该Stream已经结束,其实Stream可以返回一个实例,看实例代码:
Stream<String> stream =
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> s.startsWith("a"));
stream.anyMatch(s -> true); // ok
stream.noneMatch(s -> true); // exception
该结果会出现异常,因为stream的anyMatch()方法还没执行完就执行noneMatch(),异常如下:
java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459)
at com.winterbe.java8.Streams5.test7(Streams5.java:38)
at com.winterbe.java8.Streams5.main(Streams5.java:28)
可以通过创建Supplier来实现重复执行,代码例子如下:
Supplier<Stream<String>> streamSupplier =
() -> Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> s.startsWith("a"));
streamSupplier.get().anyMatch(s -> true); // ok
streamSupplier.get().noneMatch(s -> true); // ok
较为复杂的Stream操作
前面只是举例说明了一下,常用的map()、filter()、foreach()等操作很容易看明白,还有很多操作可以看这个链接
Java stream doc。
首先创建一个Person类方便说明,如下:
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name;
}
}
List<Person> persons =
Arrays.asList(
new Person("Max", 18),
new Person("Peter", 23),
new Person("Pamela", 23),
new Person("David", 12));
Collect
Collect可以在最后将结果转化成一个集合返回,代码如下:
List<Person> filtered =
persons
.stream()
.filter(p -> p.name.startsWith("P"))
.collect(Collectors.toList());
System.out.println(filtered); // [Peter, Pamela]
可以看到还是比较简单实用的,通过Collectors.toList()方法直接返回一个List,如果需要set就用toSet(),
使用非常方便。
还可以生成Map指点类集合和求平均值,代码例子分别如下:
Map<Integer, String> map = persons
.stream()
.collect(Collectors.toMap(
p -> p.age,
p -> p.name,
(name1, name2) -> name1 + ";" + name2));
System.out.println(map);
Double averageAge = persons
.stream()
.collect(Collectors.averagingInt(p -> p.age));
System.out.println(averageAge); // 19.0
FlatMap
如果集合或数组的元素本身也是集合或数组,而且我们就是要求访问集里面的集合的时候就要用到FlatMap,代码例子如下:
class Foo {
String name;
List<Bar> bars = new ArrayList<>();
Foo(String name) {
this.name = name;
}
}
class Bar {
String name;
Bar(String name) {
this.name = name;
}
}
foos.stream()
.flatMap(f -> f.bars.stream())
.forEach(b -> System.out.println(b.name));
// Bar1 <- Foo1
// Bar2 <- Foo1
// Bar3 <- Foo1
// Bar1 <- Foo2
// Bar2 <- Foo2
// Bar3 <- Foo2
// Bar1 <- Foo3
// Bar2 <- Foo3
// Bar3 <- Foo3
Reduce
Reduce的作用就是将流操作的多个结果汇集成一个,例子如下:
Person result =
persons
.stream()
.reduce(new Person("", 0), (p1, p2) -> {
p1.age += p2.age;
p1.name += p2.name;
return p1;
});
System.out.format("name=%s; age=%s", result.name, result.age);
parallel stream
parallelStream不同于Stream的地方是它会让中间流操作利用ForkJoinPool的线程进行操作,而使用的线程是不确定的,代码例子如下:
Arrays.asList("a1", "a2", "b1", "c2", "c1")
.parallelStream()
.filter(s -> {
System.out.format("filter: %s [%s]\n",
s, Thread.currentThread().getName());
return true;
})
.map(s -> {
System.out.format("map: %s [%s]\n",
s, Thread.currentThread().getName());
return s.toUpperCase();
})
.sorted((s1, s2) -> {
System.out.format("sort: %s <> %s [%s]\n",
s1, s2, Thread.currentThread().getName());
return s1.compareTo(s2);
})
.forEach(s -> System.out.format("forEach: %s [%s]\n",
s, Thread.currentThread().getName()));
输出结果:
filter: b1 [main]
filter: a1 [ForkJoinPool.commonPool-worker-3]
filter: a2 [ForkJoinPool.commonPool-worker-1]
map: a2 [ForkJoinPool.commonPool-worker-1]
filter: c1 [ForkJoinPool.commonPool-worker-2]
forEach: A2 [ForkJoinPool.commonPool-worker-1]
map: a1 [ForkJoinPool.commonPool-worker-3]
map: b1 [main]
forEach: A1 [ForkJoinPool.commonPool-worker-3]
filter: c2 [ForkJoinPool.commonPool-worker-1]
map: c1 [ForkJoinPool.commonPool-worker-2]
map: c2 [ForkJoinPool.commonPool-worker-1]
forEach: B1 [main]
forEach: C2 [ForkJoinPool.commonPool-worker-1]
forEach: C1 [ForkJoinPool.commonPool-worker-2]