0%

Java-流(一)

流的创建

将集合转换为流

Collection接口的stream()方法可以将任意一个集合转化为一个流

1
2
3
4
String contents = new String(Files.readAllBytes(
Paths.get("../microservice.txt")), StandardCharsets.UTF_8);
List<String> list = Arrays.asList(contents.split("\\PL+"));
list.stream().filter(s -> s.length() > 12).forEach(System.out::println);

将数组转换为流

利用静态方法Stream.of()

1
2
Stream<String> words = Stream.of(contents.split("\\PL+"));
Stream<String> song = Stream.of("gently", "down", "the", "stream");

利用Pattern类的splitAsStream()方法

1
Stream<String> wordsAnotherWay = Pattern.compile("\\PL+").splitAsStream(contents);

静态方法Arrays.stream()

1
2
3
String[] article = contents.split(" ");
Stream<String> strings = Arrays.stream(article, 0, 5);
strings.forEach(System.out::println);

创建不包含任何元素的流

可以用静态的Stream.empty()方法

1
Stream<String> silence = Stream.empty();

创建无限流

常量值无限流

1
Stream<String> echos = Stream.generate(() -> "Echo");

随机数流

1
Stream<Double> randoms = Stream.generate(Math::random);

自然数序列

序列:0, 1, 2, 3…

1
Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));

Files.lines()方法

返回一个包含了文件中所有行的Stream对象

1
2
Path path = Paths.get("D:/Code/Java/Algorithm/src/cn/nopech/stream/microservice.txt");
Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8);

API java.util.stream.Stream

  • public static<T> Stream<T> of(T... values)
  • public static<T> Stream<T> empty()
  • public static<T> Stream<T> generate(Supplier<? extends T> s)
    产生无限流
  • public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
    产生无限流

API java.util.Arrays

  • public static IntStream stream(int[] array, int startInclusive, int endExclusive)
    可以指定数组的范围

API java.util.regex.Pattern

  • public static Pattern compile(String regex)
  • public Stream<String> splitAsStream(final CharSequence input)

API java.nio.file.Files

  • public static Stream<String> lines(Path path)
  • public static Stream<String> lines(Path path, Charset cs)
    产生一个流,流的元素是指定文件中的行,该文件的字符集可以指定,默认UTF-8

方法filter, mapflatMap

流的转换会产生一个新的流,其元素派生自另一个流

filter()方法

API java.util.stream.Stream

filter()方法转换产生一个流,其中元素可以条件匹配,源码如下

1
Stream<T> filter(Predicate<? super T> predicate)

我们可以看到参数是Predicate对象,即从T到boolean的函数

例子

例如,我们可以将一个字符串流转换成只包含常单词的另一个流

1
2
List<String> wordList = Arrays.asList(contents.split("\\PL+"));
Stream<String> longWords = wordList.stream().filter(s -> s.length() > 12);

map()方法

API java.util.stream.Stream

通常,我们想要按照某种方式转换流中的值,这时就可以用map()方法并传递执行该转换的函数,源码如下

1
<R> Stream<R> map(Function<? super T, ? extends R> mapper)

可以看到,传递的是一个Function对象

例子

例如,我们可以将所有单词转换为大小写,这里利用方法引用简化语句

1
Stream<String> lowercaseWords = list.stream().map(String::toLowerCase);

在使用map()方法时,会有一个函数应用到每个元素上,其结果包含了应用该函数后所产生的所有结果的流

flatMap()方法

API java.util.stream.Stream

1
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

例子

我们假设有一个函数,其返回是一个包含众多值得流,例如letters("TEST")返回的是流["T", "E", "S", "T"]

1
2
3
4
5
6
7
8
9
10
11
12
13
public static Stream<String> letters(String s) {
List<String> result = new ArrayList<>();
for (int i = 0; i < s.length(); i++) {
result.add(s.substring(i, i + 1));
}
return result.stream();
}

@Test
public void test() {
letters("TEST").forEach(System.out::print);
}
// Output: TEST

假设我们在一个字符串流上映射letters()方法:

1
2
3
4
5
6
7
8
9
10
String contents = "It is a test";
List<String> words = Arrays.asList(contents.split("\\PL+"));
Stream<Stream<String>> result = words.stream().map(w -> letters(w));
result.forEach(System.out::println);
/* Output:
java.util.stream.ReferencePipeline$Head@5b6f7412
java.util.stream.ReferencePipeline$Head@27973e9b
java.util.stream.ReferencePipeline$Head@312b1dae
java.util.stream.ReferencePipeline$Head@7530d0a
*/

得到的就是一个包含流的流,即[["I", "t"], ["i", "s"], ["a"], ["t", "e", "s", "t"]]

为了将其平摊为字母流,就可以用flatMap()方法

1
2
3
Stream<String> flatResult = words.stream().flatMap(w -> letters(w));
flatResult.forEach(System.out::print);
// Output: Itisatest

结果看起来是不是很丝滑

抽取子流与连接流

limit()方法

API java.util.stream.Stream

limit(long)方法会返回一个新的流,它在第maxSize个元素之后结束
如果原来的流更短,它就会在流结束时结束,不用担心越界

1
Stream<T> limit(long maxSize);

例子

这个方法对裁剪无限流的尺寸会很有用,例如可以裁剪随机数流的前五个元素

1
2
3
Stream<Double> randoms = Stream.generate(Math::random).limit(5);
randoms.forEach(n -> System.out.printf("%.3f ", n));
// 0.936 0.454 0.873 0.552 0.259

skip()方法

API java.util.stream.Stream

该方法会丢弃前个元素,这个方法在将文本分隔为单词时会显得很方便

1
Stream<T> skip(long n);

例子

获取文本第7个单词到第13个单词

1
2
3
Stream<String> words = Stream.of(contents.split("\\PL+")).skip(6).limit(7);
words.forEach(s -> System.out.print(s + " "));
// on the crowded streets of software architecture

concat()方法

API java.util.stream.Stream

Stream类的静态方法concat()可以将两个流连接起来

1
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

例子

连接两个字符串流["H", "e", "l", "l", "o"]["W", "o", "r", "l", "d"]

1
2
3
Stream<String> combined = Stream.concat(letters("Hello"), letters("World"));
combined.forEach(System.out::print);
// HelloWorld

其他的流转换

distinct()方法

API java.util.stream.Stream

该方法返回的时没有重复元素的流

1
Stream<T> distinct();

例子

看到输出,我们可以发现这个流可以记住前面的元素

1
2
3
4
5
6
7
Stream<String> uniqueWords = Stream.of("aaa", "aaa", "bbb", "ccc", "aaa").distinct();
uniqueWords.forEach(System.out::println);
/*
aaa
bbb
ccc
*/

sorted()方法

API java.util.stream.Stream

对于流的排序,有多种sorted()方法的重载形式,它会产生一个新的流

1
2
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);

第一个方法要求元素是实现了Comparable接口的实例

例子

1
2
3
4
Stream<String> longestFirst = Stream.of("aa","ccc","d")
.sorted(Comparator.comparing(String::length).reversed());
longestFirst.forEach(s -> System.out.print(s + " "));
// ccc aa d

peek()方法

API java.util.stream.Stream

1
Stream<T> peek(Consumer<? super T> action);

例子

1
2
3
4
5
6
7
8
9
Object[] powers = Stream.iterate(1, p -> p * 2)
.peek(e -> System.out.println("Fetching " + e)).limit(5).toArray();
/*
Fetching 1
Fetching 2
Fetching 4
Fetching 8
Fetching 16
*/

简单约简

约简是一种终结操作(terminal operation),下面的方法会将流约简为可以在程序中使用的非流值

API java.util.stream.Stream

  • Optional<T> max(Comparator<? super T> comparator);
  • Optional<T> min(Comparator<? super T> comparator);
    是终结操作:分别产生流的最大元素和最小元素(如果流是空的,会产生一个空的Optional对象)
  • Optional<T> findFirst();
  • Optional<T> findAny();
    是终结操作:分别产生流的第一个和任意元素(如果流是空的,会产生一个空的Optional对象)
  • boolean anyMatch(Predicate<? super T> predicate);
  • boolean allMatch(Predicate<? super T> predicate);
  • boolean noneMatch(Predicate<? super T> predicate);
    是终结操作:分别在流中任意元素、所有元素和没有任何元素匹配给定断言是返回true

Optional类型

Optional<T>对象是一种包装器对象,Optional<T>类型被当作一种更安全的方式,用来代替类型T的引用
这种引用要么为某对象,要么为null
但是,它只有在正确使用的情况下才会更安全

使用

方法:它在值不存在的情况下会产生一个可替代物,只有在值存在的情况下才会使用该值
看个例子,在没有字符串任何匹配时,可以使用空字符串代替

1
2
// The wrapped string, or "" if none
String result = optionalString.orElse("");

还可以调用代码来计算默认值:

1
2
// The function is only called when needed
String result = optionalString.orElseGet(() -> Locale.getDefault().getDisplayName());

或者可以在没有任何值时抛出异常

1
2
// supply a mthod that yield an exception object
String result = optionalString.orElseThrow(IllegalStateException::new);

刚刚展示的是在不存在任何值的情况下产生相应的代替物

另一种使用可选值的策略是在其存在的情况下才消费该值

ifPresent()方法会接受一个函数,如果可选值存在,会被传递给函数,否则不会发生任何事情
例如,在该值存在的情况下将添加到集合中,可以这样调用

1
2
3
optionalValue.ifPresent(v -> resultList.add(v));
或者直接调用
optionalValue.ifPresent(resultList::add);

当调用ifPresent()方法时不会返回任何值,如果想要处理函数的结果,应该使用map()方法

1
Optional<Boolean> added = optionalValue.map(resultList::add);

当下,added具有三种值之一:

  • optionalValue存在的情况下包装在Optional中的truefalse
  • optionalValue不存在的情况下的空的Optional对象

API java.util.Optional

  • public T orElse(T other)
  • public T orElseGet(Supplier<? extends T> supplier)
  • public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
  • public boolean isPresent(Consumer<? super T> action)
    如果Optional不为空,就把其值传递给action
  • public <U> Optional<U> map(Function<? super T, ? extends U> mapper)
    只要Optional不为空且结果不为null,就会产生将该Optional的值传递给mapper后的结果,否则产生一个空的Optional对象

不适合使用Optional对象的情况

get()方法

Optional类的get()方法,源码如下

1
2
3
4
5
6
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}

可以看到,该方法会在Optional值存在的情况下获得其包装的元素,否则抛出异常

因此,这个方法不如直接的方式安全

1
2
3
4
5
6
Optional<T> optionalValue = ...;
optionalValue.someMethod();

// 以下做法更安全
T value = ...;
value.someMethod();

ifPresent()方法

Optional类的ifPresent()方法,源码如下

1
2
3
public boolean isPresent() {
return value != null;
}

该方法会报告某个Optional<T>对象是否具有一个值
但是这种调用不如直接调用容易处理

1
2
3
4
5
6
7
8
if (optionalValue.ifPresent()) {
optionalValue.get().someMethod();
}

// 以下做法更简单
if (value != null) {
value.someMethod();
}

创建Optional对象

API java.util.Optional

  • public static <T> Optional<T> of(T value)
    产生一个具有给定值的Optional对象,如果value为空,会抛出异常
  • public static <T> Optional<T> ofNullable(T value)
    产生一个具有给定值的Optional对象,如果value为空,会产生一个空的Optional对象
  • public static<T> Optional<T> empty()
    产生一个空的Optional对象

flatMap()来构建Optional对象

假设有一个可以产生Optional<T>对象的方法f(),并且目标类型T具有一个可以产生Optional<U>对象的方法’g()’

如果它们都是普通的方法就可以通过s.f().g()组合起来,但Optional对象封装以后却不行

因此,需要使用flatMap()方法

1
Optional<U> result = s.f().flatMap(T::g);

如果s的值存在,那么g就可以应用,否则,返回一个空Optional<U>对象

API java.util.Optional

1
2
3
4
5
6
7
8
9
10
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty();
} else {
@SuppressWarnings("unchecked")
Optional<U> r = (Optional<U>) mapper.apply(value);
return Objects.requireNonNull(r);
}
}

附上一个演示OptionalAPI的例子

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
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

public class OptionalTest {
public static void main(String[] args) throws IOException {
String contents = new String(Files.readAllBytes(
Paths.get("../microservices.txt")), StandardCharsets.UTF_8);
List<String> wordList = Arrays.asList(contents.split("\\PL+"));

Optional<String> optionalValue = wordList.stream()
.filter(s -> s.contains("micro"))
.findFirst();
System.out.println(optionalValue.orElse("No word") + " contains micro");

Optional<String> optionalString = Optional.empty();
String result = optionalString.orElse("N/A");
System.out.println("result: " + result);
result = optionalString.orElseGet(() -> Locale.getDefault().getDisplayName());
System.out.println("result: " + result);
try {
result = optionalString.orElseThrow(IllegalStateException::new);
System.out.println("result: " + result);
} catch (Throwable t) {
t.printStackTrace();
}

optionalValue = wordList.stream()
.filter(s -> s.contains("services"))
.findFirst();
optionalValue.ifPresent(s -> System.out.println(s + " contains services"));

Set<String> results = new HashSet<>();
optionalValue.ifPresent(results::add);
Optional<Boolean> added = optionalValue.map(results::add);
System.out.println(added);

System.out.println(inverse(4.0).flatMap(OptionalTest::squareRoot));
System.out.println(inverse(-1.0).flatMap(OptionalTest::squareRoot));
System.out.println(inverse(0.0).flatMap(OptionalTest::squareRoot));
Optional<Double> result2 = Optional.of(-4.0)
.flatMap(OptionalTest::inverse)
.flatMap(OptionalTest::squareRoot);
System.out.println(result2);
}

public static Optional<Double> inverse(Double x) {
return x == 0 ? Optional.empty() : Optional.of(1 / x);
}

public static Optional<Double> squareRoot(Double x) {
return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));
}
}
/*
microservice contains micro
result: N/A
result: 中文 (中国)
Microservices contains services
Optional[false]
Optional[0.5]
Optional.empty
Optional.empty
Optional.empty
java.lang.IllegalStateException
at java.base/java.util.Optional.orElseThrow(Optional.java:408)
at cn.nopech.stream_operation.OptionalTest.main(OptionalTest.java:26)
*/