Java8-使用流

Stream API就可以在背后进行多种优化。此外,使用内部迭代的话, Stream API可以决定并行运行代码。Stream API支持的许多操作。支持复杂的数据查询,如筛选、切片、映射、查找、匹配和归约。Stream API还支持一些特殊的流:数值流、来自文件和数组等多种来源的流,无限流。


筛选和切片

谓词筛选

  • Streams接口支持filter方法。该操作会接受一个谓词(() -> boolean)作为参数,并返回一个包括所有符合谓词的元素的流

demo-1

  • 创建一张素食菜单
1
2
3
4
5
// Filtering with predicate
List<Dish> vegetarianMenu = menu.stream()
// Stream<T> filter(Predicate<? super T> predicate);
.filter(Dish::isVegetarian)
.collect(toList());

筛选各异的元素

  • distinct的方法,它会返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流。

demo-1

  • 筛选出列表中所有的偶数,并确保没有重复
1
2
3
4
5
6
// Filtering unique elements
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);

截断流

  • 流支持limit(n)方法,该方法会返回一个不超过给定长度的流。
  • limit也可以用在无序流上,比如源是一个Set。这种情况下, limit的结果不会以任何顺序排列。

demo-1

  • 选出热量超过300卡路里的头三道菜
1
2
3
4
5
6
// Truncating a stream
List<Dish> dishesLimit3 =
menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3) // 选出了符合谓词的头三个元素,然后就立即返回了结果
.collect(toList());

跳过元素

  • 流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。

demo-1

  • 跳过超过300卡路里的头两道菜,并返回剩下的
1
2
3
4
5
6
// Skipping elements
List<Dish> dishesSkip2 =
menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());

映射

对流中每一个元素应用函数

  • 流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的Target类型的元素, 返回的是映射后的元素组成的流: Stream<Target>

demo-1

  • 提取流中菜肴的名称
1
2
3
4
5
// map
List<String> dishNames = menu.stream()
// <R> Stream<R> map(Function<? super T, ? extends R> mapper);
.map(Dish::getName) // map方法输出的流的类型就是Stream<String>
.collect(toList());

demo-2

  • 单词列表每个单词的长度
1
2
3
4
5
// map
List<String> words = Arrays.asList("Hello", "World");
List<Integer> wordLengths = words.stream()
.map(String::length) // Stream<Integer>
.collect(toList());

流的扁平化

  • Arrays.stream()的方法可以接受一个数组并产生一个其中元素的流
  • 使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。(数组被映射成的流被合并起来
  • flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流

Problem-1

  • 对于一张单词表 , 如何返回一张列表,列出里面各不相同的字符呢?例如,给定单词列表[“Hello”,”World”],你想要返回列表[“H”,”e”,”l”, “o”,”W”,”r”,”d”]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// error solutions.
// 思路1
List<String[]> list = words.stream()
.map(str -> str.split("\\s+")) // 返回的流类型Stream<String[]>, 而我们想要的是Stream<Stream>
.distinct()
.collect(toList()); // 注意此时的返回值类型.

// 思路2
List<Stream<String>> streamList = words.stream()
.map(str -> str.split("\\s+")) // Stream<String[]>
.map(Arrays::stream) // Stream<Stream<String>>
.distinct()
.collect(toList()); // Stream->List,注意返回值.

// 只有以下两个是正确的.
// 思路3
// flatMap
List<String> stringList = words.stream()
.flatMap(str -> Arrays.stream(str.split("\\s+"))) // str -> Stream<String[]> -> Stream<Stream<String>> -> Stream<String>
.distinct()
.collect(toList());\

// 两个步骤分拆
List<String> uniqueCharacters = words.stream()
.map(w -> w.split("")) // Stream<String[]>
.flatMap(Arrays::stream) // Stream<String>
.distinct()
.collect(Collectors.toList());

Problem-2

  • 给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?例如,给定[1, 2, 3, 4,5],应该返回[1, 4, 9, 16, 25]
1
2
3
4
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares =numbers.stream()
.map(n -> n * n)
.collect(toList());

Problem-3

  • 给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1, 2, 3]和列表[3, 4],应该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]
1
2
3
4
5
6
List<Integer> numbers11 = Arrays.asList(1, 2, 3);
List<Integer> numbers22 = Arrays.asList(3, 4);
List<int[]> pairs1 = numbers1.stream()
.flatMap(i -> numbers2.stream()
.map(j -> new int[]{i, j})) // // 每个i都映射为一个Stream<int[]>, 需要将这多个Stream<int[]>合并为一个Stream<int[]>
.collect(toList());

Problem-4

  • 同上, 但是只返回总和能被3整除的数对
1
2
3
4
5
6
7
8
// flatMap
List<Integer> numbers1 = Arrays.asList(1,2,3,4,5);
List<Integer> numbers2 = Arrays.asList(6,7,8);
List<int[]> pairs = numbers1.stream()
.flatMap((Integer i) -> numbers2.stream()
.map((Integer j) -> new int[]{i, j}))
.filter(pair -> (pair[0] + pair[1]) % 3 == 0) // 对生成的Stream<int[]>进行过滤,得到的还是Stream<int[]>流
.collect(toList());

查找和匹配

  • 数据集中的某些元素是否匹配一个给定的属性。 Stream API通过allMatch、 anyMatch、 noneMatch、 findFirst和findAny方法提供了这样的工具。
  • anyMatch、 allMatch和noneMatch这三个操作都用到了短路特性
  • 对于流而言,某些操作 (例如allMatch、anyMatch、noneMatch、findFirst和findAny)不用处理整个流就能得到结果。只要找到一个元素,就可以有结果了。同样,limit也是一个短路操作:它只需要创建一个给定大小的流,而用不着处理流中所有的元素。
  • xxxMatch 需要传入一个Predicate谓词.

检查谓词是否至少匹配一个元素

  • anyMatch方法会看“流中是否有一个元素能匹配给定的谓词”
  • anyMatch方法返回一个boolean,因此是一个终端操作
1
2
3
if(menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}

检查谓词是否匹配所有元素

  • allMatch会看流中的元素是否都能匹配给定的谓词。
  • noneMatch可以确保流中没有任何元素与给定的谓词匹配。
1
2
3
4
5
6
7
// allMatch
boolean isHealthy = menu.stream()
.allMatch(d -> d.getCalories() < 1000); // 所有的Dish是否都是 < 1000 calories

// noneMatch
boolean isHealthy = menu.stream()
.noneMatch(d -> d.getCalories() >= 1000);

查找元素

  • findAny方法将返回当前流中的任意元素。流水线将在后台进行优化使其只需走一遍,并在利用短路找到结果时立即结束
  • findXXX()方法并不需要传入一个Predicate谓词.
1
2
3
4
Optional<Dish> dish = menu.stream()
.filter(Dish::isVegetarian)
.findAny(); // findAny可能什么元素都没找到。所以返回的是Optional<Dish>
dish.ifPresent(d -> System.out.println(d.getName()));

Optional简介

  • Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。
  • Optional里面几种可以显式地检查值是否存在或处理值不存在的情形
    • isPresent()将在Optional包含值的时候返回true, 否则返回false。
    • ifPresent(Consumer<T> block)会在值存在的时候执行给定的代码块。Consumer函数式接口;它让你传递一个接收T类型参数,并返回void的Lambda表达式。
    • T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。
    • T orElse(T other)会在值存在时返回值,否则返回一个默认值。
      1
      2
      3
      4
      menu.stream()
      .filter(Dish::isVegetarian)
      .findAny()
      .ifPresent(d -> System.out.println(d.getName());

查找第一个元素

  • findFirst 方法找到流中的第一个元素.工作方式类似于findAny
  • 何时使用findFirstfindAny
    • 为什么会同时有findFirst和findAny呢?因为并行的限制。findFirst找到第一个元素在并行上限制更多。如果不关心返回的元素是哪个,请使用findAny,因为它在使用并行流时限制较少
1
2
3
4
5
6
// 找到第一个能被3整除的数
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree = someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst(); // 同样返回的是Optional<Integer>, 因为可能什么都找不到, 9

归约

  • 归约操作(将流归约成一个值)

元素求和

  • reduce接受两个参数:

    • 一个初始值,这里是0;
    • 一个 BinaryOperator<T> ((T, T) -> T)来将两个元素结合起来产生一个新值,这里我们用的是Lambda (a, b) -> a + b; Lambda反复结合每个元素,直到流被归约成一个值。
  • 考虑流中没有任何元素的情况。reduce操作无法返回其和,因为它没有初始值。这就是为什么结果被包裹在一个Optional对象里,以表明和可能不存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// for-each方式
int sum = 0;
for (int x : numbers) {
sum += x;
}

// reduce方式求和
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);

// reduce方式求积
int product = numbers.stream()
.reduce(1, (a, b) -> a * b);

// Integer类有了一个静态的sum方法来对两个数求和
int sum = numbers.stream().reduce(0, Integer::sum);

// reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));

最大值和最小值

  • reduce接受两个参数:
    • 一个初始值
    • 一个Lambda来把两个流元素结合起来并产生一个新值
1
2
3
4
5
6
// Optional<T> reduce(BinaryOperator<T> accumulator);
Optional<Integer> max = numbers.stream().reduce(Integer::max); // 需要一个BinaryOperator, 接收两个值,返回一个值.

// 需要一个最大值
// 可以使用Lambda (x, y) -> x < y ? x : y而不是Integer::min
Optional<Integer> min = numbers.stream().reduce(Integer::min); // 同上.

demo-1

  • 用map和reduce方法数一数流中有多少个元素
1
2
3
4
5
6
int count = menu.stream()
.map(d -> 1)
.reduce(0, (a, b) -> a + b);

// 内置的count()方法
long count = menu.stream().count();

中间操作和终端操作汇总

java8-intermediate-terminal-ops-2.png-134.2kB


实践

  • 问题
    • (1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
    • (2) 交易员都在哪些不同的城市工作过?
    • (3) 查找所有来自于剑桥的交易员,并按姓名排序。
    • (4) 返回所有交易员的姓名字符串,按字母顺序排序。
    • (5) 有没有交易员是在米兰工作的?
    • (6) 打印生活在剑桥的交易员的所有交易额。
    • (7) 所有交易中,最高的交易额是多少?
    • (8) 找到交易额最小的交易。

Trader.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public  class Trader{

private String name;
private String city;

public Trader(String n, String c){
this.name = n;
this.city = c;
}

public String getName(){
return this.name;
}

public String getCity(){
return this.city;
}

public void setCity(String newCity){
this.city = newCity;
}

public String toString(){
return "Trader:"+this.name + " in " + this.city;
}
}

Transaction.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Transaction{

private Trader trader;
private int year;
private int value;

public Transaction(Trader trader, int year, int value)
{
this.trader = trader;
this.year = year;
this.value = value;
}

public Trader getTrader(){
return this.trader;
}

public int getYear(){
return this.year;
}

public int getValue(){
return this.value;
}

public String toString(){
return "{" + this.trader + ", " +
"year: "+this.year+", " +
"value:" + this.value +"}";
}
}

解答

  • toSet(),这样就会把流转换为集合;
  • joining (其内部会用到StringBuilder),会对Stream<String>进行reduce拼接.
  • 流支持min和max方法,它们可以接受一个Comparator作为参数,指定计算最小或最大值时要比较哪个键值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class MyAnswers {

public static void main(String... args) {
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");

List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);

// 找出2011年的所有交易并按交易额排序(从低到高)
List<Transaction> transactionList = transactions.stream()
.filter(t -> t.getYear() == 2011)
.sorted(comparing(Transaction::getValue))
.collect(toList());
System.out.println(transactionList);


// 交易员都在哪些不同的城市工作过
List<String> cities = transactions.stream()
.map(t -> t.getTrader())
.map(trader -> trader.getCity()) // 两个可以合并为一个.
.distinct()
.collect(toList()); // toSet()更简洁, 去掉distinct()方法.
System.out.println(cities);

// 查找所有来自于剑桥的交易员,并按姓名排序
List<Trader> traders = transactions.stream()
.map(transaction -> transaction.getTrader()) // map(Transaction::getTrader)
.filter(trander -> trander.getCity().equals("Cambridge"))
.distinct()
.sorted(comparing(Trader::getName))
.collect(toList());

System.out.println(traders);

// 返回所有交易员的姓名字符串,按字母顺序排序
String nameString = transactions.stream()
.map(transaction -> transaction.getTrader().getName())
.distinct()
.sorted()
.reduce("", (a, b) -> a + b);// collect(joining()); better.
System.out.println(nameString);

// 有没有交易员是在米兰工作的
boolean anyMilan = transactions.stream()
.anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan"));
System.out.println(anyMilan);

// 打印生活在剑桥的每个交易员的交易额
transactions.stream()
.filter(transaction -> transaction.getTrader().getCity().equals("Cambridge"))
.map(transaction -> transaction.getValue()) // map(Transaction::getValue)
.forEach(System.out::println);


// 所有交易中,最高的交易额是多少
Optional<Integer> maxValue = transactions.stream()
.map(transaction -> transaction.getValue()) // 等价于 map(Transaction::getValue)
.reduce(Integer::max);
maxValue.ifPresent(System.out::println);

// 找到交易额最小的交易
transactions.stream()
.reduce((t1, t2) -> (t1.getValue() < t2.getValue()) ? t1 : t2);
// 等价于
transactions.stream()
.min(comparing(Transaction::getValue));

}
}

数值流

  • Stream API还提供了原始类型流特化,专门支持处理数值流的方法。
  • Java 8引入了三个原始类型特化流接口:IntStreamDoubleStreamLongStream,分别将流中的元素特化为int、 long和double,从而避免了暗含的装箱成本。每个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。此外还有在必要时再把它们转换回对象流的方法。这些特化的原因并不在于流的复杂性,而是装箱造成的复杂性

IntStream.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
public interface IntStream extends BaseStream<Integer, IntStream> {

IntStream filter(IntPredicate predicate);
IntStream map(IntUnaryOperator mapper);
<U> Stream<U> mapToObj(IntFunction<? extends U> mapper);
// maptoXXX()
IntStream flatMap(IntFunction<? extends IntStream> mapper);
IntStream distinct();
IntStream sorted(); // Returns a stream consisting of the elements of this stream in sorted order.
IntStream peek(IntConsumer action);
IntStream limit(long maxSize);
IntStream skip(long n);
void forEach(IntConsumer action);
void forEachOrdered(IntConsumer action);
int[] toArray();
int reduce(int identity, IntBinaryOperator op);
<R> R collect(Supplier<R> supplier,
ObjIntConsumer<R> accumulator,
BiConsumer<R, R> combiner);
int sum();
OptionalInt min();
OptionalInt max();
long count();
OptionalDouble average();
IntSummaryStatistics summaryStatistics();
boolean anyMatch(IntPredicate predicate);
boolean allMatch(IntPredicate predicate);
boolean noneMatch(IntPredicate predicate);
OptionalInt findFirst();
OptionalInt findAny();
LongStream asLongStream();
DoubleStream asDoubleStream();
Stream<Integer> boxed();

@Override
IntStream sequential();

@Override
IntStream parallel();

@Override
PrimitiveIterator.OfInt iterator();

@Override
Spliterator.OfInt spliterator();

// ...
public static IntStream empty() {
return StreamSupport.intStream(Spliterators.emptyIntSpliterator(), false);
}
public static IntStream of(int t) {
return StreamSupport.intStream(new Streams.IntStreamBuilderImpl(t), false);
}
public static IntStream of(int... values) {
return Arrays.stream(values);
}

/**
* Returns an infinite sequential ordered {@code IntStream} produced by iterative
* application of a function {@code f} to an initial element {@code seed},
* producing a {@code Stream} consisting of {@code seed}, {@code f(seed)},
* {@code f(f(seed))}, etc.
*/
public static IntStream iterate(final int seed, final IntUnaryOperator f) {
Objects.requireNonNull(f);
final PrimitiveIterator.OfInt iterator = new PrimitiveIterator.OfInt() {
int t = seed;

@Override
public boolean hasNext() {
return true;
}

@Override
public int nextInt() {
int v = t;
t = f.applyAsInt(t);
return v;
}
};
return StreamSupport.intStream(Spliterators.spliteratorUnknownSize(
iterator,
Spliterator.ORDERED | Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
}

/**
* Returns an infinite sequential unordered stream where each element is
* generated by the provided {@code IntSupplier}. This is suitable for
* generating constant streams, streams of random elements, etc.
*/
public static IntStream generate(IntSupplier s) {
Objects.requireNonNull(s);
return StreamSupport.intStream(
new StreamSpliterators.InfiniteSupplyingSpliterator.OfInt(Long.MAX_VALUE, s), false);
}

/**
* Returns a sequential ordered {@code IntStream} from {@code startInclusive}
* (inclusive) to {@code endExclusive} (exclusive) by an incremental step of
* {@code 1}.
*/
public static IntStream range(int startInclusive, int endExclusive) {
if (startInclusive >= endExclusive) {
return empty();
} else {
return StreamSupport.intStream(
new Streams.RangeIntSpliterator(startInclusive, endExclusive, false), false);
}
}

/**
* Returns a sequential ordered {@code IntStream} from {@code startInclusive}
* (inclusive) to {@code endInclusive} (inclusive) by an incremental step of
* {@code 1}.
*/
public static IntStream rangeClosed(int startInclusive, int endInclusive) {
if (startInclusive > endInclusive) {
return empty();
} else {
return StreamSupport.intStream(
new Streams.RangeIntSpliterator(startInclusive, endInclusive, true), false);
}
}

/**
* Creates a lazily concatenated stream whose elements are all the
* elements of the first stream followed by all the elements of the
* second stream. The resulting stream is ordered if both
* of the input streams are ordered, and parallel if either of the input
* streams is parallel. When the resulting stream is closed, the close
* handlers for both input streams are invoked.
*/
public static IntStream concat(IntStream a, IntStream b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);

Spliterator.OfInt split = new Streams.ConcatSpliterator.OfInt(
a.spliterator(), b.spliterator());
IntStream stream = StreamSupport.intStream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}

// ...
}

映射到数值流

  • 将流转换为特化版本的常用方法是mapToInt、mapToDouble和mapToLong。这些方法和前面说的map方法的工作方式一样,只是它们返回的是一个特化流,而不是Stream
  • 如果流是空的, sum默认返回0
1
2
3
int calories = menu.stream() // Stream<Dish>
.mapToInt(Dish::getCalories) // IntStream
.sum();

转换回对象流

  • 要把原始流转换成一般流(每个int都会装箱成一个Integer),可以使用boxed方法;IntStream -> Stream<Integer>
1
2
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();

默认值OptionalInt

  • Optional类,这是一个可以表示值存在或不存在的容器。Optional可以用Integer、String等参考类型来参数化。对于三种原始流特化,也分别有一个Optional原始类型特化版本:OptionalInt、OptionalDouble和OptionalLong。
  • 要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt
1
2
3
4
5
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getCalories) // IntStream
.max();

int max = maxCalories.orElse(1); // 显示定义一个默认值

数值范围

  • Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:range和rangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但range是不包含结束值的,而rangeClosed则包含结束值。
1
2
3
4
5
// numeric ranges
IntStream evenNumbers = IntStream.rangeClosed(1, 100)
.filter(n -> n % 2 == 0);

System.out.println(evenNumbers.count()); // 50

勾股数

  • 三元数(a, b, c)满足公式a a + b b = c * c,其中a、 b、 c都是整数
  • 使用具有三个元素的int数组,比如new int[]{3,4,5},来表示勾股数(3,4,5);也可以定义一个包含三个域的类表示勾股数.
  • 要测试a a + b b的平方根是不是整数,也就是说它没有小数部分 – 在Java里可以使用expr % 1表示
  • 如果没有boxed(), 会抛出如下异常:
  • 如下的解决方案基本就是生成数对(number-pairs)的套路.

Error:(49, 29) java: 不兼容的类型: lambda 表达式中的返回类型错误
不存在类型变量R的实例, 以使java.util.stream.Stream与java.util.stream.IntStream一致

implementation-1

1
2
3
4
5
6
7
8
9
Stream<int[]> pythagoreanTriples =  IntStream.rangeClosed(1, 100)
.boxed()
// 多个Stream<Integer[]>扁平化为一个Stream<Integer[]>
.flatMap(a -> IntStream.rangeClosed(a, 100)
.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0) //IntStream
.boxed() // Stream<Integer>
.map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)})); // 多个Stream<Integer[]>

pythagoreanTriples.forEach(t -> System.out.println(t[0] + ", " + t[1] + ", " + t[2]));

implementation-2

1
2
3
4
5
6
7
8
Stream<int[]> triples = IntStream.rangeClosed(1, 100)
.boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.filter(b -> Math.sqrt(a * a + b * b) % 1 == 0) // 仍然得到的是IntStream
.mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)}));// IntStream的mapToObj方法来改写它, 来得到一个Stream<int[]>流
System.out.println("-----------");

triples.forEach(t -> System.out.println(t[0] + ", " + t[1] + ", " + t[2]));

implementation-3

1
2
3
4
5
6
7
Stream<double[]> triples2 = IntStream.rangeClosed(1, 100)
.boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.mapToObj(b -> new double[]{a, b, Math.sqrt(a * a + b * b)}) // 先映射出所有的可能组合
.filter(array -> array[2] % 1 == 0)); // 然后进行过滤
System.out.println("-----");
triples2.forEach(t -> System.out.println((int) t[0] + ", " + (int) t[1] + ", " + (int) t[2]));

构建流

  • 从值序列、数组、文件来创建流,甚至由生成函数来创建无限流!

由值创建流

  • 静态方法Stream.of,通过显式值创建一个流。它可以接受任意数量的参数。
1
2
3
4
5
6
7
// Stream.of
Stream<String> stream = Stream.of("Java 8", "Lambdas", "In", "Action");
stream.map(String::toUpperCase).forEach(System.out::println); // 映射为大写, 然后输出

// 空流
// Stream.empty
Stream<String> emptyStream = Stream.empty();

由数组创建流

  • 静态方法Arrays.stream从数组创建一个流。它接受一个数组作为参数。
1
2
3
4
// Arrays.stream
int[] numbers = {2, 3, 5, 7, 11, 13};
// 原始类型int的数组转换成一个IntStream
System.out.println(Arrays.stream(numbers).sum());

由文件生成流

  • java.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法是Files.lines,它会返回一个由指定文件中的各行构成的字符串流。
1
2
3
4
long uniqueWords = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())
.flatMap(line -> Arrays.stream(line.split(" "))) // line : Stream<String[]> , 然后多个String<String[]> -> 单个Stream<String[]>
.distinct()
.count();

由函数生成流:创建无限流

  • Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。这两个操作可以创建无限流. 由iterate和generate产生的流会用给定的函数按需创建值,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。

iterate

  • iterate方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator类型)。
  • 在需要依次生成一系列值的时候应该使用iterate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 接收的Lambda是UnaryOperator<t>类型,描述符是 T->T
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);

// Fabnacci 数对
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0]+t[1]})
.limit(20)
.forEach(t -> System.out.println("(" + t[0] + "," + t[1] +")"));

// Fabnacci 数列,提取第一个元素即可
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1],t[0] + t[1]})
.limit(10)
.map(t -> t[0])
.forEach(System.out::println);

generate

  • generate方法也可让你按需生成一个无限流。但generate不是依次对每个新生成的值应用函数的。它接受一个Supplier类型的Lambda提供新的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// random stream of doubles with Stream.generate
Stream.generate(Math::random) // Math.Random静态方法被用作新值生成器
.limit(10)
.forEach(System.out::println);

// supplier.
IntSupplier fib = new IntSupplier(){
private int previous = 0;
private int current = 1;
public int getAsInt(){
int nextValue = this.previous + this.current;
this.previous = this.current;
this.current = nextValue;
return this.previous;
}
};
IntStream.generate(fib).limit(10).forEach(System.out::println);

总结

  • 可以使用filter、 distinct、 skip和limit对流做筛选和切片。
  • 可以使用map和flatMap提取或转换流中的元素。
  • 可 以 使 用 findFirst 和 findAny 方 法 查 找 流 中 的 元 素 。 你 可 以 用 allMatch 、noneMatch和anyMatch方法让流匹配给定的谓词。
  • 这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流。
  • 可以利用reduce方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大元素。
  • filter和map等操作是无状态的,它们并不存储任何状态。reduce等操作要存储状态才能计算出一个值。 sorted和distinct等操作也要存储状态,因为它们需要把流中的所有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。
  • 流有三种基本的原始类型特化:IntStream、DoubleStream和LongStream。它们的操作也有相应的特化。
  • 流不仅可以从集合创建,也可从值、数组、文件以及iterate与generate等特定方法创建。
  • 无限流是没有固定大小的流。