摘要:雖然目前工作環(huán)境仍然以為主,不過(guò)目前已是大勢(shì)所趨了。標(biāo)準(zhǔn)函數(shù)式接口新的包定義旨在使用的廣泛函數(shù)式接口。這一改進(jìn)使得擁有了類似于多繼承的能力。
從Java8發(fā)布到現(xiàn)在有好幾年了,而Java9也提上發(fā)布日程了(沒(méi)記錯(cuò)的話好像就是這個(gè)月2017年7月,也許會(huì)再度跳票吧,不過(guò)沒(méi)關(guān)系,穩(wěn)定大于一切,穩(wěn)定了再發(fā)布也行),現(xiàn)在才開(kāi)始去真正學(xué)習(xí),說(shuō)來(lái)也是慚愧。雖然目前工作環(huán)境仍然以Java6為主,不過(guò)Java8目前已是大勢(shì)所趨了。Java8帶來(lái)了許多令人激動(dòng)的新特性,如lambda表達(dá)式,StreamsAPI與并行集合計(jì)算,新的時(shí)間日期API(借鑒joda-time),字節(jié)碼支持保存方法參數(shù)名(對(duì)于框架開(kāi)發(fā)真的是非常贊的一個(gè)特性),Optional類解決空指針問(wèn)題(雖然Guava中早就有這個(gè)了)等。
lambda表達(dá)式語(yǔ)法:
v->System.out.println(v)
(v)->System.out.println(v)
(String v)->System.out.println(v)
(v)->{System.out.println(v);return v+1;}
Listnumbers = Arrays.asList(1, 2, 3, 4, 5, 6); numbers.forEach((Integer value) -> System.out.println(value));
注意:lambda表達(dá)式內(nèi)如果引用了外部的局部變量,那么這個(gè)局部變量必須是final的,如果不是,編譯器會(huì)自動(dòng)給加上final。比如下面這段是錯(cuò)誤的
//這是錯(cuò)誤的 int num = 2; Function函數(shù)式接口:stringConverter = (from) -> from * num; num++;//會(huì)報(bào)錯(cuò),因?yàn)榇藭r(shí)num已經(jīng)是final聲明的了 System.out.println(stringConverter.apply(3));
僅有一個(gè)抽象方法的接口(注:Java8之后接口也可以有非抽象方法,所以此處強(qiáng)調(diào)只有一個(gè)抽象方法的接口)
可選注解:@FunctionalInterface , 作用:編譯器會(huì)檢查,javadoc文檔中會(huì)特別說(shuō)明。
這里需要強(qiáng)調(diào)的是,函數(shù)式接口只能有一個(gè)抽象方法,而不是指只能有一個(gè)方法。這分兩點(diǎn)來(lái)說(shuō)明。首先,在Java 8中,接口運(yùn)行存在實(shí)例方法(見(jiàn)默認(rèn)方法一節(jié)),其次任何被java.lang.Object實(shí)現(xiàn)的方法,都不能視為抽象方法,因此,下面的NonFunc接口不是函數(shù)式接口,因?yàn)閑quals()方法在java.lang.Object中已經(jīng)實(shí)現(xiàn)。
標(biāo)準(zhǔn)函數(shù)式接口
新的 java.util.function 包定義旨在使用 lambdas 的廣泛函數(shù)式接口。這些接口分為幾大類:
Function:接受一個(gè)參數(shù),基于參數(shù)值返回結(jié)果
Predicate:接受一個(gè)參數(shù),基于參數(shù)值返回一個(gè)布爾值
BiFunction:接受兩個(gè)參數(shù),基于參數(shù)值返回結(jié)果
Supplier:不接受參數(shù),返回一個(gè)結(jié)果
Consumer:接受一個(gè)參數(shù),無(wú)結(jié)果 (void)
interface NonFunc { boolean equals(Object obj); }
同理,下面實(shí)現(xiàn)的IntHandler接口符合函數(shù)式接口要求,雖然看起來(lái)它不像,但實(shí)際上它是一個(gè)完全符合規(guī)范的函數(shù)式接口。
@FunctionalInterface public static interface IntHandler{ void handle(int i); boolean equals(Object obj); }接口默認(rèn)方法
在Java 8之前的版本,接口只能包含抽象方法,Java 8 對(duì)接口做了進(jìn)一步的增強(qiáng)。在接口中可以添加使用 default 關(guān)鍵字修飾的非抽象方法。還可以在接口中定義靜態(tài)方法。如今,接口看上去與抽象類的功能越來(lái)越類似了。這一改進(jìn)使得Java 8擁有了類似于多繼承的能力。一個(gè)對(duì)象實(shí)例,將擁有來(lái)自于多個(gè)不同接口的實(shí)例方法。
比如,對(duì)于接口IHorse,實(shí)現(xiàn)如下:
public interface IHorse{ void eat(); default void run(){ System.out.println(“hourse run”); } }
在Java 8中,使用default關(guān)鍵字,可以在接口內(nèi)定義實(shí)例方法。注意,這個(gè)方法是并非抽象方法,而是擁有特定邏輯的具體實(shí)例方法。
所有的動(dòng)物都能自由呼吸,所以,這里可以再定義一個(gè)IAnimal接口,它也包含一個(gè)默認(rèn)方法breath()。
public interface IAnimal { default void breath(){ System.out.println(“breath”); } }
騾是馬和驢的雜交物種,因此騾(Mule)可以實(shí)現(xiàn)為IHorse,同時(shí)騾也是動(dòng)物,因此有:
public class Mule implements IHorse,IAnimal{ @Override public void eat() { System.out.println(“Mule eat”); } public static void main(String[] args) { Mule m=new Mule(); m.run(); m.breath(); } }
注意上述代碼中Mule實(shí)例同時(shí)擁有來(lái)自不同接口的實(shí)現(xiàn)方法。在這Java 8之前是做不到的。從某種程度上說(shuō),這種模式可以彌補(bǔ)Java單一繼承的一些不便。但同時(shí)也要知道,它也將遇到和多繼承相同的問(wèn)題,如果IDonkey也存在一個(gè)默認(rèn)的run()方法,那么同時(shí)實(shí)現(xiàn)它們的Mule,就會(huì)不知所措,因?yàn)樗恢缿?yīng)該以哪個(gè)方法為準(zhǔn)。此時(shí),由于IHorse和IDonkey擁有相同的默認(rèn)實(shí)例方法,故編譯器會(huì)拋出一個(gè)錯(cuò)誤:Duplicate default methods named run with the parameters () and () are inherited from the types IDonkey and IHorse
接口默認(rèn)實(shí)現(xiàn)對(duì)于整個(gè)函數(shù)式編程的流式表達(dá)式非常重要。比如,大家熟悉的java.util.Comparator接口,它在JDK 1.2時(shí)就已經(jīng)被引入。在Java 8中,Comparator接口新增了若干個(gè)默認(rèn)方法,用于多個(gè)比較器的整合。其中一個(gè)常用的默認(rèn)如下:
default ComparatorthenComparing(Comparator super T> other) { Objects.requireNonNull(other); return (Comparator & Serializable) (c1, c2) -> { int res = compare(c1, c2); return (res != 0) ? res : other.compare(c1, c2); }; }
有個(gè)這個(gè)默認(rèn)方法,在進(jìn)行排序時(shí),我們就可以非常方便得進(jìn)行元素的多條件排序,比如,如下代碼構(gòu)造一個(gè)比較器,它先按照字符串長(zhǎng)度排序,繼而按照大小寫不敏感的字母順序排序。
Comparator接口靜態(tài)方法:cmp = Comparator.comparingInt(String::length) .thenComparing(String.CASE_INSENSITIVE_ORDER);
在接口中,還允許定義靜態(tài)的方法。接口中的靜態(tài)方法可以直接用接口來(lái)調(diào)用。
例如,下面接口中定義了一個(gè)靜態(tài)方法 find,該方法可以直接用 StaticFunInterface .find() 來(lái)調(diào)用。
public interface StaticFunInterface { public static int find(){ return 1; } } public class TestStaticFun { public static void main(String[] args){ //接口中定義了靜態(tài)方法 find 直接被調(diào)用 StaticFunInterface.fine(); } }
說(shuō)明:雖然我知道了default方法是為了便于集合接口(新的StreamsAPI)向后兼容而設(shè)計(jì)的,但是這個(gè)接口靜態(tài)方法暫時(shí)還沒(méi)體會(huì)其作用,可能得接觸多了才會(huì)明白吧。
方法引用靜態(tài)方法引用:ClassName::methodName
實(shí)例上的實(shí)例方法引用:instanceReference::methodName (這里還可以使用this)
超類上的實(shí)例方法引用:super::methodName
類型上的實(shí)例方法引用:ClassName::methodName
構(gòu)造方法引用:Class::new
數(shù)組構(gòu)造方法引用:TypeName[]::new
public class InstanceMethodRef { public static void main(String[] args) { Listusers=new ArrayList (); for(int i=1;i<10;i++){ users.add(new User(i,”billy”+Integer.toString(i))); } users.stream().map(User::getName).forEach(System.out::println); } }
注意幾點(diǎn):
對(duì)于第一個(gè)方法引用User::getName,表示User類的實(shí)例方法。在執(zhí)行時(shí),Java會(huì)自動(dòng)識(shí)別流中的元素(這里指User實(shí)例)是作為調(diào)用目標(biāo)還是調(diào)用方法的參數(shù)。
一般來(lái)說(shuō),如果使用的是靜態(tài)方法,或者調(diào)用目標(biāo)明確,那么流內(nèi)的元素會(huì)自動(dòng)作為參數(shù)使用。如果函數(shù)引用表示實(shí)例方法,并且不存在調(diào)用目標(biāo),那么流內(nèi)元素就會(huì)自動(dòng)作為調(diào)用目標(biāo)。
因此,如果一個(gè)類中存在同名的實(shí)例方法和靜態(tài)函數(shù),那么編譯器就會(huì)感到很困惑,因?yàn)榇藭r(shí),它不知道應(yīng)該使用哪個(gè)方法進(jìn)行調(diào)用。
打開(kāi) Collection Api可以看到多了一個(gè) stream() default 方法:
default Streamstream() { return StreamSupport.stream(spliterator(), false); }
Stream 允許以聲明方式處理集合等可以轉(zhuǎn)換為 Stream
內(nèi)部迭代 :與原有的 Iterator 不同, Stream 將迭代操作(類似 for / for-each )全部固化到了Api內(nèi)部實(shí)現(xiàn), 用戶只需傳入表達(dá)計(jì)算邏輯的 lambda 表達(dá)式(可以理解為 Supplier 、 Function 這些的 @FunctionalInterface 的實(shí)現(xiàn)), Stream 便會(huì)自動(dòng)迭代- 數(shù)據(jù)觸發(fā)計(jì)算邏輯并生成結(jié)果. 內(nèi)部迭代主要解決了兩方面的問(wèn)題: 避免集合處理時(shí)的套路和晦澀 ; 便于庫(kù)內(nèi)部實(shí)現(xiàn)的多核并行優(yōu)化 .
流水線 :很多 Stream 操作會(huì)再返回一個(gè) Stream , 這樣多個(gè)操作就可以鏈接起來(lái), 形成一個(gè)大的流水線, 使其看起來(lái)像是 對(duì)數(shù)據(jù)源進(jìn)行數(shù)據(jù)庫(kù)式查詢 , 這也就讓自動(dòng)優(yōu)化成為可能, 如 隱式并行 .
隱式并行 :如將 .stream() 替換為 .parallelStream() , Stream 則會(huì)自動(dòng)啟用Fork/Join框架, 并行執(zhí)行各條流水線, 并最終自動(dòng)將結(jié)果進(jìn)行合并.
延遲計(jì)算 :由于 Stream 大部分的操作(如 filter() 、 generate() 、 map() …)都是接受一段 lambda 表達(dá)式, 邏輯類似接口實(shí)現(xiàn)(可以看成是 回調(diào) ), 因此代碼并不是立即執(zhí)行的, 除非流水線上觸發(fā)一個(gè)終端操作, 否則中間操作不會(huì)執(zhí)行任何處理.
短路求值 :有些操作不需要處理整個(gè)流就能夠拿到結(jié)果, 很多像 anyMatch() 、 allMatch() 、 limit() , 只要找到一個(gè)元素他們的工作就可以結(jié)束, 也就沒(méi)有必要執(zhí)行后面的操作, 因此如果后面有大量耗時(shí)的操作, 此舉可大大節(jié)省性能.
Stream 構(gòu)成
一個(gè)流管道(Stream pipeline)通常由3部分構(gòu)成: 數(shù)據(jù)源(Source) -> 中間操作/轉(zhuǎn)換(Transforming) -> 終端操作/執(zhí)行(Operations) : Stream 由數(shù)據(jù)源生成, 經(jīng)由中間操作串聯(lián)起來(lái)的一條流水線的轉(zhuǎn)換, 最后由終端操作觸發(fā)執(zhí)行拿到結(jié)果.
除了前面介紹過(guò)的 collection.stream() , 流的生成方式多種多樣, 可簡(jiǎn)單概括為3類: 通用流 、 數(shù)值流 、 其他 , 其中以 通用流最為常用, 數(shù)值流是Java為 int 、 long 、 double 三種數(shù)值類型防 拆裝箱 成本所做的優(yōu)化:
A、通用流
Arrays.stream(T[] array)
Stream.empty()
Stream.generate(Supplier
Stream.iterate(T seed, UnaryOperator
Stream.of(T... values)
Stream.concat(Stream extends T> a, Stream extends T> b) 創(chuàng)建一個(gè)懶惰連接的流,其元素是第一個(gè)流的所有元素,后跟第二個(gè)流的所有元素.
StreamSupport.stream(Spliterator
B、數(shù)值流
Arrays.stream(Xxx[] array) Returns a sequential Int/Long/DoubleStream with the specified array as its source.
XxxStream.empty() Returns an empty sequential Int/Long/DoubleStream .
XxxStream.generate(XxxSupplier s) Returns an infinite sequential unordered stream where each element is generated by the provided Int/Long/DoubleSupplier .
XxxStream.iterate(Xxx seed, XxxUnaryOperator f) Returns an infinite sequential ordered Int/Long/DoubleStream like as Stream.iterate(T seed, UnaryOperator
XxxStream.of(Xxx... values) Returns a sequential ordered stream whose elements are the specified values.
XxxStream.concat(XxxStream a, XxxStream b) Creates a lazily concatenated stream whose elements are all the elements of the first stream followed by all the elements of the second stream.
Int/LongStream.range(startInclusive, endExclusive) Returns a sequential ordered Int/LongStream from startInclusive (inclusive) to endExclusive (exclusive) by an incremental step of 1.
Int/LongStream.rangeClosed(startInclusive, endInclusive) Returns a sequential ordered Int/LongStream from startInclusive (inclusive) to endInclusive (inclusive) by an incremental step of 1.
C、其他
C.1、I/O Stream
BufferedReader.lines()
C.2、File Stream
Files.lines(Path path)
Files.find(Path start, int maxDepth, BiPredicate
DirectoryStream
Files.walk(Path start, FileVisitOption... options)
C.3、Jar
JarFile.stream()
C.4、Random
Random.ints()
Random.longs()
Random.doubles()
C.5、Pattern
splitAsStream(CharSequence input) …
另外, 三種數(shù)值流之間, 以及數(shù)值流與通用流之間都可以相互轉(zhuǎn)換:
數(shù)值流轉(zhuǎn)換: doubleStream.mapToInt(DoubleToIntFunction mapper) 、 intStream.asLongStream() …
數(shù)值流轉(zhuǎn)通用流: longStream.boxed() 、 intStream.mapToObj(IntFunction extends U> mapper) …
通用流轉(zhuǎn)數(shù)值流: stream.flatMapToInt(Function super T,? extends IntStream> mapper) 、 stream.mapToDouble(ToDoubleFunction super T> mapper) …
中間操作-Stream轉(zhuǎn)換所有的中間操作都會(huì)返回另一個(gè) Stream , 這讓多個(gè)操作可以鏈接起來(lái)組成中間操作鏈, 從而形成一條流水線, 因此它的特點(diǎn)就是前面提到的 延遲執(zhí)行 : 觸發(fā)流水線上觸發(fā)一個(gè)終端操作, 否則中間操作不執(zhí)行任何處理.
filter(Predicate super T> predicate)
distinct() Returns a stream consisting of the distinct elements (according to Object.equals(Object) ) of this stream.
limit(long maxSize)
skip(long n)
sorted(Comparator super T> comparator)
map(Function super T,? extends R> mapper) Returns a stream consisting of the results of applying the given function to the elements of this stream.
flatMap(Function super T,? extends Stream extends R>> mapper) Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the - provided mapping function to each element.
peek(Consumer super T> action) 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.
這里著重講解下 flatMap()
假設(shè)我們有這樣一個(gè)字符串list: List
Stream> streamStream = strs.stream() .map(str -> Arrays.stream(str.split("")));
我們將 String 分解成 String[] 后再由 Arrays.stream() 將 String[] 映射成 Stream
StreamstringStream = strs.stream() .flatMap(str -> Arrays.stream(str.split("")));
flatMap() 把 Stream 中的層級(jí)結(jié)構(gòu)扁平化了, 將內(nèi)層 Stream 內(nèi)的元素抽取出來(lái), 最終新的 Stream 就沒(méi)有內(nèi)層 Stream 了.
可以簡(jiǎn)單概括為: flatMap() 方法讓你把一個(gè)流中的每個(gè)值都換成另一個(gè) Stream , 然后把所有的 Stream 連接起來(lái)成為一個(gè) Stream .
終端操作不僅擔(dān)負(fù)著觸發(fā)流水線執(zhí)行的任務(wù), 他還需要拿到流水線執(zhí)行的結(jié)果, 其結(jié)果為任何不是流的值.
count()
max(Comparator super T> comparator)
min(Comparator super T> comparator)
allMatch(Predicate super T> predicate)
anyMatch(Predicate super T> predicate)
noneMatch(Predicate super T> predicate)
findAny()
findFirst()
reduce(BinaryOperator
toArray()
forEach(Consumer super T> action)
forEachOrdered(Consumer super T> action) Performs an action for each element of this stream, in the encounter order of the stream if the stream has a defined encounter order.
collect(Collector super T,A,R> collector) Performs a mutable reduction operation on the elements of this stream using a Collector .
像 IntStream / LongStream / DoubleStream 還提供了 average() 、 sum() 、 summaryStatistics() 這樣的操作, 拿到一個(gè)對(duì) Stream 進(jìn)行匯總了的結(jié)果.
java.util.stream.Stream 接口繼承自 java.util.stream.BaseStream 接口, 而 BaseStream 接口也提供了很多工具方法(如將串行流轉(zhuǎn)換為并行流的 parallel() 方法)供我們使用:
S onClose(Runnable closeHandler) Returns an equivalent stream with an additional close handler .
void close()
S unordered()
Iterator
Spliterator
S sequential()
S parallel()
boolean isParallel()
demo簡(jiǎn)單點(diǎn)的:
static int [] arr={1,4,3,6,5,7,2,9}; public static void main(String[]args){ //Array.stream()方法返回了一個(gè)流對(duì)象。類似于集合或者數(shù)組,流對(duì)象也是一個(gè)對(duì)象的集合,它將給予我們遍歷處理流內(nèi)元素的功能 Arrays.stream(arr).forEach((x)->System.out.println(x)); }
復(fù)雜點(diǎn)的:
public void joiningList() { // 生成一段[0,20)序列 Listlist = IntStream.range(0, 20) .boxed() .collect(Collectors.toList()); // 將list內(nèi)的偶數(shù)提取反向排序后聚合為一個(gè)String String string = list.stream() .filter(n -> n % 2 == 0) .sorted(Comparator.comparing((Integer i) -> i).reversed()) .limit(3) .peek((i) -> System.out.println("remained: " + i)) .map(String::valueOf) .collect(Collectors.joining()); System.out.println(string); }
public class StreamLambda { private Listtransactions; @Before public void setUp() { Trader raoul = new Trader("Raoul", "Cambridge"); Trader mario = new Trader("Mario", "Milan"); Trader alan = new Trader("Alan", "Cambridge"); Trader brian = new Trader("Brian", "Cambridge"); 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) ); } @Test public void action() { // 1. 打印2011年發(fā)生的所有交易, 并按交易額排序(從低到高) transactions.stream() .filter(transaction -> transaction.getYear() == 2011) .sorted(Comparator.comparing(Transaction::getValue)) .forEachOrdered(System.out::println); // 2. 找出交易員都在哪些不同的城市工作過(guò) Set distinctCities = transactions.stream() .map(transaction -> transaction.getTrader().getCity()) .collect(Collectors.toSet()); // or .distinct().collect(Collectors.toList()) System.out.println(distinctCities); // 3. 找出所有來(lái)自于劍橋的交易員, 并按姓名排序 Trader[] traders = transactions.stream() .map(Transaction::getTrader) .filter(trader -> trader.getCity().equals("Cambridge")) .distinct() .sorted(Comparator.comparing(Trader::getName)) .toArray(Trader[]::new); System.out.println(Arrays.toString(traders)); // 4. 返回所有交易員的姓名字符串, 并按字母順序排序 String names = transactions.stream() .map(transaction -> transaction.getTrader().getName()) .distinct() .sorted(Comparator.naturalOrder()) .reduce("", (str1, str2) -> str1 + " " + str2); System.out.println(names); // 5. 返回所有交易員的姓名字母串, 并按字母順序排序 String letters = transactions.stream() .map(transaction -> transaction.getTrader().getName()) .distinct() .map(name -> name.split("")) .flatMap(Arrays::stream) .sorted() .collect(Collectors.joining()); System.out.println(letters); // 6. 有沒(méi)有交易員是在米蘭工作 boolean workMilan = transactions.stream() .anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan")); System.out.println(workMilan); // 7. 打印生活在劍橋的交易員的所有交易額總和 long sum = transactions.stream() .filter(transaction -> transaction.getTrader().getCity().equals("Cambridge")) .mapToLong(Transaction::getValue) .sum(); System.out.println(sum); // 8. 所有交易中,最高的交易額是多少 OptionalInt max = transactions.stream() .mapToInt(Transaction::getValue) .max(); // or transactions.stream().map(Transaction::getValue).max(Comparator.naturalOrder()); System.out.println(max.orElse(0)); // 9. 找到交易額最小的交易 Optional min = transactions.stream() .min(Comparator.comparingInt(Transaction::getValue)); System.out.println(min.orElseThrow(IllegalArgumentException::new)); } }
在Java8中,可以在接口不變的情況下,將流改為并行流。這樣,就可以很自然地使用多線程進(jìn)行集合中的數(shù)據(jù)處理。
并行流與并行排序demo:我們希望可以統(tǒng)計(jì)一個(gè)1~1000000內(nèi)所有的質(zhì)數(shù)的數(shù)量。
IntStream.range(1,1000000).parallel().filter(PrimeUtil::isPrime).count();
從集合得到并行流
List進(jìn)階:自己生成流ss =new ArrayList (); ... double ave = ss.parallelStream().mapToInt(s->s.score).average().getAsDouble(); int[]arr = new int [10000000]; Arrarys.parallelSort(arr);
1、Stream.generate
通過(guò)實(shí)現(xiàn) Supplier 接口,你可以自己來(lái)控制流的生成。這種情形通常用于隨機(jī)數(shù)、常量的 Stream,或者需要前后元素間維持著某種狀態(tài)信息的 Stream。把 Supplier 實(shí)例傳遞給 Stream.generate() 生成的 Stream,默認(rèn)是串行(相對(duì) parallel 而言)但無(wú)序的(相對(duì) ordered 而言)。由于它是無(wú)限的,在管道中,必須利用 limit 之類的操作限制 Stream 大小。
//生成 10 個(gè)隨機(jī)整數(shù) Random seed = new Random(); Supplierrandom = seed::nextInt; Stream.generate(random).limit(10).forEach(System.out::println); //Another way IntStream.generate(() -> (int) (System.nanoTime() % 100)). limit(10).forEach(System.out::println);
注意幾個(gè)關(guān)鍵詞:默認(rèn)串行、無(wú)序、無(wú)限(需要進(jìn)行短路求值操作如limit)。
2、Stream.iterate
iterate 跟 reduce 操作很像,接受一個(gè)種子值,和一個(gè) UnaryOperator(例如 f)。然后種子值成為 Stream 的第一個(gè)元素,f(seed) 為第二個(gè),f(f(seed)) 第三個(gè),以此類推。
//生成一個(gè)等差數(shù)列 0 3 6 9 12 15 18 21 24 27 Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));.
與 Stream.generate 相仿,在 iterate 時(shí)候管道必須有 limit 這樣的操作來(lái)限制 Stream 大小。
進(jìn)階:用 Collectors 來(lái)進(jìn)行 reduction 操作java.util.stream.Collectors 類的主要作用就是輔助進(jìn)行各類有用的 reduction 操作,例如轉(zhuǎn)變輸出為 Collection,把 Stream 元素進(jìn)行歸組。
1、groupingBy/partitioningBy
//按照年齡歸組 MapStream總結(jié)> personGroups = Stream.generate(new PersonSupplier()). limit(100). collect(Collectors.groupingBy(Person::getAge)); Iterator it = personGroups.entrySet().iterator(); while (it.hasNext()) { Map.Entry > persons = (Map.Entry) it.next(); System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size()); } //按照未成年人和成年人歸組 Map > children = Stream.generate(new PersonSupplier()). limit(100). collect(Collectors.partitioningBy(p -> p.getAge() < 18)); System.out.println("Children number: " + children.get(true).size()); System.out.println("Adult number: " + children.get(false).size()); //在使用條件“年齡小于 18”進(jìn)行分組后可以看到,不到 18 歲的未成年人是一組,成年人是另外一組。partitioningBy 其實(shí)是一種特殊的 groupingBy,它依照條件測(cè)試的是否兩種結(jié)果來(lái)構(gòu)造返回的數(shù)據(jù)結(jié)構(gòu),get(true) 和 get(false) 能即為全部的元素對(duì)象。
其實(shí)這里很多觀念和Spark的RDD操作很相似。
總之,Stream 的特性可以歸納為:
不是數(shù)據(jù)結(jié)構(gòu),它沒(méi)有內(nèi)部存儲(chǔ),它只是用操作管道從 source(數(shù)據(jù)結(jié)構(gòu)、數(shù)組、generator function、IO channel)抓取數(shù)據(jù)。
它也絕不修改自己所封裝的底層數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)。例如 Stream 的 filter 操作會(huì)產(chǎn)生一個(gè)不包含被過(guò)濾元素的新 Stream,而不是從 source 刪除那些元素。
所有 Stream 的操作必須以 lambda 表達(dá)式為參數(shù)
不支持索引訪問(wèn),你可以請(qǐng)求第一個(gè)元素,但無(wú)法請(qǐng)求第二個(gè),第三個(gè),或最后一個(gè)。
很容易生成數(shù)組或者 List
惰性化,Intermediate 操作永遠(yuǎn)是惰性化的。
很多 Stream 操作是向后延遲的,一直到它弄清楚了最后需要多少數(shù)據(jù)才會(huì)開(kāi)始。
并行能力,當(dāng)一個(gè) Stream 是并行化的,就不需要再寫多線程代碼,所有對(duì)它的操作會(huì)自動(dòng)并行進(jìn)行的。
可以是無(wú)限的,集合有固定大小,Stream 則不必。limit(n) 和 findFirst() 這類的 short-circuiting 操作可以對(duì)無(wú)限的 Stream 進(jìn)行運(yùn)算并很快完成。
注解的更新對(duì)于注解,Java 8 主要有兩點(diǎn)改進(jìn):類型注解和重復(fù)注解。
Java 8 的類型注解擴(kuò)展了注解使用的范圍。在該版本之前,注解只能是在聲明的地方使用。現(xiàn)在幾乎可以為任何東西添加注解:局部變量、類與接口,就連方法的異常也能添加注解。新增的兩個(gè)注釋的程序元素類型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER 用來(lái)描述注解的新場(chǎng)合。
ElementType.TYPE_PARAMETER 表示該注解能寫在類型變量的聲明語(yǔ)句中。而 ElementType.TYPE_USE 表示該注解能寫在使用類型的任何語(yǔ)句中(例如聲明語(yǔ)句、泛型和強(qiáng)制轉(zhuǎn)換語(yǔ)句中的類型)。
對(duì)類型注解的支持,增強(qiáng)了通過(guò)靜態(tài)分析工具發(fā)現(xiàn)錯(cuò)誤的能力。原先只能在運(yùn)行時(shí)發(fā)現(xiàn)的問(wèn)題可以提前在編譯的時(shí)候被排查出來(lái)。Java 8 本身雖然沒(méi)有自帶類型檢測(cè)的框架,但可以通過(guò)使用 Checker Framework 這樣的第三方工具,自動(dòng)檢查和確認(rèn)軟件的缺陷,提高生產(chǎn)效率。
在Java8之前使用注解的一個(gè)限制是相同的注解在同一位置只能聲明一次,不能聲明多次。Java 8 引入了重復(fù)注解機(jī)制,這樣相同的注解可以在同一地方聲明多次。重復(fù)注解機(jī)制本身必須用 @Repeatable 注解。
IO/NIO 的改進(jìn)增加了一些新的 IO/NIO 方法,使用這些方法可以從文件或者輸入流中獲取流(java.util.stream.Stream),通過(guò)對(duì)流的操作,可以簡(jiǎn)化文本行處理、目錄遍歷和文件查找。
新增的 API 如下:
BufferedReader.line(): 返回文本行的流 Stream
File.lines(Path, Charset):返回文本行的流 Stream
File.list(Path): 遍歷當(dāng)前目錄下的文件和目錄
File.walk(Path, int, FileVisitOption): 遍歷某一個(gè)目錄下的所有文件和指定深度的子目錄
File.find(Path, int, BiPredicate, FileVisitOption... ): 查找相應(yīng)的文件
下面就是用流式操作列出當(dāng)前目錄下的所有文件和目錄:
Files.list(new File(".").toPath()) .forEach(System.out::println);新的Date/Time API
Java 的日期與時(shí)間 API 問(wèn)題由來(lái)已久,Java 8 之前的版本中關(guān)于時(shí)間、日期及其他時(shí)間日期格式化類由于線程安全、重量級(jí)、序列化成本高等問(wèn)題而飽受批評(píng)。Java 8 吸收了 Joda-Time 的精華,以一個(gè)新的開(kāi)始為 Java 創(chuàng)建優(yōu)秀的 API。新的 java.time 中包含了所有關(guān)于時(shí)鐘(Clock),本地日期(LocalDate)、本地時(shí)間(LocalTime)、本地日期時(shí)間(LocalDateTime)、時(shí)區(qū)(ZonedDateTime)和持續(xù)時(shí)間(Duration)的類。歷史悠久的 Date 類新增了 toInstant() 方法,用于把 Date 轉(zhuǎn)換成新的表示形式。這些新增的本地化時(shí)間日期 API 大大簡(jiǎn)化了了日期時(shí)間和本地化的管理。
Java日期/時(shí)間API包含以下相應(yīng)的包。
java.time包:這是新的Java日期/時(shí)間API的基礎(chǔ)包,所有的主要基礎(chǔ)類都是這個(gè)包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有這些類都是不可變的和線程安全的,在絕大多數(shù)情況下,這些類能夠有效地處理一些公共的需求。
java.time.chrono包:這個(gè)包為非ISO的日歷系統(tǒng)定義了一些泛化的API,我們可以擴(kuò)展AbstractChronology類來(lái)創(chuàng)建自己的日歷系統(tǒng)。
java.time.format包:這個(gè)包包含能夠格式化和解析日期時(shí)間對(duì)象的類,在絕大多數(shù)情況下,我們不應(yīng)該直接使用它們,因?yàn)?b>java.time包中相應(yīng)的類已經(jīng)提供了格式化和解析的方法。
java.time.temporal包:這個(gè)包包含一些時(shí)態(tài)對(duì)象,我們可以用其找出關(guān)于日期/時(shí)間對(duì)象的某個(gè)特定日期或時(shí)間,比如說(shuō),可以找到某月的第一天或最后一天。你可以非常容易地認(rèn)出這些方法,因?yàn)樗鼈兌季哂小皐ithXXX”的格式。
java.time.zone包:這個(gè)包包含支持不同時(shí)區(qū)以及相關(guān)規(guī)則的類。
例如,下面是對(duì) LocalDate,LocalTime 的簡(jiǎn)單應(yīng)用:
//LocalDate只保存日期系統(tǒng)的日期部分,有時(shí)區(qū)信息,LocalTime只保存時(shí)間部分,沒(méi)有時(shí)區(qū)信息。LocalDate和LocalTime都可以從Clock對(duì)象創(chuàng)建。 //LocalDate LocalDate localDate = LocalDate.now(); //獲取本地日期 localDate = LocalDate.ofYearDay(2014, 200); // 獲得 2014 年的第 200 天 System.out.println(localDate.toString());//輸出:2014-07-19 localDate = LocalDate.of(2014, Month.SEPTEMBER, 10); //2014 年 9 月 10 日 System.out.println(localDate.toString());//輸出:2014-09-10 //LocalTime LocalTime localTime = LocalTime.now(); //獲取當(dāng)前時(shí)間 System.out.println(localTime.toString());//輸出當(dāng)前時(shí)間 localTime = LocalTime.of(10, 20, 50);//獲得 10:20:50 的時(shí)間點(diǎn) System.out.println(localTime.toString());//輸出: 10:20:50 //Clock 時(shí)鐘,Clock類可以替換 System.currentTimeMillis() 和 TimeZone.getDefault(). 如:Clock.systemDefaultZone().millis() Clock clock = Clock.systemDefaultZone();//獲取系統(tǒng)默認(rèn)時(shí)區(qū) (當(dāng)前瞬時(shí)時(shí)間 ) long millis = clock.millis();// //LocalDateTime類合并了LocalDate和LocalTime,它保存有ISO-8601日期系統(tǒng)的日期和時(shí)間,但是沒(méi)有時(shí)區(qū)信息。 final LocalDateTime datetime = LocalDateTime.now(); final LocalDateTime datetimeFromClock = LocalDateTime.now( clock ); System.out.println( datetime ); System.out.println( datetimeFromClock ); //如果您需要一個(gè)類持有日期時(shí)間和時(shí)區(qū)信息,可以使用ZonedDateTime,它保存有ISO-8601日期系統(tǒng)的日期和時(shí)間,而且有時(shí)區(qū)信息。 final ZonedDateTime zonedDatetime = ZonedDateTime.now(); final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock ); final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) ); System.out.println( zonedDatetime ); System.out.println( zonedDatetimeFromClock ); System.out.println( zonedDatetimeFromZone );
Duration類,Duration持有的時(shí)間精確到納秒。它讓我們很容易計(jì)算兩個(gè)日期中間的差異。讓我們來(lái)看一下:
// Get duration between two dates final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 ); final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 ); final Duration duration = Duration.between( from, to ); System.out.println( "Duration in days: " + duration.toDays() ); System.out.println( "Duration in hours: " + duration.toHours() );
上面的例子計(jì)算了兩個(gè)日期(2014年4月16日和2014年5月16日)之間的持續(xù)時(shí)間(基于天數(shù)和小時(shí))
日期API操作
//日期算術(shù)操作,多數(shù)日期/時(shí)間API類都實(shí)現(xiàn)了一系列工具方法,如:加/減天數(shù)、周數(shù)、月份數(shù),等等。還有其他的工具方法能夠使用TemporalAdjuster調(diào)整日期,并計(jì)算兩個(gè)日期間的周期。 LocalDate today = LocalDate.now(); //Get the Year, check if it"s leap year System.out.println("Year "+today.getYear()+" is Leap Year? "+today.isLeapYear()); //Compare two LocalDate for before and after System.out.println("Today is before 01/01/2015? "+today.isBefore(LocalDate.of(2015,1,1))); //Create LocalDateTime from LocalDate System.out.println("Current Time="+today.atTime(LocalTime.now())); //plus and minus operations System.out.println("10 days after today will be "+today.plusDays(10)); System.out.println("3 weeks after today will be "+today.plusWeeks(3)); System.out.println("20 months after today will be "+today.plusMonths(20)); System.out.println("10 days before today will be "+today.minusDays(10)); System.out.println("3 weeks before today will be "+today.minusWeeks(3)); System.out.println("20 months before today will be "+today.minusMonths(20)); //Temporal adjusters for adjusting the dates System.out.println("First date of this month= "+today.with(TemporalAdjusters.firstDayOfMonth())); LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear()); System.out.println("Last date of this year= "+lastDayOfYear); Period period = today.until(lastDayOfYear); System.out.println("Period Format= "+period); System.out.println("Months remaining in the year= "+period.getMonths());
解析和格式化:將一個(gè)日期格式轉(zhuǎn)換為不同的格式,之后再解析一個(gè)字符串,得到日期時(shí)間對(duì)象
//Format examples LocalDate date = LocalDate.now(); //default format System.out.println("Default format of LocalDate="+date); //specific format System.out.println(date.format(DateTimeFormatter.ofPattern("d::MMM::uuuu"))); System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE)); LocalDateTime dateTime = LocalDateTime.now(); //default format System.out.println("Default format of LocalDateTime="+dateTime); //specific format System.out.println(dateTime.format(DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss"))); System.out.println(dateTime.format(DateTimeFormatter.BASIC_ISO_DATE)); Instant timestamp = Instant.now(); //default format System.out.println("Default format of Instant="+timestamp); //Parse examples LocalDateTime dt = LocalDateTime.parse("27::Apr::2014 21::39::48", DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss")); System.out.println("Default format after parsing = "+dt);
舊的日期時(shí)間支持轉(zhuǎn)換:
//Date to Instant Instant timestamp = new Date().toInstant(); //Now we can convert Instant to LocalDateTime or other similar classes LocalDateTime date = LocalDateTime.ofInstant(timestamp, ZoneId.of(ZoneId.SHORT_IDS.get("PST"))); System.out.println("Date = "+date); //Calendar to Instant Instant time = Calendar.getInstance().toInstant(); System.out.println(time); //TimeZone to ZoneId ZoneId defaultZone = TimeZone.getDefault().toZoneId(); System.out.println(defaultZone); //ZonedDateTime from specific Calendar ZonedDateTime gregorianCalendarDateTime = new GregorianCalendar().toZonedDateTime(); System.out.println(gregorianCalendarDateTime); //Date API to Legacy classes Date dt = Date.from(Instant.now()); System.out.println(dt); TimeZone tz = TimeZone.getTimeZone(defaultZone); System.out.println(tz); GregorianCalendar gc = GregorianCalendar.from(gregorianCalendarDateTime); System.out.println(gc);Java8 CompletableFuture強(qiáng)大的函數(shù)式異步編程輔助類
Future是Java 5添加的類,用來(lái)描述一個(gè)異步計(jì)算的結(jié)果。你可以使用isDone方法檢查計(jì)算是否完成,或者使用get阻塞住調(diào)用線程,直到計(jì)算完成返回結(jié)果,你也可以使用cancel方法停止任務(wù)的執(zhí)行。
雖然Future以及相關(guān)使用方法提供了異步執(zhí)行任務(wù)的能力,但是對(duì)于結(jié)果的獲取卻是很不方便,只能通過(guò)阻塞或者輪詢的方式得到任務(wù)的結(jié)果。阻塞的方式顯然和我們的異步編程的初衷相違背,輪詢的方式又會(huì)耗費(fèi)無(wú)謂的CPU資源,而且也不能及時(shí)地得到計(jì)算結(jié)果,為什么不能用觀察者設(shè)計(jì)模式當(dāng)計(jì)算結(jié)果完成及時(shí)通知監(jiān)聽(tīng)者呢?
其實(shí)Google guava早就提供了通用的擴(kuò)展Future:ListenableFuture、SettableFuture 以及輔助類Futures等,方便異步編程。
final String name = ...; inFlight.add(name); ListenableFuturefuture = service.query(name); future.addListener(new Runnable() { public void run() { processedCount.incrementAndGet(); inFlight.remove(name); lastProcessed.set(name); logger.info("Done with {0}", name); } }, executor);
在Java 8中, 新增加了一個(gè)包含50個(gè)方法左右的類: CompletableFuture,提供了非常強(qiáng)大的Future的擴(kuò)展功能,可以幫助我們簡(jiǎn)化異步編程的復(fù)雜性,提供了函數(shù)式編程的能力,可以通過(guò)回調(diào)的方式處理計(jì)算結(jié)果,并且提供了轉(zhuǎn)換和組合CompletableFuture的方法。
CompletableFuture類實(shí)現(xiàn)了CompletionStage和Future接口,所以你還是可以像以前一樣通過(guò)阻塞或者輪詢的方式獲得結(jié)果,盡管這種方式不推薦使用。
盡管Future可以代表在另外的線程中執(zhí)行的一段異步代碼,但是你還是可以在本身線程中執(zhí)行:
public class BasicMain { public static CompletableFuturecompute() { final CompletableFuture future = new CompletableFuture<>(); return future; } public static void main(String[] args) throws Exception { final CompletableFuture f = compute(); class Client extends Thread { CompletableFuture f; Client(String threadName, CompletableFuture f) { super(threadName); this.f = f; } @Override public void run() { try { System.out.println(this.getName() + ": " + f.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } new Client("Client1", f).start(); new Client("Client2", f).start(); System.out.println("waiting"); //上面的代碼中future沒(méi)有關(guān)聯(lián)任何的Callback、線程池、異步任務(wù)等,如果客戶端調(diào)用future.get就會(huì)一致傻等下去。除非我們顯式指定complete f.complete(100); //f.completeExceptionally(new Exception()); System.in.read(); } }
CompletableFuture.complete()、CompletableFuture.completeExceptionally只能被調(diào)用一次。但是我們有兩個(gè)后門方法可以重設(shè)這個(gè)值:obtrudeValue、obtrudeException,但是使用的時(shí)候要小心,因?yàn)閏omplete已經(jīng)觸發(fā)了客戶端,有可能導(dǎo)致客戶端會(huì)得到不期望的結(jié)果。
1、創(chuàng)建CompletableFuture對(duì)象。
public static CompletableFuture completedFuture(U value)
public static CompletableFuture
public static CompletableFuture
public static CompletableFuture supplyAsync(Supplier supplier)
public static CompletableFuture supplyAsync(Supplier supplier, Executor executor)
注意:以Async結(jié)尾并且沒(méi)有指定Executor的方法會(huì)使用ForkJoinPool.commonPool()作為它的線程池執(zhí)行異步代碼。因?yàn)榉椒ǖ膮?shù)類型都是函數(shù)式接口,所以可以使用lambda表達(dá)式實(shí)現(xiàn)異步任務(wù)
2、計(jì)算結(jié)果完成時(shí)的處理
當(dāng)CompletableFuture的計(jì)算結(jié)果完成,或者拋出異常的時(shí)候,我們可以執(zhí)行特定的Action。主要是下面的方法:
public CompletableFuture
public CompletableFuture
public CompletableFuture
public CompletableFuture
注意:方法不以Async結(jié)尾,意味著Action使用相同的線程執(zhí)行,而Async可能會(huì)使用其它的線程去執(zhí)行(如果使用相同的線程池,也可能會(huì)被同一個(gè)線程選中執(zhí)行)
public class Main { private static Random rand = new Random(); private static long t = System.currentTimeMillis(); static int getMoreData() { System.out.println("begin to start compute"); try { Thread.sleep(10000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("end to start compute. passed " + (System.currentTimeMillis() - t)/1000 + " seconds"); return rand.nextInt(1000); } public static void main(String[] args) throws Exception { CompletableFuturefuture = CompletableFuture.supplyAsync(Main::getMoreData); Future f = future.whenComplete((v, e) -> { System.out.println(v); System.out.println(e); }); System.out.println(f.get()); System.in.read(); } }
下面一組方法雖然也返回CompletableFuture對(duì)象,但是對(duì)象的值和原來(lái)的CompletableFuture計(jì)算的值不同。當(dāng)原先的CompletableFuture的值計(jì)算完成或者拋出異常的時(shí)候,會(huì)觸發(fā)這個(gè)CompletableFuture對(duì)象的計(jì)算,結(jié)果由BiFunction參數(shù)計(jì)算而得。
因此這組方法兼有whenComplete和轉(zhuǎn)換的兩個(gè)功能。
public CompletableFuture handle(BiFunction super T,Throwable,? extends U> fn)
public CompletableFuture handleAsync(BiFunction super T,Throwable,? extends U> fn)
public CompletableFuture handleAsync(BiFunction super T,Throwable,? extends U> fn, Executor executor)
同樣,不以Async結(jié)尾的方法由原來(lái)的線程計(jì)算,以Async結(jié)尾的方法由默認(rèn)的線程池ForkJoinPool.commonPool()或者指定的線程池executor運(yùn)行。
3、轉(zhuǎn)換
CompletableFuture可以作為monad(單子)和functor。由于回調(diào)風(fēng)格的實(shí)現(xiàn),我們不必因?yàn)榈却粋€(gè)計(jì)算完成而阻塞著調(diào)用線程,而是告訴CompletableFuture當(dāng)計(jì)算完成的時(shí)候請(qǐng)執(zhí)行某個(gè)function。而且我們還可以將這些操作串聯(lián)起來(lái),或者將CompletableFuture組合起來(lái)。
public CompletableFuture thenApply(Function super T,? extends U> fn)
public CompletableFuture thenApplyAsync(Function super T,? extends U> fn)
public CompletableFuture thenApplyAsync(Function super T,? extends U> fn, Executor executor)
這一組函數(shù)的功能是當(dāng)原來(lái)的CompletableFuture計(jì)算完后,將結(jié)果傳遞給函數(shù)fn,將fn的結(jié)果作為新的CompletableFuture計(jì)算結(jié)果。因此它的功能相當(dāng)于將CompletableFuture
它們與handle方法的區(qū)別在于handle方法會(huì)處理正常計(jì)算值和異常,因此它可以屏蔽異常,避免異常繼續(xù)拋出。而thenApply方法只是用來(lái)處理正常值,因此一旦有異常就會(huì)拋出。
CompletableFuturefuture = CompletableFuture.supplyAsync(() -> { return 100; }); CompletableFuture f = future.thenApplyAsync(i -> i * 10).thenApply(i -> i.toString()); System.out.println(f.get()); //"1000"
需要注意的是,這些轉(zhuǎn)換并不是馬上執(zhí)行的,也不會(huì)阻塞,而是在前一個(gè)stage完成后繼續(xù)執(zhí)行。
4、純消費(fèi)(執(zhí)行Action)
上面的方法是當(dāng)計(jì)算完成的時(shí)候,會(huì)生成新的計(jì)算結(jié)果(thenApply, handle),或者返回同樣的計(jì)算結(jié)果whenComplete,CompletableFuture還提供了一種處理結(jié)果的方法,只對(duì)結(jié)果執(zhí)行Action,而不返回新的計(jì)算值,因此計(jì)算值為Void:
public CompletableFuture
public CompletableFuture
public CompletableFuture
看它的參數(shù)類型也就明白了,它們是函數(shù)式接口Consumer,這個(gè)接口只有輸入,沒(méi)有返回值。
CompletableFuturefuture = CompletableFuture.supplyAsync(() -> { return 100; }); CompletableFuture f = future.thenAccept(System.out::println); System.out.println(f.get());
thenAcceptBoth以及相關(guān)方法提供了類似的功能,當(dāng)兩個(gè)CompletionStage都正常完成計(jì)算的時(shí)候,就會(huì)執(zhí)行提供的action,它用來(lái)組合另外一個(gè)異步的結(jié)果。
runAfterBoth是當(dāng)兩個(gè)CompletionStage都正常完成計(jì)算的時(shí)候,執(zhí)行一個(gè)Runnable,這個(gè)Runnable并不使用計(jì)算的結(jié)果。
public CompletableFuture
public CompletableFuture
public CompletableFuture
public CompletableFuture
例子如下:
CompletableFuturefuture = CompletableFuture.supplyAsync(() -> { return 100; }); CompletableFuture f = future.thenAcceptBoth(CompletableFuture.completedFuture(10), (x, y) -> System.out.println(x * y)); System.out.println(f.get());
更徹底地,下面一組方法當(dāng)計(jì)算完成的時(shí)候會(huì)執(zhí)行一個(gè)Runnable,與thenAccept不同,Runnable并不使用CompletableFuture計(jì)算的結(jié)果。
public CompletableFuture
public CompletableFuture
public CompletableFuture
CompletableFuturefuture = CompletableFuture.supplyAsync(() -> { return 100; }); CompletableFuture f = future.thenRun(() -> System.out.println("finished")); System.out.println(f.get());
因此,你可以根據(jù)方法的參數(shù)的類型來(lái)加速你的記憶。Runnable類型的參數(shù)會(huì)忽略計(jì)算的結(jié)果,Consumer是純消費(fèi)計(jì)算結(jié)果,BiConsumer會(huì)組合另外一個(gè)CompletionStage純消費(fèi),F(xiàn)unction會(huì)對(duì)計(jì)算結(jié)果做轉(zhuǎn)換,BiFunction會(huì)組合另外一個(gè)CompletionStage的計(jì)算結(jié)果做轉(zhuǎn)換。
5、組合compose
public CompletableFuture thenCompose(Function super T,? extends CompletionStage> fn)
public CompletableFuture thenComposeAsync(Function super T,? extends CompletionStage> fn)
public CompletableFuture thenComposeAsync(Function super T,? extends CompletionStage> fn, Executor executor)
這一組方法接受一個(gè)Function作為參數(shù),這個(gè)Function的輸入是當(dāng)前的CompletableFuture的計(jì)算值,返回結(jié)果將是一個(gè)新的CmpletableFuture,這個(gè)新的CompletableFuture會(huì)組合原來(lái)的CompletableFuture和函數(shù)返回的CompletableFuture。
public CompletableFuture
public CompletableFuture
public CompletableFuture
兩個(gè)CompletionStage是并行執(zhí)行的,它們之間并沒(méi)有先后依賴順序,other并不會(huì)等待先前的CompletableFuture執(zhí)行完畢后再執(zhí)行。
6、Either
thenAcceptBoth和runAfterBoth是當(dāng)兩個(gè)CompletableFuture都計(jì)算完成,而我們下面要了解的方法是當(dāng)任意一個(gè)CompletableFuture計(jì)算完成的時(shí)候就會(huì)執(zhí)行。
public CompletableFuture
public CompletableFuture
public CompletableFuture
public CompletableFuture applyToEither(CompletionStage extends T> other, Function super T,U> fn)
public CompletableFuture applyToEitherAsync(CompletionStage extends T> other, Function super T,U> fn)
public CompletableFuture applyToEitherAsync(CompletionStage extends T> other, Function super T,U> fn, Executor executor)
下面這個(gè)例子有時(shí)會(huì)輸出100,有時(shí)候會(huì)輸出200,哪個(gè)Future先完成就會(huì)根據(jù)它的結(jié)果計(jì)算。
Random rand = new Random(); CompletableFuturefuture = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(10000 + rand.nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } return 100; }); CompletableFuture future2 = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(10000 + rand.nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } return 200; }); CompletableFuture f = future.applyToEither(future2,i -> i.toString());
7、輔助方法 allOf 和 anyOf
用來(lái)組合多個(gè)CompletableFuture。
public static CompletableFuture
public static CompletableFuture
anyOf方法是當(dāng)任意一個(gè)CompletableFuture執(zhí)行完后就會(huì)執(zhí)行計(jì)算,計(jì)算的結(jié)果相同。
8、更進(jìn)一步
如果你用過(guò)Guava的Future類,你就會(huì)知道它的Futures輔助類提供了很多便利方法,用來(lái)處理多個(gè)Future,而不像Java的CompletableFuture,只提供了allOf、anyOf兩個(gè)方法。
比如有這樣一個(gè)需求,將多個(gè)CompletableFuture組合成一個(gè)CompletableFuture,這個(gè)組合后的CompletableFuture的計(jì)算結(jié)果是個(gè)List,它包含前面所有的CompletableFuture的計(jì)算結(jié)果,guava的Futures.allAsList可以實(shí)現(xiàn)這樣的功能.
但是對(duì)于java CompletableFuture,我們需要一些輔助方法:
public staticCompletableFuture > sequence(List
> futures) { CompletableFuture allDoneFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])); return allDoneFuture.thenApply(v -> futures.stream().map(CompletableFuture::join).collect(Collectors. toList())); } public static CompletableFuture > sequence(Stream > futures) { List > futureList = futures.filter(f -> f != null).collect(Collectors.toList()); return sequence(futureList); }
Java Future轉(zhuǎn)CompletableFuture:
public staticCompletableFuture toCompletable(Future future, Executor executor) { return CompletableFuture.supplyAsync(() -> { try { return future.get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } }, executor); }
github有多個(gè)項(xiàng)目可以實(shí)現(xiàn)Java CompletableFuture與其它Future (如Guava ListenableFuture)之間的轉(zhuǎn)換,如spotify/futures-extra、future-converter、scala/scala-java8-compat 等。
其他 Java 8 Optional類大家可能都有這樣的經(jīng)歷:調(diào)用一個(gè)方法得到了返回值卻不能直接將返回值作為參數(shù)去調(diào)用別的方法。我們首先要判斷這個(gè)返回值是否為null,只有在非空的前提下才能將其作為其他方法的參數(shù)。
Java 8引入了一個(gè)新的Optional類。Optional類的Javadoc描述如下:這是一個(gè)可以為null的容器對(duì)象。如果值存在則isPresent()方法會(huì)返回true,調(diào)用get()方法會(huì)返回該對(duì)象。
1、of:為非null的值創(chuàng)建一個(gè)Optional。需要注意的是,創(chuàng)建對(duì)象時(shí)傳入的參數(shù)不能為null。如果傳入?yún)?shù)為null,則拋出NullPointerException
//調(diào)用工廠方法創(chuàng)建Optional實(shí)例 Optionalname = Optional.of("Sanaulla"); //錯(cuò)誤寫法,傳入?yún)?shù)為null,拋出NullPointerException. Optional someNull = Optional.of(null);
2、ofNullable:為指定的值創(chuàng)建一個(gè)Optional,如果指定的值為null,則返回一個(gè)空的Optional。
ofNullable與of方法相似,唯一的區(qū)別是可以接受參數(shù)為null的情況
3、isPresent:如果值存在返回true,否則返回false。
4、get:如果Optional有值則將其返回,否則拋出NoSuchElementException。
5、ifPresent:如果Optional實(shí)例有值則為其調(diào)用consumer,否則不做處理
要理解ifPresent方法,首先需要了解Consumer類。簡(jiǎn)答地說(shuō),Consumer類包含一個(gè)抽象方法。該抽象方法對(duì)傳入的值進(jìn)行處理,但沒(méi)有返回值。Java8支持不用接口直接通過(guò)lambda表達(dá)式傳入?yún)?shù)。
//ifPresent方法接受lambda表達(dá)式作為參數(shù)。 //lambda表達(dá)式對(duì)Optional的值調(diào)用consumer進(jìn)行處理。 name.ifPresent((value) -> { System.out.println("The length of the value is: " + value.length()); });
6、orElse:如果有值則將其返回,否則返回orElse方法傳入的參數(shù)。
7、orElseGet:orElseGet與orElse方法類似,區(qū)別在于得到的默認(rèn)值。orElse方法將傳入的字符串作為默認(rèn)值,orElseGet方法可以接受Supplier接口的實(shí)現(xiàn)用來(lái)生成默認(rèn)值。示例如下:
//orElseGet與orElse方法類似,區(qū)別在于orElse傳入的是默認(rèn)值, //orElseGet可以接受一個(gè)lambda表達(dá)式生成默認(rèn)值。 //輸出:Default Value System.out.println(empty.orElseGet(() -> "Default Value")); //輸出:Sanaulla System.out.println(name.orElseGet(() -> "Default Value"));
8、orElseThrow:如果有值則將其返回,否則拋出supplier接口創(chuàng)建的異常。
在orElseGet方法中,我們傳入一個(gè)Supplier接口。然而,在orElseThrow中我們可以傳入一個(gè)lambda表達(dá)式或方法,如果值不存在來(lái)拋出異常。示例如下:
try { //orElseThrow與orElse方法類似。與返回默認(rèn)值不同, //orElseThrow會(huì)拋出lambda表達(dá)式或方法生成的異常 empty.orElseThrow(ValueAbsentException::new); } catch (Throwable ex) { //輸出: No value present in the Optional instance System.out.println(ex.getMessage()); }
9、map:如果有值,則對(duì)其執(zhí)行調(diào)用mapping函數(shù)得到返回值。如果返回值不為null,則創(chuàng)建包含mapping返回值的Optional作為map方法返回值,否則返回空Optional。
map方法用來(lái)對(duì)Optional實(shí)例的值執(zhí)行一系列操作。
//map方法執(zhí)行傳入的lambda表達(dá)式參數(shù)對(duì)Optional實(shí)例的值進(jìn)行修改。 //為lambda表達(dá)式的返回值創(chuàng)建新的Optional實(shí)例作為map方法的返回值。 OptionalupperName = name.map((value) -> value.toUpperCase()); System.out.println(upperName.orElse("No value found"));
10、flatMap:如果有值,為其執(zhí)行mapping函數(shù)返回Optional類型返回值,否則返回空Optional。flatMap與map(Funtion)方法類似,區(qū)別在于flatMap中的mapper返回值必須是Optional。調(diào)用結(jié)束時(shí),flatMap不會(huì)對(duì)結(jié)果用Optional封裝。
//flatMap與map(Function)非常類似,區(qū)別在于傳入方法的lambda表達(dá)式的返回類型。 //map方法中的lambda表達(dá)式返回值可以是任意類型,在map函數(shù)返回之前會(huì)包裝為Optional。 //但flatMap方法中的lambda表達(dá)式返回值必須是Optionl實(shí)例。 upperName = name.flatMap((value) -> Optional.of(value.toUpperCase())); System.out.println(upperName.orElse("No value found"));//輸出SANAULLA
11、filter:如果有值并且滿足斷言條件返回包含該值的Optional,否則返回空Optional。
//filter方法檢查給定的Option值是否滿足某些條件。 //如果滿足則返回同一個(gè)Option實(shí)例,否則返回空Optional。 OptionalJava8編譯器的新特性longName = name.filter((value) -> value.length() > 6); System.out.println(longName.orElse("The name is less than 6 characters"));//輸出Sanaulla //另一個(gè)例子是Optional值不滿足filter指定的條件。 Optional anotherName = Optional.of("Sana"); Optional shortName = anotherName.filter((value) -> value.length() > 6); //輸出:name長(zhǎng)度不足6字符 System.out.println(shortName.orElse("The name is less than 6 characters"));
1、參數(shù)名字
很長(zhǎng)時(shí)間以來(lái),Java程序員想盡辦法把參數(shù)名字保存在java字節(jié)碼里,并且讓這些參數(shù)名字在運(yùn)行時(shí)可用。Java 8 終于把這個(gè)需求加入到了Java語(yǔ)言(使用反射API和Parameter.getName() 方法)和字節(jié)碼里(使用java編譯命令javac的–parameters參數(shù))。
import java.lang.reflect.Method; import java.lang.reflect.Parameter; public class ParameterNames { public static void main(String[] args) throws Exception { Method method = ParameterNames.class.getMethod( "main", String[].class ); for( final Parameter parameter: method.getParameters() ) { System.out.println( "Parameter: " + parameter.getName() ); } } }
額外的,有一個(gè)方便的方法Parameter.isNamePresent() 來(lái)驗(yàn)證參數(shù)名是不是可用。
如果你編譯這個(gè)class的時(shí)候沒(méi)有添加參數(shù)–parameters,運(yùn)行的時(shí)候你會(huì)得到這個(gè)結(jié)果:
Parameter: arg0
編譯的時(shí)候添加了–parameters參數(shù)的話,運(yùn)行結(jié)果會(huì)不一樣:
Parameter: args
對(duì)于有經(jīng)驗(yàn)的Maven使用者,–parameters參數(shù)可以添加到maven-compiler-plugin的配置部分:
org.apache.maven.plugins maven-compiler-plugin 3.1 -parameters 1.8
eclipse中也可以進(jìn)行相關(guān)配置:
Java 8提供了一個(gè)新的Nashorn javascript引擎,它允許我們?cè)贘VM上運(yùn)行特定的javascript應(yīng)用。Nashorn javascript引擎只是javax.script.ScriptEngine另一個(gè)實(shí)現(xiàn),而且規(guī)則也一樣,允許Java和JavaScript互相操作。這里有個(gè)小例子:
ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName( "JavaScript" ); System.out.println( engine.getClass().getName() ); System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;");
推薦參考:http://www.importnew.com/2266...
Base64對(duì)Base64的支持最終成了Java 8標(biāo)準(zhǔn)庫(kù)的一部分,非常簡(jiǎn)單易用:
import java.nio.charset.StandardCharsets; import java.util.Base64; public class Base64s { public static void main(String[] args) { final String text = "Base64 finally in Java 8!"; final String encoded = Base64 .getEncoder() .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) ); System.out.println( encoded ); final String decoded = new String( Base64.getDecoder().decode( encoded ), StandardCharsets.UTF_8 ); System.out.println( decoded ); } }
新的Base64API也支持URL和MINE的編
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/67340.html
摘要:實(shí)戰(zhàn)讀書筆記第一章從方法傳遞到接著上次的,繼續(xù)來(lái)了解一下,如果繼續(xù)簡(jiǎn)化代碼。去掉并且生成的數(shù)字是萬(wàn),所消耗的時(shí)間循序流并行流至于為什么有時(shí)候并行流效率比循序流還低,這個(gè)以后的文章會(huì)解釋。 《Java8實(shí)戰(zhàn)》-讀書筆記第一章(02) 從方法傳遞到Lambda 接著上次的Predicate,繼續(xù)來(lái)了解一下,如果繼續(xù)簡(jiǎn)化代碼。 把方法作為值來(lái)傳遞雖然很有用,但是要是有很多類似與isHeavy...
摘要:它的出現(xiàn)是為我們解決空指針異常的,以前我們寫代碼如果不進(jìn)行判斷,會(huì)經(jīng)常出現(xiàn)異常。因?yàn)樗旧砭褪莻€(gè)對(duì)象,不管放進(jìn)去的對(duì)象為不為,始終不會(huì)返回,所以你也不需要在你的業(yè)務(wù)流程中進(jìn)行一大堆的判斷,避免了程序運(yùn)行時(shí)的空指針異常。 想必大家已經(jīng)在使用jdk1.8做項(xiàng)目開(kāi)發(fā),但是你對(duì)于它里面的一些性特性了解多少呢?有沒(méi)有在你的項(xiàng)目中運(yùn)用呢?現(xiàn)在就和我來(lái)一起梳理一下吧。 介紹 它是java.util包...
摘要:引言最近發(fā)現(xiàn)很多的新特性不熟悉,所以今天把它們都學(xué)習(xí)一邊,做出效果加深印象,說(shuō)到還加了蠻多的特性,像一些的一些效果,動(dòng)畫屬性等。 引言 最近發(fā)現(xiàn)很多css3的新特性不熟悉,所以今天把它們都學(xué)習(xí)一邊,做出效果加深印象,說(shuō)到css3還加了蠻多的特性,像一些border的一些效果,動(dòng)畫屬性animation trasiform等。 1.border-radius(邊框圓角) 效果 showI...
摘要:基礎(chǔ)語(yǔ)法變量提升都可以個(gè)難點(diǎn)在編譯時(shí)執(zhí)行并沒(méi)有報(bào)錯(cuò),執(zhí)行結(jié)果如圖注意結(jié)果沒(méi)有變更改結(jié)果值變了參考新特性未完一直更新 基礎(chǔ)語(yǔ)法 變量提升 //es5 var arr = []; for(var i=0; i
摘要:會(huì)在數(shù)據(jù)源內(nèi)部隱式的遍歷進(jìn)行處理。會(huì)并行遍歷數(shù)據(jù),將數(shù)據(jù)分成若干段,同時(shí)進(jìn)行處理,最終匯總結(jié)果一起輸出。結(jié)束操作會(huì)觸發(fā)實(shí)際計(jì)算,計(jì)算發(fā)生時(shí)會(huì)把所有中間操作積攢的操作以的方式執(zhí)行,這樣可以減少迭代次數(shù)。為函數(shù)式編程而生。 Stream實(shí)現(xiàn)了對(duì)數(shù)據(jù)源的流式處理,它可以并行操作,提高數(shù)據(jù)處理效率。 什么是流 流不是集合,它不對(duì)數(shù)據(jù)做保存,只是最數(shù)據(jù)進(jìn)行算法處理,比如最大值,最小值,排序等操作...
閱讀 2415·2021-10-14 09:43
閱讀 2444·2021-09-09 09:34
閱讀 1608·2019-08-30 12:57
閱讀 1208·2019-08-29 14:16
閱讀 728·2019-08-26 12:13
閱讀 3209·2019-08-26 11:45
閱讀 2293·2019-08-23 16:18
閱讀 2670·2019-08-23 15:27