Java8新特性學習筆記

從Java8發佈到如今有好幾年了,而Java9也提上發佈日程了(沒記錯的話好像就是這個月2017年7月,也許會再度跳票吧,不過不要緊,穩定大於一切,穩定了再發布也行),如今纔開始去真正學習,說來也是慚愧。雖然目前工做環境仍然以Java6爲主,不過Java8目前已經是大勢所趨了。Java8帶來了許多使人激動的新特性,如lambda表達式,StreamsAPI與並行集合計算,新的時間日期API(借鑑joda-time),字節碼支持保存方法參數名(對於框架開發真的是很是讚的一個特性),Optional類解決空指針問題(雖然Guava中早就有這個了)等。javascript

lambda表達式

語法:html

  • v->System.out.println(v)java

  • (v)->System.out.println(v)git

  • (String v)->System.out.println(v)程序員

  • (v)->{System.out.println(v);return v+1;}github

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.forEach((Integer value) -> System.out.println(value));

注意:lambda表達式內若是引用了外部的局部變量,那麼這個局部變量必須是final的,若是不是,編譯器會自動給加上final。好比下面這段是錯誤的數據庫

//這是錯誤的
int num = 2;
Function<Integer, Integer> stringConverter = (from) -> from * num;
num++;//會報錯,由於此時num已是final聲明的了
System.out.println(stringConverter.apply(3));

函數式接口:

  • 僅有一個抽象方法的接口(注:Java8以後接口也能夠有非抽象方法,因此此處強調只有一個抽象方法的接口)apache

  • 可選註解:@FunctionalInterface , 做用:編譯器會檢查,javadoc文檔中會特別說明。編程

這裏須要強調的是,函數式接口只能有一個抽象方法,而不是指只能有一個方法。這分兩點來講明。首先,在Java 8中,接口運行存在實例方法(見默認方法一節),其次任何被java.lang.Object實現的方法,都不能視爲抽象方法,所以,下面的NonFunc接口不是函數式接口,由於equals()方法在java.lang.Object中已經實現。 segmentfault

標準函數式接口
新的 java.util.function 包定義旨在使用 lambdas 的普遍函數式接口。這些接口分爲幾大類:

  • Function:接受一個參數,基於參數值返回結果

  • Predicate:接受一個參數,基於參數值返回一個布爾值

  • BiFunction:接受兩個參數,基於參數值返回結果

  • Supplier:不接受參數,返回一個結果

  • Consumer:接受一個參數,無結果 (void)

interface NonFunc {
boolean equals(Object obj);
}

同理,下面實現的IntHandler接口符合函數式接口要求,雖然看起來它不像,但實際上它是一個徹底符合規範的函數式接口。

@FunctionalInterface
public static interface IntHandler{
    void handle(int i);
    boolean equals(Object obj);
}

接口默認方法

在Java 8以前的版本,接口只能包含抽象方法,Java 8 對接口作了進一步的加強。在接口中能夠添加使用 default 關鍵字修飾的非抽象方法。還能夠在接口中定義靜態方法。現在,接口看上去與抽象類的功能愈來愈相似了。這一改進使得Java 8擁有了相似於多繼承的能力。一個對象實例,將擁有來自於多個不一樣接口的實例方法。
好比,對於接口IHorse,實現以下:

public interface IHorse{
    void eat();
    default void run(){
        System.out.println(「hourse run」);
    }
}

在Java 8中,使用default關鍵字,能夠在接口內定義實例方法。注意,這個方法是並不是抽象方法,而是擁有特定邏輯的具體實例方法。

全部的動物都能自由呼吸,因此,這裏能夠再定義一個IAnimal接口,它也包含一個默認方法breath()。

public interface IAnimal {
  default void breath(){
      System.out.println(「breath」);
    }
}

騾是馬和驢的雜交物種,所以騾(Mule)能夠實現爲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實例同時擁有來自不一樣接口的實現方法。在這Java 8以前是作不到的。從某種程度上說,這種模式能夠彌補Java單一繼承的一些不便。但同時也要知道,它也將遇到和多繼承相同的問題,若是IDonkey也存在一個默認的run()方法,那麼同時實現它們的Mule,就會不知所措,由於它不知道應該以哪一個方法爲準。此時,因爲IHorse和IDonkey擁有相同的默認實例方法,故編譯器會拋出一個錯誤:Duplicate default methods named run with the parameters () and () are inherited from the types IDonkey and IHorse

接口默認實現對於整個函數式編程的流式表達式很是重要。好比,你們熟悉的java.util.Comparator接口,它在JDK 1.2時就已經被引入。在Java 8中,Comparator接口新增了若干個默認方法,用於多個比較器的整合。其中一個經常使用的默認以下:

default Comparator<T> thenComparing(Comparator<? super T> other) {
  Objects.requireNonNull(other);
  return (Comparator<T> & Serializable) (c1, c2) -> {
    int res = compare(c1, c2);
    return (res != 0) ? res : other.compare(c1, c2);
  };

}

有個這個默認方法,在進行排序時,咱們就能夠很是方便得進行元素的多條件排序,好比,以下代碼構造一個比較器,它先按照字符串長度排序,繼而按照大小寫不敏感的字母順序排序。

Comparator<String> cmp = Comparator.comparingInt(String::length)
.thenComparing(String.CASE_INSENSITIVE_ORDER);

接口靜態方法:

在接口中,還容許定義靜態的方法。接口中的靜態方法能夠直接用接口來調用。
例如,下面接口中定義了一個靜態方法 find,該方法能夠直接用 StaticFunInterface .find() 來調用。

public interface StaticFunInterface {
public static int find(){
return 1;
}
}
public class TestStaticFun {
public static void main(String[] args){
//接口中定義了靜態方法 find 直接被調用
StaticFunInterface.fine();
}
}

說明:雖然我知道了default方法是爲了便於集合接口(新的StreamsAPI)向後兼容而設計的,可是這個接口靜態方法暫時還沒體會其做用,可能得接觸多了纔會明白吧。

方法引用

  • 靜態方法引用:ClassName::methodName

  • 實例上的實例方法引用:instanceReference::methodName (這裏還可使用this)

  • 超類上的實例方法引用:super::methodName

  • 類型上的實例方法引用:ClassName::methodName

  • 構造方法引用:Class::new

  • 數組構造方法引用:TypeName[]::new

public class InstanceMethodRef {
  public static void main(String[] args) {
    List<User> users=new ArrayList<User>();
    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類的實例方法。在執行時,Java會自動識別流中的元素(這裏指User實例)是做爲調用目標仍是調用方法的參數。
通常來講,若是使用的是靜態方法,或者調用目標明確,那麼流內的元素會自動做爲參數使用。若是函數引用表示實例方法,而且不存在調用目標,那麼流內元素就會自動做爲調用目標。
所以,若是一個類中存在同名的實例方法和靜態函數,那麼編譯器就會感到很困惑,由於此時,它不知道應該使用哪一個方法進行調用。

Stream API

打開 Collection Api能夠看到多了一個 stream() default 方法:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

Stream 容許以聲明方式處理集合等能夠轉換爲 Stream<T> 的數據, 他有不少特色:

  • 內部迭代 :與原有的 Iterator 不一樣, Stream 將迭代操做(相似 for / for-each )所有固化到了Api內部實現, 用戶只需傳入表達計算邏輯的 lambda 表達式(能夠理解爲 Supplier 、 Function 這些的 @FunctionalInterface 的實現), Stream 便會自動迭代- 數據觸發計算邏輯並生成結果. 內部迭代主要解決了兩方面的問題: 避免集合處理時的套路和晦澀 ; 便於庫內部實現的多核並行優化 .

  • 流水線 :不少 Stream 操做會再返回一個 Stream , 這樣多個操做就能夠連接起來, 造成一個大的流水線, 使其看起來像是 對數據源進行數據庫式查詢 , 這也就讓自動優化成爲可能, 如 隱式並行 .

  • 隱式並行 :如將 .stream() 替換爲 .parallelStream() , Stream 則會自動啓用Fork/Join框架, 並行執行各條流水線, 並最終自動將結果進行合併.

  • 延遲計算 :因爲 Stream 大部分的操做(如 filter() 、 generate() 、 map() …)都是接受一段 lambda 表達式, 邏輯相似接口實現(能夠當作是 回調 ), 所以代碼並非當即執行的, 除非流水線上觸發一個終端操做, 不然中間操做不會執行任何處理.

  • 短路求值 :有些操做不須要處理整個流就可以拿到結果, 不少像 anyMatch() 、 allMatch() 、 limit() , 只要找到一個元素他們的工做就能夠結束, 也就沒有必要執行後面的操做, 所以若是後面有大量耗時的操做, 此舉可大大節省性能.

Stream 構成
一個流管道(Stream pipeline)一般由3部分構成: 數據源(Source) -> 中間操做/轉換(Transforming) -> 終端操做/執行(Operations) : Stream 由數據源生成, 經由中間操做串聯起來的一條流水線的轉換, 最後由終端操做觸發執行拿到結果.

一、數據源-Stream生成

除了前面介紹過的 collection.stream() , 流的生成方式多種多樣, 可簡單歸納爲3類: 通用流 、 數值流 、 其餘 , 其中以 通用流最爲經常使用, 數值流是Java爲 int 、 long 、 double 三種數值類型防 拆裝箱 成本所作的優化:
A、通用流

  • Arrays.stream(T[] array)

  • Stream.empty()

  • Stream.generate(Supplier<T> s) 返回無限無序流,其中每一個元素由Supplier<T>生成.

  • Stream.iterate(T seed, UnaryOperator<T> f) 返回無限有序流。

  • Stream.of(T... values)

  • Stream.concat(Stream<? extends T> a, Stream<? extends T> b) 建立一個懶惰鏈接的流,其元素是第一個流的全部元素,後跟第二個流的全部元素.

  • StreamSupport.stream(Spliterator<T> spliterator, boolean parallel) 從Spliterator建立一個新的順序流或並行流。.

B、數值流

  • 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<T> f)

  • 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.一、I/O Stream

  • BufferedReader.lines()

C.二、File Stream

  • Files.lines(Path path)

  • Files.find(Path start, int maxDepth, BiPredicate<Path,BasicFileAttributes> matcher, FileVisitOption... options)

  • DirectoryStream<Path> newDirectoryStream(Path dir)

  • Files.walk(Path start, FileVisitOption... options)

C.三、Jar

  • JarFile.stream()

C.四、Random

  • Random.ints()

  • Random.longs()

  • Random.doubles()

C.五、Pattern

  • splitAsStream(CharSequence input) …

另外, 三種數值流之間, 以及數值流與通用流之間均可以相互轉換:

  • 數值流轉換: doubleStream.mapToInt(DoubleToIntFunction mapper) 、 intStream.asLongStream() …

  • 數值流轉通用流: longStream.boxed() 、 intStream.mapToObj(IntFunction<? extends U> mapper) …

  • 通用流轉數值流: stream.flatMapToInt(Function<? super T,? extends IntStream> mapper) 、 stream.mapToDouble(ToDoubleFunction<? super T> mapper) …

中間操做-Stream轉換

全部的中間操做都會返回另外一個 Stream , 這讓多個操做能夠連接起來組成中間操做鏈, 從而造成一條流水線, 所以它的特色就是前面提到的 延遲執行 : 觸發流水線上觸發一個終端操做, 不然中間操做不執行任何處理.

  • 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<String> strs = Arrays.asList("hello", "alibaba", "world"); 如何列出裏面各不相同的字符呢?

Stream<Stream<String>> streamStream = strs.stream()  
        .map(str -> Arrays.stream(str.split("")));

咱們將 String 分解成 String[] 後再由 Arrays.stream() 將 String[] 映射成 Stream<String> , 但這個結果是咱們不想看到的: 咱們明明想要的是 Stream<String> 卻獲得的是 Stream<Stream<String>> , 他把咱們想要的結果包到 Stream 裏面了. 這時候就須要咱們的 flatMap() 出場了:

Stream<String> stringStream = strs.stream()
        .flatMap(str -> Arrays.stream(str.split("")));

flatMap() 把 Stream 中的層級結構扁平化了, 將內層 Stream 內的元素抽取出來, 最終新的 Stream 就沒有內層 Stream 了.
能夠簡單歸納爲: flatMap() 方法讓你把一個流中的每一個值都換成另外一個 Stream , 而後把全部的 Stream 鏈接起來成爲一個 Stream .

終端操做-Stream執行

終端操做不只擔負着觸發流水線執行的任務, 他還須要拿到流水線執行的結果, 其結果爲任何不是流的值.

  • 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<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.

  • 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 進行彙總了的結果.

java.util.stream.Stream 接口繼承自 java.util.stream.BaseStream 接口, 而 BaseStream 接口也提供了不少工具方法(如將串行流轉換爲並行流的 parallel() 方法)供咱們使用:

  • S onClose(Runnable closeHandler) Returns an equivalent stream with an additional close handler .

  • void close()

  • S unordered()

  • Iterator<T> iterator()

  • Spliterator<T> spliterator() Returns a spliterator for the elements of this stream.

  • 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()方法返回了一個流對象。相似於集合或者數組,流對象也是一個對象的集合,它將給予咱們遍歷處理流內元素的功能
  Arrays.stream(arr).forEach((x)->System.out.println(x));
}

複雜點的:

public void joiningList() {
    // 生成一段[0,20)序列
    List<Integer> list = IntStream.range(0, 20)
            .boxed()
            .collect(Collectors.toList());

    // 將list內的偶數提取反向排序後聚合爲一個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<Transaction> 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年發生的全部交易, 並按交易額排序(從低到高)
        transactions.stream()
                .filter(transaction -> transaction.getYear() == 2011)
                .sorted(Comparator.comparing(Transaction::getValue))
                .forEachOrdered(System.out::println);

        // 2. 找出交易員都在哪些不一樣的城市工做過
        Set<String> 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<Transaction> min = transactions.stream()
                .min(Comparator.comparingInt(Transaction::getValue));
        System.out.println(min.orElseThrow(IllegalArgumentException::new));
    }
}

在Java8中,能夠在接口不變的狀況下,將流改成並行流。這樣,就能夠很天然地使用多線程進行集合中的數據處理。

並行流與並行排序

demo:咱們但願能夠統計一個1~1000000內全部的質數的數量。

IntStream.range(1,1000000).parallel().filter(PrimeUtil::isPrime).count();

從集合獲得並行流

List<Student> ss =new ArrayList<Student>();
...
double ave = ss.parallelStream().mapToInt(s->s.score).average().getAsDouble();

int[]arr = new int [10000000];
Arrarys.parallelSort(arr);

進階:本身生成流

一、Stream.generate

經過實現 Supplier 接口,你能夠本身來控制流的生成。這種情形一般用於隨機數、常量的 Stream,或者須要先後元素間維持着某種狀態信息的 Stream。把 Supplier 實例傳遞給 Stream.generate() 生成的 Stream,默認是串行(相對 parallel 而言)但無序的(相對 ordered 而言)。因爲它是無限的,在管道中,必須利用 limit 之類的操做限制 Stream 大小。

//生成 10 個隨機整數
Random seed = new Random();
Supplier<Integer> random = 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)。

二、Stream.iterate

iterate 跟 reduce 操做很像,接受一個種子值,和一個 UnaryOperator(例如 f)。而後種子值成爲 Stream 的第一個元素,f(seed) 爲第二個,f(f(seed)) 第三個,以此類推。

//生成一個等差數列 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 操做,例如轉變輸出爲 Collection,把 Stream 元素進行歸組。
一、groupingBy/partitioningBy

//按照年齡歸組
Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()).
 limit(100).
 collect(Collectors.groupingBy(Person::getAge));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
 Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
 System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
}


//按照未成年人和成年人歸組
Map<Boolean, List<Person>> 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,它依照條件測試的是否兩種結果來構造返回的數據結構,get(true) 和 get(false) 能即爲所有的元素對象。

Stream總結

其實這裏不少觀念和Spark的RDD操做很類似。
總之,Stream 的特性能夠概括爲:

  • 不是數據結構,它沒有內部存儲,它只是用操做管道從 source(數據結構、數組、generator function、IO channel)抓取數據。

  • 它也毫不修改本身所封裝的底層數據結構的數據。例如 Stream 的 filter 操做會產生一個不包含被過濾元素的新 Stream,而不是從 source 刪除那些元素。

  • 全部 Stream 的操做必須以 lambda 表達式爲參數

  • 不支持索引訪問,你能夠請求第一個元素,但沒法請求第二個,第三個,或最後一個。

  • 很容易生成數組或者 List

  • 惰性化,Intermediate 操做永遠是惰性化的。

  • 不少 Stream 操做是向後延遲的,一直到它弄清楚了最後須要多少數據纔會開始。

  • 並行能力,當一個 Stream 是並行化的,就不須要再寫多線程代碼,全部對它的操做會自動並行進行的。

  • 能夠是無限的,集合有固定大小,Stream 則沒必要。limit(n) 和 findFirst() 這類的 short-circuiting 操做能夠對無限的 Stream 進行運算並很快完成。

註解的更新

對於註解,Java 8 主要有兩點改進:類型註解和重複註解。
Java 8 的類型註解擴展了註解使用的範圍。在該版本以前,註解只能是在聲明的地方使用。如今幾乎能夠爲任何東西添加註解:局部變量、類與接口,就連方法的異常也能添加註解。新增的兩個註釋的程序元素類型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER 用來描述註解的新場合。
ElementType.TYPE_PARAMETER 表示該註解能寫在類型變量的聲明語句中。而 ElementType.TYPE_USE 表示該註解能寫在使用類型的任何語句中(例如聲明語句、泛型和強制轉換語句中的類型)。
對類型註解的支持,加強了經過靜態分析工具發現錯誤的能力。原先只能在運行時發現的問題能夠提早在編譯的時候被排查出來。Java 8 自己雖然沒有自帶類型檢測的框架,但能夠經過使用 Checker Framework 這樣的第三方工具,自動檢查和確認軟件的缺陷,提升生產效率。

在Java8以前使用註解的一個限制是相同的註解在同一位置只能聲明一次,不能聲明屢次。Java 8 引入了重複註解機制,這樣相同的註解能夠在同一地方聲明屢次。重複註解機制自己必須用 @Repeatable 註解。

IO/NIO 的改進

增長了一些新的 IO/NIO 方法,使用這些方法能夠從文件或者輸入流中獲取流(java.util.stream.Stream),經過對流的操做,能夠簡化文本行處理、目錄遍歷和文件查找。
新增的 API 以下:

  • BufferedReader.line(): 返回文本行的流 Stream<String>

  • File.lines(Path, Charset):返回文本行的流 Stream<String>

  • 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 建立優秀的 API。新的 java.time 中包含了全部關於時鐘(Clock),本地日期(LocalDate)、本地時間(LocalTime)、本地日期時間(LocalDateTime)、時區(ZonedDateTime)和持續時間(Duration)的類。歷史悠久的 Date 類新增了 toInstant() 方法,用於把 Date 轉換成新的表示形式。這些新增的本地化時間日期 API 大大簡化了了日期時間和本地化的管理。

Java日期/時間API包含如下相應的包。

  • java.time包:這是新的Java日期/時間API的基礎包,全部的主要基礎類都是這個包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。全部這些類都是不可變的和線程安全的,在絕大多數狀況下,這些類可以有效地處理一些公共的需求。

  • java.time.chrono包:這個包爲非ISO的日曆系統定義了一些泛化的API,咱們能夠擴展AbstractChronology類來建立本身的日曆系統。

  • java.time.format包:這個包包含可以格式化和解析日期時間對象的類,在絕大多數狀況下,咱們不該該直接使用它們,由於java.time包中相應的類已經提供了格式化和解析的方法

  • java.time.temporal包:這個包包含一些時態對象,咱們能夠用其找出關於日期/時間對象的某個特定日期或時間,好比說,能夠找到某月的第一天或最後一天。你能夠很是容易地認出這些方法,由於它們都具備「withXXX」的格式。

  • java.time.zone包:這個包包含支持不一樣時區以及相關規則的類。

例如,下面是對 LocalDate,LocalTime 的簡單應用:

//LocalDate只保存日期系統的日期部分,有時區信息,LocalTime只保存時間部分,沒有時區信息。LocalDate和LocalTime均可以從Clock對象建立。
//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();//獲取系統默認時區 (當前瞬時時間 )
long millis = clock.millis();//

//LocalDateTime類合併了LocalDate和LocalTime,它保存有ISO-8601日期系統的日期和時間,可是沒有時區信息。
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
System.out.println( datetime );
System.out.println( datetimeFromClock );

//若是您須要一個類持有日期時間和時區信息,可使用ZonedDateTime,它保存有ISO-8601日期系統的日期和時間,並且有時區信息。
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日)之間的持續時間(基於天數和小時)

日期API操做

//日期算術操做,多很多天期/時間API類都實現了一系列工具方法,如:加/減天數、週數、月份數,等等。還有其餘的工具方法可以使用TemporalAdjuster調整日期,並計算兩個日期間的週期。
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());

解析和格式化:將一個日期格式轉換爲不一樣的格式,以後再解析一個字符串,獲得日期時間對象

//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);

舊的日期時間支持轉換:

//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強大的函數式異步編程輔助類

Future是Java 5添加的類,用來描述一個異步計算的結果。你可使用isDone方法檢查計算是否完成,或者使用get阻塞住調用線程,直到計算完成返回結果,你也可使用cancel方法中止任務的執行。
雖然Future以及相關使用方法提供了異步執行任務的能力,可是對於結果的獲取倒是很不方便,只能經過阻塞或者輪詢的方式獲得任務的結果。阻塞的方式顯然和咱們的異步編程的初衷相違背,輪詢的方式又會耗費無謂的CPU資源,並且也不能及時地獲得計算結果,爲何不能用觀察者設計模式當計算結果完成及時通知監聽者呢?
其實Google guava早就提供了通用的擴展Future:ListenableFuture、SettableFuture 以及輔助類Futures等,方便異步編程。

final String name = ...;
inFlight.add(name);
ListenableFuture<Result> future = 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的擴展功能,能夠幫助咱們簡化異步編程的複雜性,提供了函數式編程的能力,能夠經過回調的方式處理計算結果,而且提供了轉換和組合CompletableFuture的方法。
CompletableFuture類實現了CompletionStage和Future接口,因此你仍是能夠像之前同樣經過阻塞或者輪詢的方式得到結果,儘管這種方式不推薦使用。
儘管Future能夠表明在另外的線程中執行的一段異步代碼,可是你仍是能夠在自己線程中執行:

public class BasicMain {
    public static CompletableFuture<Integer> compute() {
        final CompletableFuture<Integer> future = new CompletableFuture<>();
        return future;
    }
    public static void main(String[] args) throws Exception {
        final CompletableFuture<Integer> f = compute();
        class Client extends Thread {
            CompletableFuture<Integer> f;
            Client(String threadName, CompletableFuture<Integer> 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沒有關聯任何的Callback、線程池、異步任務等,若是客戶端調用future.get就會一致傻等下去。除非咱們顯式指定complete
        f.complete(100);
        //f.completeExceptionally(new Exception());
        System.in.read();
    }
}

CompletableFuture.complete()、CompletableFuture.completeExceptionally只能被調用一次。可是咱們有兩個後門方法能夠重設這個值:obtrudeValue、obtrudeException,可是使用的時候要當心,由於complete已經觸發了客戶端,有可能致使客戶端會獲得不指望的結果。

一、建立CompletableFuture對象。

  • public static <U> CompletableFuture<U> completedFuture(U value)

  • public static CompletableFuture<Void> runAsync(Runnable runnable)

  • public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)

  • public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)

  • public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
    注意:以Async結尾而且沒有指定Executor的方法會使用ForkJoinPool.commonPool()做爲它的線程池執行異步代碼。由於方法的參數類型都是函數式接口,因此可使用lambda表達式實現異步任務

二、計算結果完成時的處理
當CompletableFuture的計算結果完成,或者拋出異常的時候,咱們能夠執行特定的Action。主要是下面的方法:

  • public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)

  • public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)

  • public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)

  • public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
    注意:方法不以Async結尾,意味着Action使用相同的線程執行,而Async可能會使用其它的線程去執行(若是使用相同的線程池,也可能會被同一個線程選中執行)

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<Integer> future = CompletableFuture.supplyAsync(Main::getMoreData);
        Future<Integer> f = future.whenComplete((v, e) -> {
            System.out.println(v);
            System.out.println(e);
        });
        System.out.println(f.get());
        System.in.read();
    }
}

下面一組方法雖然也返回CompletableFuture對象,可是對象的值和原來的CompletableFuture計算的值不一樣。當原先的CompletableFuture的值計算完成或者拋出異常的時候,會觸發這個CompletableFuture對象的計算,結果由BiFunction參數計算而得。
所以這組方法兼有whenComplete和轉換的兩個功能。

  • public <U> CompletableFuture<U> handle(BiFunction<? super T,Throwable,? extends U> fn)

  • public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)

  • public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)
    一樣,不以Async結尾的方法由原來的線程計算,以Async結尾的方法由默認的線程池ForkJoinPool.commonPool()或者指定的線程池executor運行。

三、轉換
CompletableFuture能夠做爲monad(單子)和functor。因爲回調風格的實現,咱們沒必要由於等待一個計算完成而阻塞着調用線程,而是告訴CompletableFuture當計算完成的時候請執行某個function。並且咱們還能夠將這些操做串聯起來,或者將CompletableFuture組合起來。

  • public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)

  • public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)

  • public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
    這一組函數的功能是當原來的CompletableFuture計算完後,將結果傳遞給函數fn,將fn的結果做爲新的CompletableFuture計算結果。所以它的功能至關於將CompletableFuture<T>轉換成CompletableFuture<U>

它們與handle方法的區別在於handle方法會處理正常計算值和異常,所以它能夠屏蔽異常,避免異常繼續拋出。而thenApply方法只是用來處理正常值,所以一旦有異常就會拋出。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture<String> f =  future.thenApplyAsync(i -> i * 10).thenApply(i -> i.toString());
System.out.println(f.get()); //"1000"

須要注意的是,這些轉換並非立刻執行的,也不會阻塞,而是在前一個stage完成後繼續執行。

四、純消費(執行Action)
上面的方法是當計算完成的時候,會生成新的計算結果(thenApply, handle),或者返回一樣的計算結果whenComplete,CompletableFuture還提供了一種處理結果的方法,只對結果執行Action,而不返回新的計算值,所以計算值爲Void:

  • public CompletableFuture<Void> thenAccept(Consumer<? super T> action)

  • public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)

  • public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)
    看它的參數類型也就明白了,它們是函數式接口Consumer,這個接口只有輸入,沒有返回值。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture<Void> f =  future.thenAccept(System.out::println);
System.out.println(f.get());

thenAcceptBoth以及相關方法提供了相似的功能,當兩個CompletionStage都正常完成計算的時候,就會執行提供的action,它用來組合另一個異步的結果。
runAfterBoth是當兩個CompletionStage都正常完成計算的時候,執行一個Runnable,這個Runnable並不使用計算的結果。

  • public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)

  • public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)

  • public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action, Executor executor)

  • public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action)

例子以下:

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture<Void> f =  future.thenAcceptBoth(CompletableFuture.completedFuture(10), (x, y) -> System.out.println(x * y));
System.out.println(f.get());

更完全地,下面一組方法當計算完成的時候會執行一個Runnable,與thenAccept不一樣,Runnable並不使用CompletableFuture計算的結果。

  • public CompletableFuture<Void> thenRun(Runnable action)

  • public CompletableFuture<Void> thenRunAsync(Runnable action)

  • public CompletableFuture<Void> thenRunAsync(Runnable action, Executor executor)

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture<Void> f =  future.thenRun(() -> System.out.println("finished"));
System.out.println(f.get());

所以,你能夠根據方法的參數的類型來加速你的記憶。Runnable類型的參數會忽略計算的結果,Consumer是純消費計算結果,BiConsumer會組合另一個CompletionStage純消費,Function會對計算結果作轉換,BiFunction會組合另一個CompletionStage的計算結果作轉換。

五、組合compose

  • public <U> CompletableFuture<U> thenCompose(Function<? super T,? extends CompletionStage<U>> fn)

  • public <U> CompletableFuture<U> thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn)

  • public <U> CompletableFuture<U> thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn, Executor executor)
    這一組方法接受一個Function做爲參數,這個Function的輸入是當前的CompletableFuture的計算值,返回結果將是一個新的CmpletableFuture,這個新的CompletableFuture會組合原來的CompletableFuture和函數返回的CompletableFuture。

  • public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)

  • public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)

  • public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor)
    兩個CompletionStage是並行執行的,它們之間並無前後依賴順序,other並不會等待先前的CompletableFuture執行完畢後再執行。

六、Either
thenAcceptBoth和runAfterBoth是當兩個CompletableFuture都計算完成,而咱們下面要了解的方法是當任意一個CompletableFuture計算完成的時候就會執行。

  • public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)

  • public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)

  • public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)

  • public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn)

  • public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn)

  • public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn, Executor executor)
    下面這個例子有時會輸出100,有時候會輸出200,哪一個Future先完成就會根據它的結果計算。

Random rand = new Random();
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(10000 + rand.nextInt(1000));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return 100;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(10000 + rand.nextInt(1000));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return 200;
});
CompletableFuture<String> f =  future.applyToEither(future2,i -> i.toString());

七、輔助方法 allOf 和 anyOf
用來組合多個CompletableFuture。

  • public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

  • public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
    allOf方法是當全部的CompletableFuture都執行完後執行計算。

anyOf方法是當任意一個CompletableFuture執行完後就會執行計算,計算的結果相同。

八、更進一步
若是你用過Guava的Future類,你就會知道它的Futures輔助類提供了不少便利方法,用來處理多個Future,而不像Java的CompletableFuture,只提供了allOf、anyOf兩個方法。
好比有這樣一個需求,將多個CompletableFuture組合成一個CompletableFuture,這個組合後的CompletableFuture的計算結果是個List,它包含前面全部的CompletableFuture的計算結果,guava的Futures.allAsList能夠實現這樣的功能.
可是對於java CompletableFuture,咱們須要一些輔助方法:

public static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
       CompletableFuture<Void> allDoneFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
       return allDoneFuture.thenApply(v -> futures.stream().map(CompletableFuture::join).collect(Collectors.<T>toList()));
   }
public static <T> CompletableFuture<Stream<T>> sequence(Stream<CompletableFuture<T>> futures) {
       List<CompletableFuture<T>> futureList = futures.filter(f -> f != null).collect(Collectors.toList());
       return sequence(futureList);
   }

Java Future轉CompletableFuture:

public static <T> CompletableFuture<T> toCompletable(Future<T> future, Executor executor) {
    return CompletableFuture.supplyAsync(() -> {
        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    }, executor);
}

github有多個項目能夠實現Java CompletableFuture與其它Future (如Guava ListenableFuture)之間的轉換,如spotify/futures-extra、future-converter、scala/scala-java8-compat 等。

其餘

Java 8 Optional類

你們可能都有這樣的經歷:調用一個方法獲得了返回值卻不能直接將返回值做爲參數去調用別的方法。咱們首先要判斷這個返回值是否爲null,只有在非空的前提下才能將其做爲其餘方法的參數。
Java 8引入了一個新的Optional類。Optional類的Javadoc描述以下:這是一個能夠爲null的容器對象。若是值存在則isPresent()方法會返回true,調用get()方法會返回該對象。
一、of:爲非null的值建立一個Optional。須要注意的是,建立對象時傳入的參數不能爲null。若是傳入參數爲null,則拋出NullPointerException

//調用工廠方法建立Optional實例
Optional<String> name = Optional.of("Sanaulla");
//錯誤寫法,傳入參數爲null,拋出NullPointerException.
Optional<String> someNull = Optional.of(null);

二、ofNullable:爲指定的值建立一個Optional,若是指定的值爲null,則返回一個空的Optional。
ofNullable與of方法類似,惟一的區別是能夠接受參數爲null的狀況
三、isPresent:若是值存在返回true,不然返回false。
四、get:若是Optional有值則將其返回,不然拋出NoSuchElementException。
五、ifPresent:若是Optional實例有值則爲其調用consumer,不然不作處理
要理解ifPresent方法,首先須要瞭解Consumer類。簡答地說,Consumer類包含一個抽象方法。該抽象方法對傳入的值進行處理,但沒有返回值。Java8支持不用接口直接經過lambda表達式傳入參數。

//ifPresent方法接受lambda表達式做爲參數。
//lambda表達式對Optional的值調用consumer進行處理。
name.ifPresent((value) -> {
  System.out.println("The length of the value is: " + value.length());
});

六、orElse:若是有值則將其返回,不然返回orElse方法傳入的參數。
七、orElseGet:orElseGet與orElse方法相似,區別在於獲得的默認值。orElse方法將傳入的字符串做爲默認值,orElseGet方法能夠接受Supplier接口的實現用來生成默認值。示例以下:

//orElseGet與orElse方法相似,區別在於orElse傳入的是默認值,
//orElseGet能夠接受一個lambda表達式生成默認值。
//輸出:Default Value
System.out.println(empty.orElseGet(() -> "Default Value"));
//輸出:Sanaulla
System.out.println(name.orElseGet(() -> "Default Value"));

八、orElseThrow:若是有值則將其返回,不然拋出supplier接口建立的異常。
在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());
}

九、map:若是有值,則對其執行調用mapping函數獲得返回值。若是返回值不爲null,則建立包含mapping返回值的Optional做爲map方法返回值,不然返回空Optional。
map方法用來對Optional實例的值執行一系列操做。

//map方法執行傳入的lambda表達式參數對Optional實例的值進行修改。
//爲lambda表達式的返回值建立新的Optional實例做爲map方法的返回值。
Optional<String> upperName = name.map((value) -> value.toUpperCase());
System.out.println(upperName.orElse("No value found"));

十、flatMap:若是有值,爲其執行mapping函數返回Optional類型返回值,不然返回空Optional。flatMap與map(Funtion)方法相似,區別在於flatMap中的mapper返回值必須是Optional。調用結束時,flatMap不會對結果用Optional封裝。

//flatMap與map(Function)很是相似,區別在於傳入方法的lambda表達式的返回類型。
//map方法中的lambda表達式返回值能夠是任意類型,在map函數返回以前會包裝爲Optional。 
//但flatMap方法中的lambda表達式返回值必須是Optionl實例。 
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName.orElse("No value found"));//輸出SANAULLA

十一、filter:若是有值而且知足斷言條件返回包含該值的Optional,不然返回空Optional。

//filter方法檢查給定的Option值是否知足某些條件。
//若是知足則返回同一個Option實例,不然返回空Optional。
Optional<String> longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters"));//輸出Sanaulla
 
//另外一個例子是Optional值不知足filter指定的條件。
Optional<String> anotherName = Optional.of("Sana");
Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
//輸出:name長度不足6字符
System.out.println(shortName.orElse("The name is less than 6 characters"));

Java8編譯器的新特性

一、參數名字
很長時間以來,Java程序員想盡辦法把參數名字保存在java字節碼裏,而且讓這些參數名字在運行時可用。Java 8 終於把這個需求加入到了Java語言(使用反射API和Parameter.getName() 方法)和字節碼裏(使用java編譯命令javac的–parameters參數)。

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() 來驗證參數名是否是可用。

若是你編譯這個class的時候沒有添加參數–parameters,運行的時候你會獲得這個結果:
Parameter: arg0
編譯的時候添加了–parameters參數的話,運行結果會不同:
Parameter: args
對於有經驗的Maven使用者,–parameters參數能夠添加到maven-compiler-plugin的配置部分:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
        <compilerArgument>-parameters</compilerArgument>
        <source>1.8</source>
        <target>1.8</target>
    </configuration>
</plugin>

eclipse中也能夠進行相關配置:

Nashorn javascript引擎

Java 8提供了一個新的Nashorn javascript引擎,它容許咱們在JVM上運行特定的javascript應用。Nashorn javascript引擎只是javax.script.ScriptEngine另外一個實現,並且規則也同樣,容許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的編碼解碼。
(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder()).

並行數組

Java 8新增長了不少方法支持並行的數組處理。最重要的大概是parallelSort()這個方法顯著地使排序在多核計算機上速度加快。下面的小例子演示了這個新的方法(parallelXXX)的行爲。

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
 
public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];        
 
        Arrays.parallelSetAll( arrayOfLong,
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();
 
        Arrays.parallelSort( arrayOfLong );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
}

這一小段代碼使用parallelSetAll() t方法填充這個長度是2000的數組,而後使用parallelSort() 排序。這個程序輸出了排序前和排序後的10個數字來驗證數組真的已經被排序了。

併發工具

在新增Stream機制與lambda的基礎之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法來支持彙集操做。同時也在java.util.concurrent.ForkJoinPool類中加入了一些新方法來支持共有資源池(common pool)
新增的java.util.concurrent.locks.StampedLock類提供一直基於容量的鎖,這種鎖有三個模型來控制讀寫操做(它被認爲是不太有名的java.util.concurrent.locks.ReadWriteLock類的替代者)。
在java.util.concurrent.atomic包中還增長了下面這些類:

  • DoubleAccumulator

  • DoubleAdder

  • LongAccumulator

  • LongAdder

類依賴分析工具:jdeps

Jdeps是一個功能強大的命令行工具,它能夠幫咱們顯示出包層級或者類層級java類文件的依賴關係。它接受class文件、目錄、jar文件做爲輸入,默認狀況下,jdeps會輸出到控制檯。

JVM的新特性

JVM內存永久區(Perm)已經被metaspace替換(JEP 122)。JVM參數 -XX:PermSize 和 –XX:MaxPermSize被XX:MetaSpaceSize 和 -XX:MaxMetaspaceSize代替。

參考:

相關文章
相關標籤/搜索