《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
類以及衍生的對象