摘要:雖然目前工作環(huán)境仍然以為主,不過目前已是大勢所趨了。標準函數(shù)式接口新的包定義旨在使用的廣泛函數(shù)式接口。這一改進使得擁有了類似于多繼承的能力。
從Java8發(fā)布到現(xiàn)在有好幾年了,而Java9也提上發(fā)布日程了(沒記錯的話好像就是這個月2017年7月,也許會再度跳票吧,不過沒關系,穩(wěn)定大于一切,穩(wěn)定了再發(fā)布也行),現(xiàn)在才開始去真正學習,說來也是慚愧。雖然目前工作環(huán)境仍然以Java6為主,不過Java8目前已是大勢所趨了。Java8帶來了許多令人激動的新特性,如lambda表達式,StreamsAPI與并行集合計算,新的時間日期API(借鑒joda-time),字節(jié)碼支持保存方法參數(shù)名(對于框架開發(fā)真的是非常贊的一個特性),Optional類解決空指針問題(雖然Guava中早就有這個了)等。
lambda表達式語法:
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表達式內(nèi)如果引用了外部的局部變量,那么這個局部變量必須是final的,如果不是,編譯器會自動給加上final。比如下面這段是錯誤的
//這是錯誤的 int num = 2; Function函數(shù)式接口:stringConverter = (from) -> from * num; num++;//會報錯,因為此時num已經(jīng)是final聲明的了 System.out.println(stringConverter.apply(3));
僅有一個抽象方法的接口(注:Java8之后接口也可以有非抽象方法,所以此處強調(diào)只有一個抽象方法的接口)
可選注解:@FunctionalInterface , 作用:編譯器會檢查,javadoc文檔中會特別說明。
這里需要強調(diào)的是,函數(shù)式接口只能有一個抽象方法,而不是指只能有一個方法。這分兩點來說明。首先,在Java 8中,接口運行存在實例方法(見默認方法一節(jié)),其次任何被java.lang.Object實現(xiàn)的方法,都不能視為抽象方法,因此,下面的NonFunc接口不是函數(shù)式接口,因為equals()方法在java.lang.Object中已經(jīng)實現(xiàn)。
標準函數(shù)式接口
新的 java.util.function 包定義旨在使用 lambdas 的廣泛函數(shù)式接口。這些接口分為幾大類:
Function:接受一個參數(shù),基于參數(shù)值返回結(jié)果
Predicate:接受一個參數(shù),基于參數(shù)值返回一個布爾值
BiFunction:接受兩個參數(shù),基于參數(shù)值返回結(jié)果
Supplier:不接受參數(shù),返回一個結(jié)果
Consumer:接受一個參數(shù),無結(jié)果 (void)
interface NonFunc {
boolean equals(Object obj);
}
同理,下面實現(xiàn)的IntHandler接口符合函數(shù)式接口要求,雖然看起來它不像,但實際上它是一個完全符合規(guī)范的函數(shù)式接口。
@FunctionalInterface
public static interface IntHandler{
void handle(int i);
boolean equals(Object obj);
}
接口默認方法
在Java 8之前的版本,接口只能包含抽象方法,Java 8 對接口做了進一步的增強。在接口中可以添加使用 default 關鍵字修飾的非抽象方法。還可以在接口中定義靜態(tài)方法。如今,接口看上去與抽象類的功能越來越類似了。這一改進使得Java 8擁有了類似于多繼承的能力。一個對象實例,將擁有來自于多個不同接口的實例方法。
比如,對于接口IHorse,實現(xiàn)如下:
public interface IHorse{
void eat();
default void run(){
System.out.println(“hourse run”);
}
}
在Java 8中,使用default關鍵字,可以在接口內(nèi)定義實例方法。注意,這個方法是并非抽象方法,而是擁有特定邏輯的具體實例方法。
所有的動物都能自由呼吸,所以,這里可以再定義一個IAnimal接口,它也包含一個默認方法breath()。
public interface IAnimal {
default void breath(){
System.out.println(“breath”);
}
}
騾是馬和驢的雜交物種,因此騾(Mule)可以實現(xiàn)為IHorse,同時騾也是動物,因此有:
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實例同時擁有來自不同接口的實現(xiàn)方法。在這Java 8之前是做不到的。從某種程度上說,這種模式可以彌補Java單一繼承的一些不便。但同時也要知道,它也將遇到和多繼承相同的問題,如果IDonkey也存在一個默認的run()方法,那么同時實現(xiàn)它們的Mule,就會不知所措,因為它不知道應該以哪個方法為準。此時,由于IHorse和IDonkey擁有相同的默認實例方法,故編譯器會拋出一個錯誤:Duplicate default methods named run with the parameters () and () are inherited from the types IDonkey and IHorse
接口默認實現(xiàn)對于整個函數(shù)式編程的流式表達式非常重要。比如,大家熟悉的java.util.Comparator接口,它在JDK 1.2時就已經(jīng)被引入。在Java 8中,Comparator接口新增了若干個默認方法,用于多個比較器的整合。其中一個常用的默認如下:
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òu)造一個比較器,它先按照字符串長度排序,繼而按照大小寫不敏感的字母順序排序。
Comparator接口靜態(tài)方法:cmp = Comparator.comparingInt(String::length) .thenComparing(String.CASE_INSENSITIVE_ORDER);
在接口中,還允許定義靜態(tài)的方法。接口中的靜態(tài)方法可以直接用接口來調(diào)用。
例如,下面接口中定義了一個靜態(tài)方法 find,該方法可以直接用 StaticFunInterface .find() 來調(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();
}
}
說明:雖然我知道了default方法是為了便于集合接口(新的StreamsAPI)向后兼容而設計的,但是這個接口靜態(tài)方法暫時還沒體會其作用,可能得接觸多了才會明白吧。
方法引用靜態(tài)方法引用:ClassName::methodName
實例上的實例方法引用:instanceReference::methodName (這里還可以使用this)
超類上的實例方法引用:super::methodName
類型上的實例方法引用:ClassName::methodName
構(gòu)造方法引用:Class::new
數(shù)組構(gòu)造方法引用:TypeName[]::new
public class InstanceMethodRef {
public static void main(String[] args) {
List users=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);
}
}
注意幾點:
對于第一個方法引用User::getName,表示User類的實例方法。在執(zhí)行時,Java會自動識別流中的元素(這里指User實例)是作為調(diào)用目標還是調(diào)用方法的參數(shù)。
一般來說,如果使用的是靜態(tài)方法,或者調(diào)用目標明確,那么流內(nèi)的元素會自動作為參數(shù)使用。如果函數(shù)引用表示實例方法,并且不存在調(diào)用目標,那么流內(nèi)元素就會自動作為調(diào)用目標。
因此,如果一個類中存在同名的實例方法和靜態(tài)函數(shù),那么編譯器就會感到很困惑,因為此時,它不知道應該使用哪個方法進行調(diào)用。
打開 Collection Api可以看到多了一個 stream() default 方法:
default Streamstream() { return StreamSupport.stream(spliterator(), false); }
Stream 允許以聲明方式處理集合等可以轉(zhuǎn)換為 Stream
內(nèi)部迭代 :與原有的 Iterator 不同, Stream 將迭代操作(類似 for / for-each )全部固化到了Api內(nèi)部實現(xiàn), 用戶只需傳入表達計算邏輯的 lambda 表達式(可以理解為 Supplier 、 Function 這些的 @FunctionalInterface 的實現(xiàn)), Stream 便會自動迭代- 數(shù)據(jù)觸發(fā)計算邏輯并生成結(jié)果. 內(nèi)部迭代主要解決了兩方面的問題: 避免集合處理時的套路和晦澀 ; 便于庫內(nèi)部實現(xiàn)的多核并行優(yōu)化 .
流水線 :很多 Stream 操作會再返回一個 Stream , 這樣多個操作就可以鏈接起來, 形成一個大的流水線, 使其看起來像是 對數(shù)據(jù)源進行數(shù)據(jù)庫式查詢 , 這也就讓自動優(yōu)化成為可能, 如 隱式并行 .
隱式并行 :如將 .stream() 替換為 .parallelStream() , Stream 則會自動啟用Fork/Join框架, 并行執(zhí)行各條流水線, 并最終自動將結(jié)果進行合并.
延遲計算 :由于 Stream 大部分的操作(如 filter() 、 generate() 、 map() …)都是接受一段 lambda 表達式, 邏輯類似接口實現(xiàn)(可以看成是 回調(diào) ), 因此代碼并不是立即執(zhí)行的, 除非流水線上觸發(fā)一個終端操作, 否則中間操作不會執(zhí)行任何處理.
短路求值 :有些操作不需要處理整個流就能夠拿到結(jié)果, 很多像 anyMatch() 、 allMatch() 、 limit() , 只要找到一個元素他們的工作就可以結(jié)束, 也就沒有必要執(zhí)行后面的操作, 因此如果后面有大量耗時的操作, 此舉可大大節(jié)省性能.
Stream 構(gòu)成
一個流管道(Stream pipeline)通常由3部分構(gòu)成: 數(shù)據(jù)源(Source) -> 中間操作/轉(zhuǎn)換(Transforming) -> 終端操作/執(zhí)行(Operations) : Stream 由數(shù)據(jù)源生成, 經(jīng)由中間操作串聯(lián)起來的一條流水線的轉(zhuǎn)換, 最后由終端操作觸發(fā)執(zhí)行拿到結(jié)果.
除了前面介紹過的 collection.stream() , 流的生成方式多種多樣, 可簡單概括為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)建一個懶惰連接的流,其元素是第一個流的所有元素,后跟第二個流的所有元素.
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)換所有的中間操作都會返回另一個 Stream , 這讓多個操作可以鏈接起來組成中間操作鏈, 從而形成一條流水線, 因此它的特點就是前面提到的 延遲執(zhí)行 : 觸發(fā)流水線上觸發(fā)一個終端操作, 否則中間操作不執(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()
假設我們有這樣一個字符串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 中的層級結(jié)構(gòu)扁平化了, 將內(nèi)層 Stream 內(nèi)的元素抽取出來, 最終新的 Stream 就沒有內(nèi)層 Stream 了.
可以簡單概括為: flatMap() 方法讓你把一個流中的每個值都換成另一個 Stream , 然后把所有的 Stream 連接起來成為一個 Stream .
終端操作不僅擔負著觸發(fā)流水線執(zhí)行的任務, 他還需要拿到流水線執(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() 這樣的操作, 拿到一個對 Stream 進行匯總了的結(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簡單點的:
static int [] arr={1,4,3,6,5,7,2,9};
public static void main(String[]args){
//Array.stream()方法返回了一個流對象。類似于集合或者數(shù)組,流對象也是一個對象的集合,它將給予我們遍歷處理流內(nèi)元素的功能
Arrays.stream(arr).forEach((x)->System.out.println(x));
}
復雜點的:
public void joiningList() {
// 生成一段[0,20)序列
List list = IntStream.range(0, 20)
.boxed()
.collect(Collectors.toList());
// 將list內(nèi)的偶數(shù)提取反向排序后聚合為一個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 List transactions;
@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. 找出交易員都在哪些不同的城市工作過
Set distinctCities = transactions.stream()
.map(transaction -> transaction.getTrader().getCity())
.collect(Collectors.toSet()); // or .distinct().collect(Collectors.toList())
System.out.println(distinctCities);
// 3. 找出所有來自于劍橋的交易員, 并按姓名排序
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. 有沒有交易員是在米蘭工作
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中,可以在接口不變的情況下,將流改為并行流。這樣,就可以很自然地使用多線程進行集合中的數(shù)據(jù)處理。
并行流與并行排序demo:我們希望可以統(tǒng)計一個1~1000000內(nèi)所有的質(zhì)數(shù)的數(shù)量。
IntStream.range(1,1000000).parallel().filter(PrimeUtil::isPrime).count();
從集合得到并行流
List進階:自己生成流ss =new ArrayList (); ... double ave = ss.parallelStream().mapToInt(s->s.score).average().getAsDouble(); int[]arr = new int [10000000]; Arrarys.parallelSort(arr);
1、Stream.generate
通過實現(xiàn) Supplier 接口,你可以自己來控制流的生成。這種情形通常用于隨機數(shù)、常量的 Stream,或者需要前后元素間維持著某種狀態(tài)信息的 Stream。把 Supplier 實例傳遞給 Stream.generate() 生成的 Stream,默認是串行(相對 parallel 而言)但無序的(相對 ordered 而言)。由于它是無限的,在管道中,必須利用 limit 之類的操作限制 Stream 大小。
//生成 10 個隨機整數(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);
注意幾個關鍵詞:默認串行、無序、無限(需要進行短路求值操作如limit)。
2、Stream.iterate
iterate 跟 reduce 操作很像,接受一個種子值,和一個 UnaryOperator(例如 f)。然后種子值成為 Stream 的第一個元素,f(seed) 為第二個,f(f(seed)) 第三個,以此類推。
//生成一個等差數(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 時候管道必須有 limit 這樣的操作來限制 Stream 大小。
進階:用 Collectors 來進行 reduction 操作java.util.stream.Collectors 類的主要作用就是輔助進行各類有用的 reduction 操作,例如轉(zhuǎn)變輸出為 Collection,把 Stream 元素進行歸組。
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”進行分組后可以看到,不到 18 歲的未成年人是一組,成年人是另外一組。partitioningBy 其實是一種特殊的 groupingBy,它依照條件測試的是否兩種結(jié)果來構(gòu)造返回的數(shù)據(jù)結(jié)構(gòu),get(true) 和 get(false) 能即為全部的元素對象。
其實這里很多觀念和Spark的RDD操作很相似。
總之,Stream 的特性可以歸納為:
不是數(shù)據(jù)結(jié)構(gòu),它沒有內(nèi)部存儲,它只是用操作管道從 source(數(shù)據(jù)結(jié)構(gòu)、數(shù)組、generator function、IO channel)抓取數(shù)據(jù)。
它也絕不修改自己所封裝的底層數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)。例如 Stream 的 filter 操作會產(chǎn)生一個不包含被過濾元素的新 Stream,而不是從 source 刪除那些元素。
所有 Stream 的操作必須以 lambda 表達式為參數(shù)
不支持索引訪問,你可以請求第一個元素,但無法請求第二個,第三個,或最后一個。
很容易生成數(shù)組或者 List
惰性化,Intermediate 操作永遠是惰性化的。
很多 Stream 操作是向后延遲的,一直到它弄清楚了最后需要多少數(shù)據(jù)才會開始。
并行能力,當一個 Stream 是并行化的,就不需要再寫多線程代碼,所有對它的操作會自動并行進行的。
可以是無限的,集合有固定大小,Stream 則不必。limit(n) 和 findFirst() 這類的 short-circuiting 操作可以對無限的 Stream 進行運算并很快完成。
注解的更新對于注解,Java 8 主要有兩點改進:類型注解和重復注解。
Java 8 的類型注解擴展了注解使用的范圍。在該版本之前,注解只能是在聲明的地方使用?,F(xiàn)在幾乎可以為任何東西添加注解:局部變量、類與接口,就連方法的異常也能添加注解。新增的兩個注釋的程序元素類型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER 用來描述注解的新場合。
ElementType.TYPE_PARAMETER 表示該注解能寫在類型變量的聲明語句中。而 ElementType.TYPE_USE 表示該注解能寫在使用類型的任何語句中(例如聲明語句、泛型和強制轉(zhuǎn)換語句中的類型)。
對類型注解的支持,增強了通過靜態(tài)分析工具發(fā)現(xiàn)錯誤的能力。原先只能在運行時發(fā)現(xiàn)的問題可以提前在編譯的時候被排查出來。Java 8 本身雖然沒有自帶類型檢測的框架,但可以通過使用 Checker Framework 這樣的第三方工具,自動檢查和確認軟件的缺陷,提高生產(chǎn)效率。
在Java8之前使用注解的一個限制是相同的注解在同一位置只能聲明一次,不能聲明多次。Java 8 引入了重復注解機制,這樣相同的注解可以在同一地方聲明多次。重復注解機制本身必須用 @Repeatable 注解。
IO/NIO 的改進增加了一些新的 IO/NIO 方法,使用這些方法可以從文件或者輸入流中獲取流(java.util.stream.Stream),通過對流的操作,可以簡化文本行處理、目錄遍歷和文件查找。
新增的 API 如下:
BufferedReader.line(): 返回文本行的流 Stream
File.lines(Path, Charset):返回文本行的流 Stream
File.list(Path): 遍歷當前目錄下的文件和目錄
File.walk(Path, int, FileVisitOption): 遍歷某一個目錄下的所有文件和指定深度的子目錄
File.find(Path, int, BiPredicate, FileVisitOption... ): 查找相應的文件
下面就是用流式操作列出當前目錄下的所有文件和目錄:
Files.list(new File(".").toPath())
.forEach(System.out::println);
新的Date/Time API
Java 的日期與時間 API 問題由來已久,Java 8 之前的版本中關于時間、日期及其他時間日期格式化類由于線程安全、重量級、序列化成本高等問題而飽受批評。Java 8 吸收了 Joda-Time 的精華,以一個新的開始為 Java 創(chuàng)建優(yōu)秀的 API。新的 java.time 中包含了所有關于時鐘(Clock),本地日期(LocalDate)、本地時間(LocalTime)、本地日期時間(LocalDateTime)、時區(qū)(ZonedDateTime)和持續(xù)時間(Duration)的類。歷史悠久的 Date 類新增了 toInstant() 方法,用于把 Date 轉(zhuǎn)換成新的表示形式。這些新增的本地化時間日期 API 大大簡化了了日期時間和本地化的管理。
Java日期/時間API包含以下相應的包。
java.time包:這是新的Java日期/時間API的基礎包,所有的主要基礎類都是這個包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有這些類都是不可變的和線程安全的,在絕大多數(shù)情況下,這些類能夠有效地處理一些公共的需求。
java.time.chrono包:這個包為非ISO的日歷系統(tǒng)定義了一些泛化的API,我們可以擴展AbstractChronology類來創(chuàng)建自己的日歷系統(tǒng)。
java.time.format包:這個包包含能夠格式化和解析日期時間對象的類,在絕大多數(shù)情況下,我們不應該直接使用它們,因為java.time包中相應的類已經(jīng)提供了格式化和解析的方法。
java.time.temporal包:這個包包含一些時態(tài)對象,我們可以用其找出關于日期/時間對象的某個特定日期或時間,比如說,可以找到某月的第一天或最后一天。你可以非常容易地認出這些方法,因為它們都具有“withXXX”的格式。
java.time.zone包:這個包包含支持不同時區(qū)以及相關規(guī)則的類。
例如,下面是對 LocalDate,LocalTime 的簡單應用:
//LocalDate只保存日期系統(tǒng)的日期部分,有時區(qū)信息,LocalTime只保存時間部分,沒有時區(qū)信息。LocalDate和LocalTime都可以從Clock對象創(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(); //獲取當前時間 System.out.println(localTime.toString());//輸出當前時間 localTime = LocalTime.of(10, 20, 50);//獲得 10:20:50 的時間點 System.out.println(localTime.toString());//輸出: 10:20:50 //Clock 時鐘,Clock類可以替換 System.currentTimeMillis() 和 TimeZone.getDefault(). 如:Clock.systemDefaultZone().millis() Clock clock = Clock.systemDefaultZone();//獲取系統(tǒng)默認時區(qū) (當前瞬時時間 ) long millis = clock.millis();// //LocalDateTime類合并了LocalDate和LocalTime,它保存有ISO-8601日期系統(tǒng)的日期和時間,但是沒有時區(qū)信息。 final LocalDateTime datetime = LocalDateTime.now(); final LocalDateTime datetimeFromClock = LocalDateTime.now( clock ); System.out.println( datetime ); System.out.println( datetimeFromClock ); //如果您需要一個類持有日期時間和時區(qū)信息,可以使用ZonedDateTime,它保存有ISO-8601日期系統(tǒng)的日期和時間,而且有時區(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持有的時間精確到納秒。它讓我們很容易計算兩個日期中間的差異。讓我們來看一下:
// 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() );
上面的例子計算了兩個日期(2014年4月16日和2014年5月16日)之間的持續(xù)時間(基于天數(shù)和小時)
日期API操作
//日期算術操作,多數(shù)日期/時間API類都實現(xiàn)了一系列工具方法,如:加/減天數(shù)、周數(shù)、月份數(shù),等等。還有其他的工具方法能夠使用TemporalAdjuster調(diào)整日期,并計算兩個日期間的周期。
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());
解析和格式化:將一個日期格式轉(zhuǎn)換為不同的格式,之后再解析一個字符串,得到日期時間對象
//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);
舊的日期時間支持轉(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強大的函數(shù)式異步編程輔助類
Future是Java 5添加的類,用來描述一個異步計算的結(jié)果。你可以使用isDone方法檢查計算是否完成,或者使用get阻塞住調(diào)用線程,直到計算完成返回結(jié)果,你也可以使用cancel方法停止任務的執(zhí)行。
雖然Future以及相關使用方法提供了異步執(zhí)行任務的能力,但是對于結(jié)果的獲取卻是很不方便,只能通過阻塞或者輪詢的方式得到任務的結(jié)果。阻塞的方式顯然和我們的異步編程的初衷相違背,輪詢的方式又會耗費無謂的CPU資源,而且也不能及時地得到計算結(jié)果,為什么不能用觀察者設計模式當計算結(jié)果完成及時通知監(jiān)聽者呢?
其實Google guava早就提供了通用的擴展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中, 新增加了一個包含50個方法左右的類: CompletableFuture,提供了非常強大的Future的擴展功能,可以幫助我們簡化異步編程的復雜性,提供了函數(shù)式編程的能力,可以通過回調(diào)的方式處理計算結(jié)果,并且提供了轉(zhuǎn)換和組合CompletableFuture的方法。
CompletableFuture類實現(xiàn)了CompletionStage和Future接口,所以你還是可以像以前一樣通過阻塞或者輪詢的方式獲得結(jié)果,盡管這種方式不推薦使用。
盡管Future可以代表在另外的線程中執(zhí)行的一段異步代碼,但是你還是可以在本身線程中執(zhí)行:
public class BasicMain {
public static CompletableFuture compute() {
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沒有關聯(lián)任何的Callback、線程池、異步任務等,如果客戶端調(diào)用future.get就會一致傻等下去。除非我們顯式指定complete
f.complete(100);
//f.completeExceptionally(new Exception());
System.in.read();
}
}
CompletableFuture.complete()、CompletableFuture.completeExceptionally只能被調(diào)用一次。但是我們有兩個后門方法可以重設這個值:obtrudeValue、obtrudeException,但是使用的時候要小心,因為complete已經(jīng)觸發(fā)了客戶端,有可能導致客戶端會得到不期望的結(jié)果。
1、創(chuàng)建CompletableFuture對象。
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é)尾并且沒有指定Executor的方法會使用ForkJoinPool.commonPool()作為它的線程池執(zhí)行異步代碼。因為方法的參數(shù)類型都是函數(shù)式接口,所以可以使用lambda表達式實現(xiàn)異步任務
2、計算結(jié)果完成時的處理
當CompletableFuture的計算結(jié)果完成,或者拋出異常的時候,我們可以執(zhí)行特定的Action。主要是下面的方法:
public CompletableFuture
public CompletableFuture
public CompletableFuture
public CompletableFuture
注意:方法不以Async結(jié)尾,意味著Action使用相同的線程執(zhí)行,而Async可能會使用其它的線程去執(zhí)行(如果使用相同的線程池,也可能會被同一個線程選中執(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 {
CompletableFuture future = 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對象,但是對象的值和原來的CompletableFuture計算的值不同。當原先的CompletableFuture的值計算完成或者拋出異常的時候,會觸發(fā)這個CompletableFuture對象的計算,結(jié)果由BiFunction參數(shù)計算而得。
因此這組方法兼有whenComplete和轉(zhuǎn)換的兩個功能。
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é)尾的方法由原來的線程計算,以Async結(jié)尾的方法由默認的線程池ForkJoinPool.commonPool()或者指定的線程池executor運行。
3、轉(zhuǎn)換
CompletableFuture可以作為monad(單子)和functor。由于回調(diào)風格的實現(xiàn),我們不必因為等待一個計算完成而阻塞著調(diào)用線程,而是告訴CompletableFuture當計算完成的時候請執(zhí)行某個function。而且我們還可以將這些操作串聯(lián)起來,或者將CompletableFuture組合起來。
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ù)的功能是當原來的CompletableFuture計算完后,將結(jié)果傳遞給函數(shù)fn,將fn的結(jié)果作為新的CompletableFuture計算結(jié)果。因此它的功能相當于將CompletableFuture
它們與handle方法的區(qū)別在于handle方法會處理正常計算值和異常,因此它可以屏蔽異常,避免異常繼續(xù)拋出。而thenApply方法只是用來處理正常值,因此一旦有異常就會拋出。
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í)行的,也不會阻塞,而是在前一個stage完成后繼續(xù)執(zhí)行。
4、純消費(執(zhí)行Action)
上面的方法是當計算完成的時候,會生成新的計算結(jié)果(thenApply, handle),或者返回同樣的計算結(jié)果whenComplete,CompletableFuture還提供了一種處理結(jié)果的方法,只對結(jié)果執(zhí)行Action,而不返回新的計算值,因此計算值為Void:
public CompletableFuture
public CompletableFuture
public CompletableFuture
看它的參數(shù)類型也就明白了,它們是函數(shù)式接口Consumer,這個接口只有輸入,沒有返回值。
CompletableFuturefuture = CompletableFuture.supplyAsync(() -> { return 100; }); CompletableFuture f = future.thenAccept(System.out::println); System.out.println(f.get());
thenAcceptBoth以及相關方法提供了類似的功能,當兩個CompletionStage都正常完成計算的時候,就會執(zhí)行提供的action,它用來組合另外一個異步的結(jié)果。
runAfterBoth是當兩個CompletionStage都正常完成計算的時候,執(zhí)行一個Runnable,這個Runnable并不使用計算的結(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());
更徹底地,下面一組方法當計算完成的時候會執(zhí)行一個Runnable,與thenAccept不同,Runnable并不使用CompletableFuture計算的結(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ù)的類型來加速你的記憶。Runnable類型的參數(shù)會忽略計算的結(jié)果,Consumer是純消費計算結(jié)果,BiConsumer會組合另外一個CompletionStage純消費,F(xiàn)unction會對計算結(jié)果做轉(zhuǎn)換,BiFunction會組合另外一個CompletionStage的計算結(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)
這一組方法接受一個Function作為參數(shù),這個Function的輸入是當前的CompletableFuture的計算值,返回結(jié)果將是一個新的CmpletableFuture,這個新的CompletableFuture會組合原來的CompletableFuture和函數(shù)返回的CompletableFuture。
public CompletableFuture
public CompletableFuture
public CompletableFuture
兩個CompletionStage是并行執(zhí)行的,它們之間并沒有先后依賴順序,other并不會等待先前的CompletableFuture執(zhí)行完畢后再執(zhí)行。
6、Either
thenAcceptBoth和runAfterBoth是當兩個CompletableFuture都計算完成,而我們下面要了解的方法是當任意一個CompletableFuture計算完成的時候就會執(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)
下面這個例子有時會輸出100,有時候會輸出200,哪個Future先完成就會根據(jù)它的結(jié)果計算。
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
用來組合多個CompletableFuture。
public static CompletableFuture
public static CompletableFuture
anyOf方法是當任意一個CompletableFuture執(zhí)行完后就會執(zhí)行計算,計算的結(jié)果相同。
8、更進一步
如果你用過Guava的Future類,你就會知道它的Futures輔助類提供了很多便利方法,用來處理多個Future,而不像Java的CompletableFuture,只提供了allOf、anyOf兩個方法。
比如有這樣一個需求,將多個CompletableFuture組合成一個CompletableFuture,這個組合后的CompletableFuture的計算結(jié)果是個List,它包含前面所有的CompletableFuture的計算結(jié)果,guava的Futures.allAsList可以實現(xiàn)這樣的功能.
但是對于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有多個項目可以實現(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)用一個方法得到了返回值卻不能直接將返回值作為參數(shù)去調(diào)用別的方法。我們首先要判斷這個返回值是否為null,只有在非空的前提下才能將其作為其他方法的參數(shù)。
Java 8引入了一個新的Optional類。Optional類的Javadoc描述如下:這是一個可以為null的容器對象。如果值存在則isPresent()方法會返回true,調(diào)用get()方法會返回該對象。
1、of:為非null的值創(chuàng)建一個Optional。需要注意的是,創(chuàng)建對象時傳入的參數(shù)不能為null。如果傳入?yún)?shù)為null,則拋出NullPointerException
//調(diào)用工廠方法創(chuàng)建Optional實例 Optionalname = Optional.of("Sanaulla"); //錯誤寫法,傳入?yún)?shù)為null,拋出NullPointerException. Optional someNull = Optional.of(null);
2、ofNullable:為指定的值創(chuàng)建一個Optional,如果指定的值為null,則返回一個空的Optional。
ofNullable與of方法相似,唯一的區(qū)別是可以接受參數(shù)為null的情況
3、isPresent:如果值存在返回true,否則返回false。
4、get:如果Optional有值則將其返回,否則拋出NoSuchElementException。
5、ifPresent:如果Optional實例有值則為其調(diào)用consumer,否則不做處理
要理解ifPresent方法,首先需要了解Consumer類。簡答地說,Consumer類包含一個抽象方法。該抽象方法對傳入的值進行處理,但沒有返回值。Java8支持不用接口直接通過lambda表達式傳入?yún)?shù)。
//ifPresent方法接受lambda表達式作為參數(shù)。
//lambda表達式對Optional的值調(diào)用consumer進行處理。
name.ifPresent((value) -> {
System.out.println("The length of the value is: " + value.length());
});
6、orElse:如果有值則將其返回,否則返回orElse方法傳入的參數(shù)。
7、orElseGet:orElseGet與orElse方法類似,區(qū)別在于得到的默認值。orElse方法將傳入的字符串作為默認值,orElseGet方法可以接受Supplier接口的實現(xiàn)用來生成默認值。示例如下:
//orElseGet與orElse方法類似,區(qū)別在于orElse傳入的是默認值, //orElseGet可以接受一個lambda表達式生成默認值。 //輸出:Default Value System.out.println(empty.orElseGet(() -> "Default Value")); //輸出:Sanaulla System.out.println(name.orElseGet(() -> "Default Value"));
8、orElseThrow:如果有值則將其返回,否則拋出supplier接口創(chuàng)建的異常。
在orElseGet方法中,我們傳入一個Supplier接口。然而,在orElseThrow中我們可以傳入一個lambda表達式或方法,如果值不存在來拋出異常。示例如下:
try {
//orElseThrow與orElse方法類似。與返回默認值不同,
//orElseThrow會拋出lambda表達式或方法生成的異常
empty.orElseThrow(ValueAbsentException::new);
} catch (Throwable ex) {
//輸出: No value present in the Optional instance
System.out.println(ex.getMessage());
}
9、map:如果有值,則對其執(zhí)行調(diào)用mapping函數(shù)得到返回值。如果返回值不為null,則創(chuàng)建包含mapping返回值的Optional作為map方法返回值,否則返回空Optional。
map方法用來對Optional實例的值執(zhí)行一系列操作。
//map方法執(zhí)行傳入的lambda表達式參數(shù)對Optional實例的值進行修改。 //為lambda表達式的返回值創(chuàng)建新的Optional實例作為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é)束時,flatMap不會對結(jié)果用Optional封裝。
//flatMap與map(Function)非常類似,區(qū)別在于傳入方法的lambda表達式的返回類型。
//map方法中的lambda表達式返回值可以是任意類型,在map函數(shù)返回之前會包裝為Optional。
//但flatMap方法中的lambda表達式返回值必須是Optionl實例。
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName.orElse("No value found"));//輸出SANAULLA
11、filter:如果有值并且滿足斷言條件返回包含該值的Optional,否則返回空Optional。
//filter方法檢查給定的Option值是否滿足某些條件。 //如果滿足則返回同一個Option實例,否則返回空Optional。 OptionalJava8編譯器的新特性longName = name.filter((value) -> value.length() > 6); System.out.println(longName.orElse("The name is less than 6 characters"));//輸出Sanaulla //另一個例子是Optional值不滿足filter指定的條件。 Optional anotherName = Optional.of("Sana"); Optional shortName = anotherName.filter((value) -> value.length() > 6); //輸出:name長度不足6字符 System.out.println(shortName.orElse("The name is less than 6 characters"));
1、參數(shù)名字
很長時間以來,Java程序員想盡辦法把參數(shù)名字保存在java字節(jié)碼里,并且讓這些參數(shù)名字在運行時可用。Java 8 終于把這個需求加入到了Java語言(使用反射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() );
}
}
}
額外的,有一個方便的方法Parameter.isNamePresent() 來驗證參數(shù)名是不是可用。
如果你編譯這個class的時候沒有添加參數(shù)–parameters,運行的時候你會得到這個結(jié)果:
Parameter: arg0
編譯的時候添加了–parameters參數(shù)的話,運行結(jié)果會不一樣:
Parameter: args
對于有經(jīng)驗的Maven使用者,–parameters參數(shù)可以添加到maven-compiler-plugin的配置部分:
org.apache.maven.plugins maven-compiler-plugin 3.1 -parameters 1.8 1.8
eclipse中也可以進行相關配置:
Java 8提供了一個新的Nashorn javascript引擎,它允許我們在JVM上運行特定的javascript應用。Nashorn javascript引擎只是javax.script.ScriptEngine另一個實現(xiàn),而且規(guī)則也一樣,允許Java和JavaScript互相操作。這里有個小例子:
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對Base64的支持最終成了Java 8標準庫的一部分,非常簡單易用:
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)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/67340.html
摘要:實戰(zhàn)讀書筆記第一章從方法傳遞到接著上次的,繼續(xù)來了解一下,如果繼續(xù)簡化代碼。去掉并且生成的數(shù)字是萬,所消耗的時間循序流并行流至于為什么有時候并行流效率比循序流還低,這個以后的文章會解釋。 《Java8實戰(zhàn)》-讀書筆記第一章(02) 從方法傳遞到Lambda 接著上次的Predicate,繼續(xù)來了解一下,如果繼續(xù)簡化代碼。 把方法作為值來傳遞雖然很有用,但是要是有很多類似與isHeavy...
摘要:它的出現(xiàn)是為我們解決空指針異常的,以前我們寫代碼如果不進行判斷,會經(jīng)常出現(xiàn)異常。因為它本身就是個對象,不管放進去的對象為不為,始終不會返回,所以你也不需要在你的業(yè)務流程中進行一大堆的判斷,避免了程序運行時的空指針異常。 想必大家已經(jīng)在使用jdk1.8做項目開發(fā),但是你對于它里面的一些性特性了解多少呢?有沒有在你的項目中運用呢?現(xiàn)在就和我來一起梳理一下吧。 介紹 它是java.util包...
摘要:基礎語法變量提升都可以個難點在編譯時執(zhí)行并沒有報錯,執(zhí)行結(jié)果如圖注意結(jié)果沒有變更改結(jié)果值變了參考新特性未完一直更新 基礎語法 變量提升 //es5 var arr = []; for(var i=0; i
摘要:會在數(shù)據(jù)源內(nèi)部隱式的遍歷進行處理。會并行遍歷數(shù)據(jù),將數(shù)據(jù)分成若干段,同時進行處理,最終匯總結(jié)果一起輸出。結(jié)束操作會觸發(fā)實際計算,計算發(fā)生時會把所有中間操作積攢的操作以的方式執(zhí)行,這樣可以減少迭代次數(shù)。為函數(shù)式編程而生。 Stream實現(xiàn)了對數(shù)據(jù)源的流式處理,它可以并行操作,提高數(shù)據(jù)處理效率。 什么是流 流不是集合,它不對數(shù)據(jù)做保存,只是最數(shù)據(jù)進行算法處理,比如最大值,最小值,排序等操作...
閱讀 2497·2021-10-14 09:43
閱讀 2544·2021-09-09 09:34
閱讀 1666·2019-08-30 12:57
閱讀 1267·2019-08-29 14:16
閱讀 791·2019-08-26 12:13
閱讀 3261·2019-08-26 11:45
閱讀 2357·2019-08-23 16:18
閱讀 2734·2019-08-23 15:27