《On Java 8》中文版,又名《Java编程思想》 第5版
接下来的都是个人学习过程的笔记,不是总结,没有参考价值,但是这本书很棒
流创建
package chapter14;
import java.util.*;
import java.util.stream.Stream;
public class ConllectionToStream {
public static void main(String[] args) {
List<Bubble> bubbles = Arrays.asList(new Bubble(1), new Bubble((2)), new Bubble(3));
System.out.println(bubbles.stream()
.mapToInt(b -> b.i)
.sum());
Set<String> w=new HashSet<>(Arrays.asList("It's a wonderful pie!".split(" ")));
w.stream().map(x->x+" ").forEach(System.out::print);
System.out.println();
Map<String,Double> m=new HashMap<>();
m.put("p1",3.14);
Stream<Map.Entry<String, Double>> stream = m.entrySet().stream();
stream.map((e->e.getKey()+": "+e.getValue()))
.forEach(System.out::println);
m.put("e", 2.718);
stream.map((e->e.getKey()+": "+e.getValue()))
.forEach(System.out::println);//会报错
}
}
上面会报错stream has already been operated upon or closed
,也就是说,流在获取后,如果源被修改,这个流就要重新获取
随机数流
// streams/RandomGenerators.java
import java.util.*;
import java.util.stream.*;
public class RandomGenerators {
public static <T> void show(Stream<T> stream) {
stream
.limit(4)
.forEach(System.out::println);
System.out.println("++++++++");
}
public static void main(String[] args) {
Random rand = new Random(47);
show(rand.ints().boxed());
show(rand.longs().boxed());
show(rand.doubles().boxed());
// 控制上限和下限:
show(rand.ints(10, 20).boxed());
show(rand.longs(50, 100).boxed());
show(rand.doubles(20, 30).boxed());
// 控制流大小:
show(rand.ints(2).boxed());
show(rand.longs(2).boxed());
show(rand.doubles(2).boxed());
// 控制流的大小和界限
show(rand.ints(3, 3, 9).boxed());
show(rand.longs(3, 12, 22).boxed());
show(rand.doubles(3, 11.5, 12.3).boxed());
}
}
对于boxed
方法,书上的解释是
但是 Random 类只能生成基本类型 int, long, double 的流。幸运的是,
boxed()
流操作将会自动地把基本类型包装成为对应的装箱类型,从而使得show()
能够接受流。
rand.ints()
的返回是IntStream
类型,其完整声明为public interface IntStream extends BaseStream<Integer, IntStream>
,而Stream
类型的完整声明为public interface Stream<T> extends BaseStream<T, Stream<T>>
,可以看出两者没有继承关系(我原本以为有呢)。而boxed()
就是进行转换用的
// streams/RandomWords.java
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
import java.io.*;
import java.nio.file.*;
public class RandomWords implements Supplier<String> {
List<String> words = new ArrayList<>();
Random rand = new Random(47);
RandomWords(String fname) throws IOException {
List<String> lines = Files.readAllLines(Paths.get(fname));
// 略过第一行
for (String line : lines.subList(1, lines.size())) {
for (String word : line.split("[ .?,]+"))
words.add(word.toLowerCase());
}
}
public String get() {
return words.get(rand.nextInt(words.size()));
}
@Override
public String toString() {
return words.stream()
.collect(Collectors.joining(" "));
}
public static void main(String[] args) throws Exception {
System.out.println(
Stream.generate(new RandomWords("Cheese.dat"))
.limit(10)
.collect(Collectors.joining(" ")));
}
}
首先,上面运行过程中,toString
没有被调用;然后Stream.generate
会根据Supplier
生成一个Stream
;limit
没啥好说的;最后,collect
是Stream
类的一个方法,顾名思义,将流内元素收集起来,该方法接受一个Collector
对象,这个对象指导流如何进行收集操作
generate()
package chapter14;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class Bubbles {
public static void main(String[] args) {
Supplier<Object> s=Bubble::bubbler;
Supplier<Bubble> b=Bubble::bubbler;
Stream.generate(s).limit(5).forEach(System.out::println);
}
}
上面代码里,s
和b
都是通过lambda表达式构建的,但s和b的泛型类型是不一样的。实际上Bubble::bubbler
返回的类是什么,是根据声明的时候的变量类型来决定的。就比如s
是Supplier<Object>
类的,那么表达式返回的就是这个类的。(当然,泛型只在编译期起作用)
iterate()
package chapter14;
import java.util.stream.Stream;
public class Fibonacci {
int x=1;
Stream<Integer> numbers(){
return Stream.iterate(0,i->{
System.out.println("damn"+i);
int result=x+i;
x=i;
return result;
});
}
public static void main(String[] args) {
new Fibonacci().numbers()
.skip(20)
.limit(10)
.forEach(System.out::println);
}
}
Stream.iterate() 以种子(第一个参数)开头,并将其传给方法(第二个参数)。方法的结果将添加到流,并存储作为第一个参数用于下次调用 iterate(),依次类推。上面可以看出,result每次都会存一下,用作下一次地调用的输入
流的建造者模式
package chapter14;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class FileToWordsBuilder {
Stream.Builder<String> builder=Stream.builder();
public FileToWordsBuilder(String filePath) throws IOException {
Files.lines(Paths.get(filePath))
.forEach(line->{
for (String w:line.split("[ .?,]+"))
builder.add(w);
});
}
Stream<String> stream(){
return builder.build();
}
public static void main(String[] args) throws IOException {
new FileToWordsBuilder("D:\\code\\OnJava8\\src\\chapter14\\Cheese.dat")
.stream()
.limit(7)
.map(w->w+" ")
.forEach(System.out::print);
}
}
没什么,只是觉得知道了如何构建一个流,挺好的
中间操作
跟踪和调试
// streams/Peeking.java
class Peeking {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat")
.skip(21)
.limit(4)
.map(w -> w + " ")
.peek(System.out::print)
.map(String::toUpperCase)
.peek(System.out::print)
.map(String::toLowerCase)
.forEach(System.out::print);
}
}
peek
方法接受一个Consumer
对象,其功能如下所述,就是对输入流中的每个元素进行一个操作,并且返回这个流
Returns a stream consisting of the elements of this stream, additionally performing the provided action on each element as elements are consumed from the resulting stream.
流元素排序
public class SortedComparator {
public static void main(String[] args) throws IOException {
FileToWords.stream("D:\\code\\OnJava8\\src\\chapter14\\Cheese.dat")
.skip(10)
.limit(10)
// .sorted(Comparator.reverseOrder())
.sorted((String o1, String o2) ->1)
.map(w->w+" ")
.forEach(System.out::print);
}
}
也可以用lambda表达式,String
加和不加都行,不加的话,会根据这个流的泛型类进行推断。
移除元素
下面的代码的作用是找质数,isPrime
的功能是判断一个数n
是不是质数,其中rangeClosed
方法提供的是一个包含右端点的数字流,noneMatch
方法是对流中每个元素进行判别,如果所有元素的判别都是false
,那么返回true
(顾名思义嘛,noneMatch
没有元素能match上)。而iterate
方法之前说过,是一个迭代方法,生成从2开始的整数流
// streams/Prime.java
import java.util.stream.*;
import static java.util.stream.LongStream.*;
public class Prime {
public static Boolean isPrime(long n) {
return rangeClosed(2, (long)Math.sqrt(n))
.noneMatch(i -> n % i == 0);
}
public LongStream numbers() {
return iterate(2, i -> i + 1)
.filter(Prime::isPrime);
}
public static void main(String[] args) {
new Prime().numbers()
.limit(10)
.forEach(n -> System.out.format("%d ", n));
System.out.println();
new Prime().numbers()
.skip(90)
.limit(10)
.forEach(n -> System.out.format("%d ", n));
}
}
在map中组合流
Files.lines
方法会产生一个字符串流,每个元素其实都是一行,也就是“行流”,如果简单地使用map
方法,对每行使用splitAsStream
,那生成的就是一个流的流,随后flat
就得了
// streams/FileToWords.java
import java.nio.file.*;
import java.util.stream.*;
import java.util.regex.Pattern;
public class FileToWords {
public static Stream<String> stream(String filePath) throws Exception {
return Files.lines(Paths.get(filePath))
.skip(1) // First (comment) line
.flatMap(line ->
Pattern.compile("\\W+").splitAsStream(line));
}
}
Optional类
每次我都会为Stream.<String>empty()
这种方法感到震惊
注意,空流是通过 Stream.empty() 创建的。如果你在没有任何上下文环境的情况下调用 Stream.empty(),Java 并不知道它的数据类型;这个语法解决了这个问题。如果编译器拥有了足够的上下文信息,比如:
Stream<String> s = Stream.empty();
,就可以在调用 empty() 时推断类型。
java的Optional
类和Scala里的Option
类一样啊= =
// streams/OptionalBasics.java
import java.util.*;
import java.util.stream.*;
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());
}
}
Optional对象的操作
下面的filter
方法是归属于Optional
类的。不是基本的Stream
类的filter
方法
// streams/OptionalFilter.java
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
class OptionalFilter {
static String[] elements = {
"Foo", "", "Bar", "Baz", "Bingo"
};
static Stream<String> testStream() {
return Arrays.stream(elements);
}
static void test(String descr, Predicate<String> pred) {
System.out.println(" ---( " + descr + " )---");
for(int i = 0; i <= elements.length; i++) {
System.out.println(
testStream()
.skip(i)
.findFirst()
.filter(pred));
}
}
public static void main(String[] args) {
test("true", str -> true);
test("false", str -> false);
test("str != \"\"", str -> str != "");
test("str.length() == 3", str -> str.length() == 3);
test("startsWith(\"B\")",
str -> str.startsWith("B"));
}
}
flatMap
是应用于已生成 Optional 的映射函数,所以 flatMap()
不会像 map()
那样将结果封装在 Optional 中,因此,flatMap
将提取非空 Optional
的内容并将其应用在映射函数。唯一的区别就是 flatMap()
不会把结果包装在 Optional
中。
看方法定义可以看出,public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
,该方法的映射函数必须得是生成Optional
的
Optional流
// streams/Signal.java
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class Signal {
private final String msg;
public Signal(String msg) { this.msg = msg; }
public String getMsg() { return msg; }
@Override
public String toString() {
return "Signal(" + msg + ")";
}
static Random rand = new Random(47);
public static Signal morse() {
switch(rand.nextInt(4)) {
case 1: return new Signal("dot");
case 2: return new Signal("dash");
default: return null;
}
}
public static Stream<Optional<Signal>> stream() {
return Stream.generate(Signal::morse)
.map(signal -> Optional.ofNullable(signal));
}
}
// streams/StreamOfOptionals.java
import java.util.*;
import java.util.stream.*;
public class StreamOfOptionals {
public static void main(String[] args) {
Signal.stream()
.limit(10)
.forEach(System.out::println);
System.out.println(" ---");
Signal.stream()
.limit(10)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(System.out::println);
}
}
上面两段代码主要看的是Signal.stream().limit(10).filter(Optional::isPresent) .map(Optional::get).forEach(System.out::println);
这一段,filter
方法接受的是Predicate
对象,该类是一个接口,只有一个boolean test(T t);
方法需要实现,而Optional::get
正是上一章节中讲过的非绑定方法引用,这个get
方法是个非静态方法,他没有参数。这也就是说,Optional::isPresent
会生成一个Predicate
对象,这个对象的test
方法长下面这样
boolean test(Optional t){
return t.isPresent();
}
终端操作
收集
package chapter14;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
public class TreeSetOfWords {
static final String path="D:\\code\\OnJava8\\src\\chapter14\\TreeSetOfWords.java";
public static void main(String[] args) throws IOException {
Set<String> words2=
Files.lines(Paths.get(path))
.flatMap(s-> Arrays.stream(s.split("\\W+")))
.filter(s->!s.matches("\\d+"))
.map(String::trim)
.filter(s->s.length()>2)
.limit(100)
.collect(Collectors.toCollection(TreeSet::new));
System.out.println(words2);
}
}
toCollection
的功能如下
Returns a Collector that accumulates the input elements into a new {@code Collection}, in encounter order. The {@code Collection} is created by the provided factory.
// streams/MapCollector.java
import java.util.*;
import java.util.stream.*;
class Pair {
public final Character c;
public final Integer i;
Pair(Character c, Integer i) {
this.c = c;
this.i = i;
}
public Character getC() { return c; }
public Integer getI() { return i; }
@Override
public String toString() {
return "Pair(" + c + ", " + i + ")";
}
}
class RandomPair {
Random rand = new Random(47);
// An infinite iterator of random capital letters:
Iterator<Character> capChars = rand.ints(65,91)
.mapToObj(i -> (char)i)
.iterator();
public Stream<Pair> stream() {
return rand.ints(100, 1000).distinct()
.mapToObj(i -> new Pair(capChars.next(), i));
}
}
public class MapCollector {
public static void main(String[] args) {
Map<Integer, Character> map =
new RandomPair().stream()
.limit(8)
.collect(
Collectors.toMap(Pair::getI, Pair::getC));
System.out.println(map);
}
}
可以看到RandomPair
中的stream
方法返回了一个对象流,这个流是组合多个流以生成的新的对象流。书上说使用迭代器是唯一可以完成的方法。
这个代码里的toMap
方法需要两个参数,是两个Function
对象,前者是生成key,后者生成value
// streams/SpecialCollector.java
import java.util.*;
import java.util.stream.*;
public class SpecialCollector {
public static void main(String[] args) throws Exception {
ArrayList<String> words =
FileToWords.stream("Cheese.dat")
.collect(ArrayList::new,
ArrayList::add,
ArrayList::addAll);
words.stream()
.filter(s -> s.equals("cheese"))
.forEach(System.out::println);
}
}
官方文档对这个方法介绍的听明白的
Performs a mutable reduction operation on the elements of this stream. A mutable reduction is one in which the reduced value is a mutable result container, such as an ArrayList, and elements are incorporated by updating the state of the result rather than by replacing the result. This produces a result equivalent to:
R result = supplier.get();
for (T element : this stream)
accumulator.accept(result, element);
return result;
至于第三个参数BiConsumer<R,R> combiner
,指的是这个方法可以将两个R
类型的容器结合起来。至于有啥用,我也没看明白
组合所有流元素
// streams/Reduce.java
import java.util.*;
import java.util.stream.*;
class Frobnitz {
int size;
Frobnitz(int sz) { size = sz; }
@Override
public String toString() {
return "Frobnitz(" + size + ")";
}
// Generator:
static Random rand = new Random(47);
static final int BOUND = 100;
static Frobnitz supply() {
return new Frobnitz(rand.nextInt(BOUND));
}
}
public class Reduce {
public static void main(String[] args) {
Stream.generate(Frobnitz::supply)
.limit(10)
.peek(System.out::println)
.reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1)
.ifPresent(System.out::println);
}
}
打印结果如下,其中前十个个第一个peek方法打印出来的,最后一个才是最后一个ifPresent
的打印结果。reduce
方法接受BinaryOperator<T>
对象,至于其为什么返回Optional对象,这是为了应付流为空的状况
Frobnitz(58)
Frobnitz(55)
Frobnitz(93)
Frobnitz(61)
Frobnitz(61)
Frobnitz(29)
Frobnitz(68)
Frobnitz(0)
Frobnitz(22)
Frobnitz(7)
Frobnitz(29)
这个Optional<T> reduce(BinaryOperator<T> accumulator)
做的事在文档中描述的也很清楚
Performs a reduction on the elements of this stream, using an associative accumulation function, and returns an Optional describing the reduced value, if any. This is equivalent to:
boolean foundAny = false;
T result = null;
for (T element : this stream) {
if (!foundAny) {
foundAny = true;
result = element;
}
else
result = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();
实际上Stream.generate(Frobnitz::supply)
产生的流的类型是ReferencePipeline
的,其reduce
方法如下
@Override
public final Optional<P_OUT> reduce(BinaryOperator<P_OUT> accumulator) {
return evaluate(ReduceOps.makeRef(accumulator));
}
evaluate
方法实际上是一个返回泛型类的方法final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp)
,这引起了我的疑问,返回的既然是泛型类,为什么能和Optional<P_OUT>
的签名一致呢,我做了个小实验,如下所示,发现编译器好像可以进行类型推断,testS()
返回类型为String
,那么其调用test
的时候,就认为泛型R
就是String
了
public class TestParamType {
<R> R test(R a){
return a;
}
String testS(){
return test("a");
}
}
匹配
// streams/Matching.java
// Demonstrates short-circuiting of *Match() operations
import java.util.stream.*;
import java.util.function.*;
import static streams.RandInts.*;
interface Matcher extends BiPredicate<Stream<Integer>, Predicate<Integer>> {}
public class Matching {
static void show(Matcher match, int val) {
System.out.println(
match.test(
IntStream.rangeClosed(1, 9)
.boxed()
.peek(n -> System.out.format("%d ", n)),
n -> n < val));
}
public static void main(String[] args) {
show(Stream::allMatch, 10);
show(Stream::allMatch, 4);
show(Stream::anyMatch, 2);
show(Stream::anyMatch, 0);
show(Stream::noneMatch, 5);
show(Stream::noneMatch, 0);
}
}
这段代码真的很好很好,以show(Stream::allMatch, 4);
为例,Stream::allMatch
是通过非绑定方法生成的一个Matcher
对象,这个对象里的test
方法长成如下这样
boolean test(Stream<Integer> t, Predicate<Integer> u){
return t.allMatch(u);
}
所以说test
方法返回的是allMatch
的结果,而show(Stream::allMatch, 4)
的的功能等同于下面代码
System.out.println(IntStream.rangeClosed(1, 9)
.boxed()
.peek(n -> System.out.format("%d ", n))
.allMatch(n->n<4))
其结果如下所示
1 2 3 4 false
诶我们发现,IntStream.rangeClosed(1, 9)
生成的明明是1到9的整数流,并且通过peek
进行了打印,而为什么只打印了1 2 3 4呢。个人揣测这是因为流的特性,因为流并不存储数据,而只是一个结构,上面的例子中,IntStream.rangeClosed(1, 9).boxed()
先生成一个Stream<Integer>
流,这个流就是我们要操作的流,在应用的时候,他先生成1,然后调用peek
方法进行打印,再调用allMatch
里Predicate
对象的test
方法,发现返回的true
,相安无事,继续;然后再生成一个2,重复上面的过程;直到这个流生成了4,执行peek
方法没啥事儿,但当执行到了allMatch
的时候,发现test
的返回是false
了,那么由于短路特性,这个流不会再进行后续的计算,直接执行结束,并返回allMatch
的执行结果。
信息
数字流信息
信息和数字流信息的统计值返回的都是Optional
类以及衍生的对象