Java8 通關攻略

點贊+收藏 就學會系列,文章收錄在 GitHub JavaEgg ,N線互聯網開發必備技能兵器譜javascript

Java8早在2014年3月就發佈了,6年了,你有對它作個全面的瞭解嗎html

本文是用我拙劣的英文和不要臉的這抄抄那抄抄,熬出來的,沒有深究源碼,只是對 Java8 有一個總體的認知,能夠上手用起來,示例代碼也都在github上java

JDK 8 有什麼新功能

  • Java 編程語言( Java Programming Language)git

    • Lambda表達式:一個新的語言特性, 它們使您可以將函數視爲方法參數,或將代碼視爲數據
    • 方法引用: 方法引用爲已經有名稱的方法提供易於閱讀的lambda表達式
    • 默認方法:使用 default 關鍵字爲接口定義默認方法(有實現的方法)
    • 重複註解提供了將同一註解屢次應用於同一聲明或類型使用的能力
    • 類型註解提供了在使用類型的任何地方應用註解的能力,而不只僅是在聲明上
    • Java8 加強了類型推斷
    • 方法參數反射
    • java.util.function: 一個新的包,它包含爲lambda表達式和方法引用提供目標類型的通用功能接口
  • 集合(Collections)程序員

    • java.util.stream包中新增了 Stream API ,用來支持對元素流的函數式操做
    • 改進了有鍵衝突問題的 HashMap
  • 精簡運行時(Compact Profiles)github

  • 安全性(Security)數據庫

  • JavaFXexpress

  • Tools(包含一些調用Nashorn引擎、 啓動JavaFX應用程序等等 )apache

  • 國際化(Internationalization)編程

    • Unicode加強,包括對Unicode 6.2.0的支持
    • 提供了新的 Calendar 和 Locale API
  • 部署(Deployment)

  • 日期-時間 包(Date-Time Package):提供了更全面的時間和日期操做

  • 腳本(Scripting):Java 8提供了一個新的 Nashorn javascript 引擎(取代了Nashorn javascript引擎),它容許咱們在JVM上運行特定的 javascript 應用

  • 改進 IO 和 NIO

  • 改進 java.langjava.util

    • 支持數組並行排序
    • 支持Base64 的編碼和解碼
    • 支持 無符號運算
    • Optional 類 :最大化減小空指針異常
  • JDBC

    • JDBC-ODBC橋已被移除
    • JDBC 4.2引入了新的特性
  • Java DB(一個Java數據庫)

  • 網絡(Networking)

    • 新增了 java.net.URLPermission
  • 併發(Concurrency

    • CompletableFuture 加強了以前的Future
    • java.util.concurrent.ConcurrentHashMap 支持基於新添加的streams功能和lambda表達式的聚合操做
    • java.util.concurrent.atomic 提供了一組原子變量類, 對於單個變量支持無鎖、線程安全操做的工具類
    • java.util.concurrent.ForkJoinPool 用於補充ExecutorService
    • java.util.concurrent.locks.StampedLock 提供了基於功能的鎖,有三種模式用於控制讀/寫訪問
  • JVM: 移除了 PermGen ,取而代之的是Metaspace

Java8 特別強大的是Lambda 表達式和Stream,經過它兩新增和加強了不少包

新增: java.lang.invokejava.util.functionjava.util.stream

修改:

modify-class.png


1、Lambda表達式

能夠把 Lambda 表達式理解爲簡潔的表示可傳遞的匿名函數的一種方式,Lambda表達式基於數學中的λ演算得名:它沒有名稱,但有參數列表、函數主體、返回類型,可能還有一個能夠拋出的異常列表。

  • 匿名——匿名函數(即沒有函數名的函數),不像普通的方法有一個明確的名稱,「寫得少,想得多」
  • 函數——Lambda函數不像方法那樣屬於某個特定的類,但同樣有參數列表、函數主體和返回類型
  • 傳遞——Lambda表達式能夠做爲參數傳遞給方法或者存儲在變量中
  • 簡潔——無需像匿名類那樣寫不少模板代碼

Lambda表達式使您可以封裝單個行爲單元並將其傳遞給其餘代碼。若是但願對集合的每一個元素、流程完成時或流程遇到錯誤時執行某個操做,可使用lambda表達式。

1. 爲何要使用Lambda表達式

Lambda 是一個匿名函數,咱們能夠把 Lambda表達式理解爲是一段能夠傳遞的代碼(將代碼像數據同樣進行傳遞——行爲參數化)。能夠寫出更簡潔、更靈活的代碼。做爲一種更緊湊的代碼風格,使Java的語言表達能力獲得了提高。

匿名類的一個問題是,若是您的匿名類的實現很是簡單,例如一個接口只包含一個方法,那麼匿名類的語法可能看起來很笨拙和不清楚。在這些狀況下,您一般試圖將功能做爲參數傳遞給另外一個方法,例如當有人單擊按鈕時應該採起什麼操做。Lambda表達式容許您這樣作,將功能視爲方法參數,或將代碼視爲數據。

hello-lambda

2. Lambda 表達式語法

(parameters) -> expression(parameters) ->{ statements; }

Lambda 表達式在 Java 語言中引入了一個新的語法元素和操做符。這個操做符爲 -> , 該操做符被稱爲 Lambda 操做符或剪頭操做符。它將 Lambda 分爲兩個部分:

  • 左側:指定了 Lambda 表達式須要的全部參數

  • 右側:指定了 Lambda 體,即 Lambda 表達式要執行的功能

eg(錯誤示範):

(Integer i) -> return "hello"+i;   //錯誤的Lambda,return是一個控制流語句,須要{}
(String s) -> {"hello";}    //「hello」是一個表達式,不是語句,不須要{},能夠寫成{return 「hello」;}
複製代碼

eg(正確示範):

  1. 無參,無返回值,Lambda 體只需一條語句

    Runnable runnable = () -> System.out.println("hello lambda");
    複製代碼
  2. Lambda 須要一個參數

    Consumer<String> consumer = (args) -> System.out.println(args);
    複製代碼

    Lambda 只須要一個參數時,參數的小括號能夠省略

    Consumer<String> consumer = args -> System.out.println(args);
    複製代碼
  3. Lambda 須要兩個參數,而且有返回值

    BinaryOperator<Long> binaryOperator = (Long x,Long y) -> {
    	System.out.println("實現函數接口方法");
    	return x +y;
    };
    複製代碼

    參數的數據類型可省略,Java8加強了類型推斷,且當 Lambda 體只有一條語句時,return 與大括號能夠省略

    BinaryOperator<Long> binaryOperator = (x, y) -> x + y;
    複製代碼

類型推斷

上述 Lambda 表達式中的參數類型都是由編譯器推斷得出的。Lambda 表達式中無需指定類型,程序依然能夠編譯,這是由於 javac 根據程序的上下文,在後臺推斷出了參數的類型。Lambda 表達式的類型依賴於上下文環境,是由編譯器推斷出來的。這就是所謂的「類型推斷」。Java7中引入的菱形運算符<>),就是利用泛型從上下文推斷類型。

List<String> list = new ArrayList<>();
複製代碼

3. Lambda表達式實例

官方提供的示例,假設你要開發一個社交軟件,那個缺打的PM整天改需求,今天要查詢出成年用戶的信息,明天又要查詢成年女性的用戶信息,後天又要按各類奇怪的搜索條件查詢。

這時的程序員:從簡單的用戶遍歷比較方法改成通用的搜索方法到後來都用上了工廠模式,等到第7天的時候,你不耐煩了,瑪德,每一個條件就一句話,我寫了7個類,我可不想作CtrlCV工程師,這時候Lambda表達式是你的不二之選。

行爲參數化就是能夠幫助你處理頻繁變動的需求的一種軟件開發模式。

官方提供的demo,一步步告訴你使用Java8的好處(從值參數化到行爲參數化)。代碼

import java.util.List;
import java.util.ArrayList;
import java.time.chrono.IsoChronology;
import java.time.LocalDate;
public class Person {
    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    Person(String nameArg, LocalDate birthdayArg,
           Sex genderArg, String emailArg) {
        name = nameArg;
        birthday = birthdayArg;
        gender = genderArg;
        emailAddress = emailArg;
    }

    public int getAge() {
        return birthday
                .until(IsoChronology.INSTANCE.dateNow())
                .getYears();
    }

    public void printPerson() {
        System.out.println(name + ", " + this.getAge());
    }

    public Sex getGender() {
        return gender;
    }

    public String getName() {
        return name;
    }

    public String getEmailAddress() {
        return emailAddress;
    }

    public LocalDate getBirthday() {
        return birthday;
    }

    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }

    public static List<Person> createRoster() {
        List<Person> roster = new ArrayList<>();
        roster.add(new Person(
                        "Fred",
                        IsoChronology.INSTANCE.date(1980, 6, 20),
                        Person.Sex.MALE,
                        "fred@example.com"));
        roster.add(new Person(
                        "Jane",
                        IsoChronology.INSTANCE.date(1990, 7, 15),
                        Person.Sex.FEMALE, "jane@example.com"));
        roster.add(new Person(
                        "George",
                        IsoChronology.INSTANCE.date(1991, 8, 13),
                        Person.Sex.MALE, "george@example.com"));
        roster.add(new Person(
                        "Bob",
                        IsoChronology.INSTANCE.date(2000, 9, 12),
                        Person.Sex.MALE, "bob@example.com"));
        return roster;
    }
}

複製代碼

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

public class RosterTest {
    interface CheckPerson {
        boolean test(Person p);
    }

    /** * 1. eg:輸出年齡大於20歲的成員 * 匹配符合某一特徵的成員的方法 * 若是老闆要年齡在某一區間的成員呢?接着換方法 */
    public static void printPersonsOlderThan(List<Person> roster, int age) {
        for (Person p : roster) {
            if (p.getAge() >= age) {
                p.printPerson();
            }
        }
    }

    /** * 2. eg:輸出年齡在14到30歲之間的成員 * 更全面的匹配方法 * 若是老闆只要男性成員呢? */
    public static void printPersonsWithinAgeRange( List<Person> roster, int low, int high) {
        for (Person p : roster) {
            if (low <= p.getAge() && p.getAge() < high) {
                p.printPerson();
            }
        }
    }

    /** * 3. eg:老闆又提出了各類複雜的需求,不要處女座的、只要郵箱是163的,怎麼搞? * 方法1:在本地類中指定搜索條件代碼,經過接口方式,不一樣的需求對應不一樣的實現類, * 每次都要新建實現類,寫大量的代碼 * 方法2:在匿名類中指定搜索條件代碼,不須要寫各類實現,可是還要寫個interface CheckPerson, * 並且匿名類寫起來也挺麻煩 * 方法3:Lambda表達式是懶人的不二之選,CheckPerson是一個只包含一個抽象方法的接口, * 比較簡單,Lambda能夠省略其實現 */
    public static void printPersons( List<Person> roster, CheckPerson tester) {
        for (Person p : roster) {
            if (tester.test(p)) {
                p.printPerson();
            }
        }
    }

    /** * 4. eg: 搞這麼久,還得寫一個接口,並且是隻有一個抽象方法,仍是不爽? * 你也可使用標準的函數接口來代替接口CheckPerson,從而進一步減小所需的代碼量 * java.util.function包中定義了標準的函數接口 * 咱們可使用JDK8提供的 Predicate<T>接口來代替CheckPerson。 * 該接口包含方法boolean test(T t) */
    public static void printPersonsWithPredicate( List<Person> roster, Predicate<Person> tester) {
        for (Person p : roster) {
            if (tester.test(p)) {
                p.printPerson();
            }
        }
    }

    /** * 5. Lambda表達式可不僅是可以簡化匿名類 * 簡化 p.printPerson(), * 使用Consumer<T>接口的void accept(T t)方法,至關於入參的操做 */
    public static void processPersons( List<Person> roster, Predicate<Person> tester, Consumer<Person> block) {
        for (Person p : roster) {
            if (tester.test(p)) {
                block.accept(p);
            }
        }
    }

    /** * 6. eg: 老闆說了只想看到郵箱 * Function<T,R>接口,至關於輸入類型,mapper定義參數,block負責方對給定的參數進行執行 */
    public static void processPersonsWithFunction( List<Person> roster, Predicate<Person> tester, Function<Person, String> mapper, Consumer<String> block) {
        for (Person p : roster) {
            if (tester.test(p)) {
                String data = mapper.apply(p);
                block.accept(data);
            }
        }
    }

    // 7. 使用泛型
    public static <X, Y> void processElements( Iterable<X> source, Predicate<X> tester, Function<X, Y> mapper, Consumer<Y> block) {
        for (X p : source) {
            if (tester.test(p)) {
                Y data = mapper.apply(p);
                block.accept(data);
            }
        }
    }

    public static void main(String[] args) {
        List<Person> roster = Person.createRoster();

        /** * 1. 輸出年齡大於20歲的成員 */
        System.out.println("Persons older than 20:");
        printPersonsOlderThan(roster, 20);
        System.out.println();

        /** * 2. 輸出年齡在14到30歲之間的成員 */
        System.out.println("Persons between the ages of 14 and 30:");
        printPersonsWithinAgeRange(roster, 14, 30);
        System.out.println();

        /** * 3. 輸出年齡在18到25歲的男性成員 * (在本地類中指定搜索條件) * 您可使用一個匿名類而不是一個本地類,而且沒必要爲每一個搜索聲明一個新類 */
        System.out.println("Persons who are eligible for Selective Service:");
        class CheckPersonEligibleForSelectiveService implements CheckPerson {
            public boolean test(Person p) {
                return p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25;
            }
        }

        // 這個其實就是經過行爲參數化傳遞代碼
        printPersons(
                roster, new CheckPersonEligibleForSelectiveService());

        System.out.println();

        // 3. 在匿名類中指定搜索條件代碼
        System.out.println("Persons who are eligible for Selective Service " +
                "(anonymous class):");
        printPersons(
                roster,
                new CheckPerson() {
                    public boolean test(Person p) {
                        return p.getGender() == Person.Sex.MALE
                                && p.getAge() >= 18
                                && p.getAge() <= 25;
                    }
                }
        );

        System.out.println();

        // 3: 使用Lambda表達式簡化代碼,一個箭頭
        System.out.println("Persons who are eligible for Selective Service " +
                "(lambda expression):");

        printPersons(
                roster,
                (Person p) -> p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25
        );

        System.out.println();

        // 4. 使用Lambda的標準功能接口
        System.out.println("Persons who are eligible for Selective Service " +
                "(with Predicate parameter):");

        printPersonsWithPredicate(
                roster,
                p -> p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25
        );

        System.out.println();

        //5.使用Predicate和Consumer參數
        System.out.println("5. Persons who are eligible for Selective Service " +
                "(with Predicate and Consumer parameters):");

        processPersons(
                roster,
                p -> p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25,
                p -> p.printPerson()
        );

        System.out.println();

        // 6. 經過Function<T,R> 指定輸出類型
        System.out.println("Persons who are eligible for Selective Service " +
                "(with Predicate, Function, and Consumer parameters):");

        processPersonsWithFunction(
                roster,
                p -> p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25,
                p -> p.getEmailAddress(),
                email -> System.out.println(email)
        );

        System.out.println();

        // 7. 使用泛型
        System.out.println("Persons who are eligible for Selective Service " +
                "(generic version):");

        processElements(
                roster,
                p -> p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25,
                p -> p.getEmailAddress(),
                email -> System.out.println(email)
        );

        System.out.println();

        // 8: 使用接受Lambda表達式的批量數據操做
        System.out.println("Persons who are eligible for Selective Service " +
                "(with bulk data operations):");

        roster.stream()
                .filter(
                        p -> p.getGender() == Person.Sex.MALE
                                && p.getAge() >= 18
                                && p.getAge() <= 25)
                .map(p -> p.getEmailAddress())
                .forEach(email -> System.out.println(email));
        System.out.println();

        /** * 9. 按年齡排序。Java 8 以前須要實現 Comparator 接口 * 接口比較器是一個功能接口。所以, * 可使用lambda表達式來代替定義並建立一個實現了Comparator的類的新實例: */
        Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

        Arrays.sort(rosterAsArray,
                (a, b) -> Person.compareByAge(a, b)
        );

        for (Person person : roster) {
            person.printPerson();
        }

        /** * 這種比較兩個Person實例的出生日期的方法已經做爲Person. * comparebyage存在。你能夠在lambda表達式中調用這個方法 */

        Arrays.sort(rosterAsArray,
                (a, b) -> Person.compareByAge(a, b)
        );
}
複製代碼

2、函數式接口

1. 什麼是函數式接口

  • 只包含一個抽象方法的接口,稱爲函數式接口,該抽象方法也被稱爲函數方法。 咱們熟知的Comparator和Runnable、Callable就屬於函數式接口。
  • 這樣的接口這麼簡單,都不值得在程序中定義,因此,JDK8在 java.util.function 中定義了幾個標準的函數式接口,供咱們使用。Package java.util.function
  • 能夠經過 Lambda 表達式來建立該接口的對象。(若 Lambda 表達式拋出一個受檢異常,那麼該異常須要在目標接口的抽象方法上進行聲明)。
  • 咱們能夠在任意函數式接口上使用 @FunctionalInterface 註解, 這樣作能夠檢查它是不是一個函數式接口,同時 javadoc 也會包含一條聲明,說明這個接口是一個函數式接口。

2. 自定義函數式接口

@FunctionalInterface    //@FunctionalInterface標註該接口會被設計成一個函數式接口,不然會編譯錯誤
public interface MyFunc<T> {
    T getValue(T t);
}
複製代碼
public static String toUpperString(MyFunc<String> myFunc, String str) {
    return myFunc.getValue(str);
}

public static void main(String[] args) {
    String newStr = toUpperString((str) -> str.toUpperCase(), "abc");
    System.out.println(newStr);
}
複製代碼

做爲參數傳遞 Lambda 表達式:爲了將 Lambda 表達式做爲參數傳遞,接收Lambda 表達式的參數類型必須是與該 Lambda 表達式兼容的函數式接口的類型

函數接口爲lambda表達式和方法引用提供目標類型

3. Java 內置四大核心函數式接口

函數式接口 參數類型 返回類型 用途
Consumer<T> T void 對類型爲T的對象應用操做,包含方法:void accept(T t)
Supplier<T> T 返回類型爲T的對象,包 含方法:T get();
Function<T,R> T R 對類型爲T的對象應用操做,並返回結果。結果是R類型的對象。包含方法:R apply(T t);
Predicate<T> T boolean 肯定類型爲T的對象是否知足某約束,並返回 boolean 值。包含方法 boolean test(T t);
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/* * Java8 內置的四大核心函數式接口 * Consumer<T> : 消費型接口 void accept(T t); * Supplier<T> : 供給型接口 T get(); * Function<T, R> : 函數型接口 R apply(T t); * Predicate<T> : 斷言型接口 boolean test(T t); */
public class FunctionalInterfaceTest {

    //Predicate<T> 斷言型接口:將知足條件的字符串放入集合
    public List<String> filterStr(List<String> list, Predicate<String> predicate) {
        List<String> newList = new ArrayList<>();
        for (String s : list) {
            if (predicate.test(s)) {
                newList.add(s);
            }
        }
        return newList;
    }

    @Test
    public void testPredicate() {
        List<String> list = Arrays.asList("hello", "java8", "function", "predicate");
        List<String> newList = filterStr(list, s -> s.length() > 5);
        for (String s : newList) {
            System.out.println(s);
        }
    }

    // Function<T, R> 函數型接口:處理字符串
    public String strHandler(String str, Function<String, String> function) {
        return function.apply(str);
    }

    @Test
    public void testFunction() {
        String str1 = strHandler("測試內置函數式接口", s -> s.substring(2));
        System.out.println(str1);

        String str2 = strHandler("abcdefg", s -> s.toUpperCase());
        System.out.println(str2);
    }

    //Supplier<T> 供給型接口 :產生指定個數的整數,並放入集合
    public List<Integer> getNumList(int num, Supplier<Integer> supplier) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            Integer n = supplier.get();
            list.add(n);
        }
        return list;
    }

    @Test
    public void testSupplier() {
        List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));

        for (Integer num : numList) {
            System.out.println(num);
        }
    }

    //Consumer<T> 消費型接口 :修改參數
    public void modifyValue(Integer value, Consumer<Integer> consumer) {
        consumer.accept(value);
    }

    @Test
    public void testConsumer() {
        modifyValue(3, s -> System.out.println(s * 3));
    }
}
複製代碼

Package java.util.function 包下還提供了不少其餘的演變方法。

java8-function.png

?> Tip

Java類型要麼是引用類型(Byte、Integer、Objuct、List),要麼是原始類型(int、double、byte、char)。可是泛型只能綁定到引用類型。將原始類型轉換爲對應的引用類型,叫裝箱,相反,將引用類型轉換爲對應的原始類型,叫拆箱。固然Java提供了自動裝箱機制幫咱們執行了這一操做。

List<Integer> list = new ArrayList();
	for (int i = 0; i < 10; i++) {
	list.add(i);    //int被裝箱爲Integer
}
複製代碼

但這在性能方面是要付出代價的。裝箱後的值本質上就是把原始類型包裹起來,並保存在堆裏。所以,裝箱後的值須要更多的內存,並須要額外的內存搜索來獲取被包裹的原始值。

以上funciton包中的IntPredicate、DoubleConsumer、LongBinaryOperator、ToDoubleFuncation等就是避免自動裝箱的操做。通常,針對專門的輸入參數類型的函數式接口的名稱都要加上對應的原始類型前綴。


3、方法引用

  • 方法引用是指經過方法的名字來指向一個方法

  • 當要傳遞給 Lambda 體的操做,已經有實現的方法了,就可使用方法引用(實現抽象方法的參數列表,必須與方法引用方法的參數列表保持一致!)

  • 方法引用的惟一用途是支持Lambda的簡寫(能夠理解爲方法引用是lambda表達式的另外一種表現形式,快捷寫法)

使用 :: 操做符將方法名對象或類的名字分隔開

1. eg

BinaryOperator<Double> binaryOperator = (x,y)->Math.pow(x,y);
//等價於
BinaryOperator<Double> binaryOperator1 = Math::pow;
複製代碼

2. 方法引用類型

Java 8 提供了4種方法引用

Kind Example
靜態方法引用 ContainingClass::staticMethodName
特定對象的實例方法引用 containingObject::instanceMethodName
特定類型的任意對象的實例方法引用 ContainingType::methodName
構造器引用 ClassName::new

1. 靜態方法引用

//比較年齡的方法在Person.compareByAge的已經存在,因此可使用方法引用
Arrays.sort(rosterAsArray, Person::compareByAge);
//---------------------
@Test
public void test3(){
    BiFunction<Double,Double,Double> bif = (x,y)->Math.max(x,y);
    System.out.println(bif.apply(22.1,23.2));

    System.out.println("===等價於===");

    BiFunction<Double,Double,Double> bif1 = Math::max;
    System.out.println(bif1.apply(22.1,23.2));
}

@Test
public void test4(){
    Comparator<Integer> com = (x, y)->Integer.compare(x,y);
    System.out.println(com.compare(1,2));

    System.out.println("===等價於===");
    Comparator<Integer> com1 = Integer::compare;
    System.out.println(com1.compare(1,2));
}
複製代碼

2. 特定對象的實例方法引用

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
        
    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
//------------------------
@Test
public void test2() {
    Person person = new Person("Tom", IsoChronology.INSTANCE.date(1995, 6, 20), Person.Sex.MALE, "tom@qq.com");

    Supplier<String> sup = () -> person.getName();
    System.out.println(sup.get());

    System.out.println("===等價於===");

    Supplier<String> sup1 = person::getName;
    System.out.println(sup1.get());
}
複製代碼

3. 特定類型的任意對象的實例方法引用

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
//-------------------
@Test
public void test5(){
    BiPredicate<String,String> bp = (x,y)->x.equals(y);
    System.out.println(bp.test("Java情報局","Java情報局1"));
    System.out.println("===等價於===");

    BiPredicate<String,String> bp1 = String::equals;
    System.out.println(bp.test("Java情報局","Java情報局"));
}
複製代碼

4. 構造器引用

將一個集合內元素複製到另外一個集合中。

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
    DEST transferElements( SOURCE sourceCollection, Supplier<DEST> collectionFactory) {
        
        DEST result = collectionFactory.get();
        for (T t : sourceCollection) {
            result.add(t);
        }
        return result;
}
複製代碼

Supplier是一個函數式接口,您可使用lambda表達式調用方法TransferElements

Set<Person> rosterSetLambda =
    transferElements(roster, () -> { return new HashSet<>(); });
複製代碼

使用構造器引用代替lambda表達式

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);
//Java編譯器能夠推斷出要建立包含Person類型元素的HashSet集合,可簡寫
Set<Person> rosterSet = transferElements(roster, HashSet::new);
複製代碼
Function<Integer,MyClass> fun = (n) -> new MyClass(n);
//等價於
Function<Integer,Person> fun = MyClass::new;
// 帶兩個參數的構造器引用就要用BiFunction,多個參數的話,還能夠自定義一個這樣的函數式接口
複製代碼
@Test
public void test6(){
    Supplier<Person> sup = ()->new Person("Tom", IsoChronology.INSTANCE.date(1995, 6, 20), Person.Sex.MALE, "tom@qq.com");
    System.out.println(sup.get());
複製代碼

構造器引用還能夠建立數組

@Test
public void test7(){
    Function<Integer,String[]> fun = args -> new String[args];
    String[] strs = fun.apply(6);
    System.out.println(strs.length);
    
    System.out.println("===等價於===");
    
    Function<Integer,String[]> fun1 = String[]::new;
    String[] strs1 = fun1.apply(6);
    System.out.println(strs1.length);
}
複製代碼

4、Stream——函數式數據處理

Stream 是 Java8 中處理集合的關鍵抽象概念,它能夠指定你但願對集合進行的操做,能夠執行很是複雜的查找、過濾和映射數據等操做。 使用Stream API 對集合數據進行操做,就相似於使用 SQL 執行的數據庫查詢。也可使用 Stream API 來並行執行操做。簡而言之, Stream API 提供了一種高效且易於使用的處理數據的方式

1. Stream是個啥

Stream(流) 是數據渠道,用於操做數據源(集合、數組等)所生成的元素序列。

集合講的是數據,流講的是計算!

?>tip

  • Stream 本身不會存儲元素

  • Stream 不會改變源對象。相反,他們會返回一個持有結果的新Stream

  • Stream 操做是延遲執行的。這意味着他們會等到須要結果的時候才執行

流操做有兩個重要特色

  • 流水線——不少流操做自己會返回一個流,這樣多個操做就能夠連接起來,造成一個大的流水線
  • 內部迭代——與迭代器顯示迭代集合不一樣,流的迭代操做都在背後進行

2. Stream 的操做三個步驟

  1. 建立 Stream 一個數據源(如:集合、數組),獲取一個流
  2. 中間操做(一箇中間操做鏈,對數據源的數據進行處理,造成一條流的流水線)
  3. 終止操做(一個終止操做,執行中間操做鏈,併產生結果)

2.1. 建立 Stream

Java8 中的 Collection 接口被擴展,提供了兩個獲取流的方法:

  • default Stream<E> stream() : 返回一個順序流

  • default Stream<E> parallelStream() : 返回一個並行流

由數組建立流

Java8 中的 Arrays 的靜態方法 stream() 能夠獲取數組流:

  • static Stream stream(T[] array): 返回一個流

重載形式,可以處理對應基本類型的數組:

  • public static IntStream stream(int[] array)

  • public static LongStream stream(long[] array)

  • public static DoubleStream stream(double[] array)

由值建立流

可使用靜態方法 Stream.of(), 經過顯示值建立一個流。它能夠接收任意數量的參數。

  • public static<T> Stream<T> of(T... values) : 返回一個流
由函數建立流:建立無限流

可使用靜態方法 Stream.iterate() 和 Stream.generate(), 建立無限流。

  • 迭代

    • public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
  • 生成

    • public static<T> Stream<T> generate(Supplier<T> s) :
//建立 Stream
@Test
public void test1(){
  //1. Collection 提供了兩個方法 stream() 與 parallelStream()
  List<String> list = new ArrayList<>();
  Stream<String> stream = list.stream(); //獲取一個順序流
  Stream<String> parallelStream = list.parallelStream(); //獲取一個並行流

  //2. 經過 Arrays 中的 stream() 獲取一個數組流
  Integer[] nums = new Integer[10];
  Stream<Integer> stream1 = Arrays.stream(nums);

  //3. 經過 Stream 類中靜態方法 of()
  Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);

  //4. 建立無限流
  //迭代
  Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
  stream3.forEach(System.out::println);

  //生成
  Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
  stream4.forEach(System.out::println);
}
複製代碼

2.2. Stream 的中間操做

多箇中間操做能夠鏈接起來造成一個流水線,除非流水線上觸發終止操做,不然中間操做不會執行任何的處理! 而在終止操做時一次性所有處理,稱爲「惰性求值」

2.2.1 篩選與切片
方法 描述
filter(Predicate p) 接收 Lambda , 從流中排除某些元素
distinct() 篩選,經過流所生成元素的 hashCode() 和 equals() 去除重複元素
limit(long maxSize) 截斷流,使其元素不超過給定數量
skip(long n) 跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補
List<Person> persons = Person.createRoster();

//內部迭代:迭代操做 Stream API 內部完成
@Test
public void test2(){
  //全部的中間操做不會作任何的處理
  Stream<Person> stream = persons.stream()
    .filter((e) -> {
      System.out.println("測試中間操做");
      return e.getAge() <= 35;
    });

  //只有當作終止操做時,全部的中間操做會一次性的所有執行,稱爲「惰性求值」
  stream.forEach(System.out::println);
}

//外部迭代
@Test
public void test3(){
  Iterator<Person> it = persons.iterator();

  while(it.hasNext()){
    System.out.println(it.next());
  }
}

@Test
public void test4(){
  persons.stream()
    .filter((p) -> {
      System.out.println("大於25歲的成員:"); // && ||
      return (p.getAge()) >= 25;
    }).limit(3)
    .forEach(System.out::println);
}

@Test
public void test5(){
  persons.parallelStream()
    .filter((e) -> e.getAge() >= 20)
    .skip(2)
    .forEach(System.out::println);
}

@Test
public void test6(){
  persons.stream()
    .distinct()
    .forEach(System.out::println);
}
複製代碼
2.2.2 映射
方法 描述
map(Function f) 接收一個函數做爲參數,該函數會被應用到每一個元素上,並將其映射成一個新的元素
mapToDouble(ToDoubleFunction f) 接收一個函數做爲參數,該函數會被應用到每一個元素上,產生一個新的 DoubleStream
mapToInt(ToIntFunction f) 接收一個函數做爲參數,該函數會被應用到每一個元素上,產生一個新的 IntStream。
mapToLong(ToLongFunction f) 接收一個函數做爲參數,該函數會被應用到每一個元素上,產生一個新的 LongStream
flatMap(Function f) 接收一個函數做爲參數,將流中的每一個值都換成另外一個流,而後把全部流鏈接成一個流
//映射
@Test
public void test1(){
  Stream<String> str = persons.stream()
    .map((e) -> e.getName());
  System.out.println("-------------------------------------------");
  List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
  Stream<String> stream = strList.stream()
    .map(String::toUpperCase);
  stream.forEach(System.out::println);

  System.out.println("---------------------------------------------");

  Stream<Character> stream3 = strList.stream()
    .flatMap(TestStreamAPI::filterCharacter);
  stream3.forEach(System.out::println);
}

public static Stream<Character> filterCharacter(String str){
  List<Character> list = new ArrayList<>();
  for (Character ch : str.toCharArray()) {
    list.add(ch);
  }
  return list.stream();
}
複製代碼
2.2.3 排序
方法 描述
sorted() 產生一個新流,其中按天然順序排序
sorted(Comparator comp) 產生一個新流,其中按比較器順序排序
@Test
public void test(){
  persons.stream()
    .map(Person::getName)
    .sorted()
    .forEach(System.out::println);

  System.out.println("------------------------------------");

  persons.stream()
    .sorted((x, y) -> {
      if(x.getAge() == y.getAge()){
        return x.getName().compareTo(y.getName());
      }else{
        return Integer.compare(x.getAge(), y.getAge());
      }
    }).forEach(System.out::println);
}
複製代碼

2.3. Stream 的終止操做

終端操做會從流的流水線生成結果。其結果能夠是任何不是流的值,例如:List、Integer,甚至是 void

2.3.1 查找與匹配
方法 描述
allMatch(Predicate p) 檢查是否匹配全部元素
anyMatch(Predicate p) 檢查是否至少匹配一個元素
noneMatch(Predicate p) 檢查是否沒有匹配全部元素
findFirst() 返回第一個元素
findAny() 返回當前流中的任意元素
count() 返回流中元素總數
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 內部迭代(使用 Collection 接口須要用戶去作迭 代,稱爲外部迭代。相反,Stream API 使用內部 迭代——它幫你把迭代作了)
public class TestStreamAPI2 {

	List<Person> persons = Person.createRoster();	
	//3. 終止操做
	@Test
	public void test1(){
			boolean bl = persons.stream()
				.allMatch((e) -> e.getGender().equals(Person.Sex.FEMALE));
			
			System.out.println("全部成員都爲女性嗎?"+bl);
			
			boolean bl1 = persons.stream()
				.anyMatch((e) -> e.getGender().equals(Person.Sex.FEMALE));
			
			System.out.println("成員中有女性嗎?"+bl1);
			
			boolean bl2 = persons.stream()
				.noneMatch((e) -> e.getGender().equals(Person.Sex.FEMALE));
			
			System.out.println("成員中是否是沒有女性?"+bl2);
	}
	
	@Test
	public void test2(){
		Optional<Person> op = persons.stream()
			.sorted(Comparator.comparingInt(Person::getAge))
			.findFirst();
		System.out.println("年齡最小的:"+op.get());
		
		Optional<Person> op2 = persons.parallelStream()
			.filter((e) -> e.getGender().equals(Person.Sex.MALE))
			.findAny();
		
		System.out.println("隨便找個男的:"+op2.get());
	}
	
	@Test
	public void test3(){
		long count = persons.stream()
						 .filter((e) -> e.getGender().equals(Person.Sex.FEMALE))
						 .count();
		
		System.out.println("女生的人數:"+count);
		
		Optional<Integer> op = persons.stream()
			.map(Person::getAge)
			.max(Integer::compare);
		
		System.out.println("最大年齡:"+op.get());
		
		Optional<Person> op2 = persons.stream()
			.min((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()));
		
		System.out.println("最小年齡成員:"+op2.get());
	}
	
	//注意:流進行了終止操做後,不能再次使用
	@Test
	public void test4(){
		Stream<Person> stream = persons.stream()
		 .filter((e) -> e.getGender().equals(Person.Sex.FEMALE));
		
		long count = stream.count();
		
		stream.map(Person::getAge)
			.max(Integer::compare);
	}
}
複製代碼
2.3.2 規約
方法 描述
reduce(T iden, BinaryOperator b) 能夠將流中元素反覆結合起來,獲得一個值。 返回 T
reduce(BinaryOperator b) 能夠將流中元素反覆結合起來,獲得一個值。 返回 Optional<T>

備註:map 和 reduce 的鏈接一般稱爲 map-reduce 模式,因 Google 用它來進行網絡搜索而出名。

List<Person> persons = Person.createRoster();

//3. 終止操做:歸約
@Test
public void test1(){
  List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
  Integer sum = list.stream()
    .reduce(0, (x, y) -> x + y);

  System.out.println(sum);
  System.out.println("----------------------------------------");

  Optional<Integer> op = persons.stream()
    .map(Person::getAge)
    .reduce(Integer::sum);
  System.out.println("全部成員的年齡和:"+op.get());
}

//需求:搜索名字中 「B」 出現的次數
@Test
public void test2(){
  Optional<Integer> sum = persons.stream()
    .map(Person::getName)
    .flatMap(TestStreamAPI1::filterCharacter)
    .map((ch) -> {
      if(ch.equals('B'))
        return 1;
      else 
        return 0;
    }).reduce(Integer::sum);

  System.out.println(sum.get());
}
複製代碼
2.3.3 收集
方法 描述
collect(Collector c) 將流轉換爲其餘形式。接收一個 Collector接口的 實現,用於給Stream中元素作彙總的方法

Collectors

Collector接口中方法的實現決定了如何對流執行收集操做(如收集到 List、Set、Map)。可是 Collectors 實用類提供了不少靜態方法,能夠方便地建立常見收集器實例,具體方法與實例以下表: docs.oracle.com/javase/8/do…

方法 返回類型 做用 示例
toList List 把流中元素收集到List List list= list.stream().collect(Collectors.toList());
toSet Set 把流中元素收集到Set Set set= list.stream().collect(Collectors.toSet());
toCollection Collection 把流中元素收集到建立的集合 Collectione mps=list.stream().collect(Collectors.toCollection(ArrayList::new));
counting Long 計算流中元素的個數 long count = list.stream().collect(Collectors.counting());
summingInt Integer 對流中元素的整數屬性求和 Integer sum = persons.stream() .collect(Collectors.summingInt(Person::getAge));
averagingInt Double 計算流中元素Integer屬性的平均值 double avg= list.stream().collect(Collectors.averagingInt(Person::getAge));
summarizingInt IntSummaryStatistics 收集流中Integer屬性的統計值。 如:平均值 IntSummaryStatistics iss= list.stream().collect(Collectors.summarizingInt(Person::getAge));
joining String 鏈接流中每一個字符串 String str= list.stream().map(Person::getName).collect(Collectors.joining());
maxBy Optional<T> 根據比較器選擇最大值 Optionalmax= list.stream().collect(Collectors.maxBy(comparingInt(Person::getAge)));
minBy Optonal<T> 根據比較器選擇最小值 Optional min = list.stream().collect(Collectors.minBy(comparingInt(Person::getAge)));
reducing 歸約產生的類型 從一個做爲累加器的初始值開始,利用BinaryOperator與 流中元素逐個結合,從而歸 約成單個值 int total=list.stream().collect(Collectors.reducing(0, Person::getAge, Integer::sum));
collectingAndThen 轉換函數返回的類型 包裹另外一個收集器,對其結果轉換函數 int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingBy Map<K,List<T>> 根據某屬性值對流分組,屬性爲K,結果爲V Map<Person.Sex, List<Person>> map = persons.stream() .collect(Collectors.groupingBy(Person::getGender));
partitioningBy Map<Boolean,List<T>> 根據true或false進行分區 Map<Boolean, List<Person>> map = persons.stream() .collect(Collectors.partitioningBy((e) -> e.getAge() >= 50));
@Test
public void test3(){
  List<String> list = persons.stream()
    .map(Person::getName)
    .collect(Collectors.toList());
  list.forEach(System.out::println);
}

@Test
public void test4(){
  Optional<Integer> max = persons.stream()
    .map(Person::getAge)
    .collect(Collectors.maxBy(Integer::compare));

  System.out.println("最大年齡:"+max.get());

  Optional<Person> op = persons.stream().min(Comparator.comparingInt(Person::getAge));

  System.out.println("最小年齡的成員:"+op.get());

  Integer sum = persons.stream()
    .collect(Collectors.summingInt(Person::getAge));

  System.out.println("全部成員年齡和:"+sum);

  IntSummaryStatistics dss = persons.stream()
    .collect(Collectors.summarizingInt(Person::getAge));

  System.out.println("最大年齡:"+dss.getMax());
}

//分組
@Test
public void test5(){
  Map<Person.Sex, List<Person>> map = persons.stream()
    .collect(Collectors.groupingBy(Person::getGender));

  System.out.println("按性別分組:"+map);
}

//多級分組
@Test
public void test6(){
  Map<Person.Sex, Map<String, List<Person>>> map = persons.stream()
    .collect(Collectors.groupingBy(Person::getGender, Collectors.groupingBy((e) -> {
      if(e.getAge() >= 60)
        return "老年";
      else if(e.getAge() >= 35)
        return "中年";
      else
        return "成年";
    })));

  System.out.println(map);
}

//分區
@Test
public void test7(){
  Map<Boolean, List<Person>> map = persons.stream()
    .collect(Collectors.partitioningBy((e) -> e.getAge() >= 50));

  System.out.println(map);
}
@Test
public void test8(){
  String str = persons.stream()
    .map(Person::getName)
    .collect(Collectors.joining("," , "----", "----"));

  System.out.println(str);
}

@Test
public void test9(){
  Optional<Integer> sum = persons.stream()
    .map(Person::getAge)
    .collect(Collectors.reducing(Integer::sum));
  System.out.println(sum.get());
}
複製代碼

3. 並行流與串行流

先說說並行和併發

併發是兩個任務共享時間段,並行則是兩個任務在同一時間發生,好比運行在多核CPU上。

lbvsVU.png

並行流就是把一個內容分紅多個數據塊,並用不一樣的線程分別處理每一個數據塊的流

Java 8 中將並行進行了優化,咱們能夠很容易的對數據進行並行操做。Stream API 能夠聲明性地經過 parallel()sequential() 在並行流與順序流之間進行切換。若是想從一個集合類建立一個流,調用parallerStream就能夠獲取一個並行流。

public static long parallelSum(long n) {
    return Stream.iterate(1L, i -> i + 1)
        .limit(n)
        .parallel()    //將流轉化爲並行流
        .reduce(0L, Long::sum);
}
複製代碼

配置並行流使用的線程池

使用流的parallel方法,你可能會想到,並行流用的線程是從哪兒來的?有多少個?怎麼自定義?

並行流內部使用了默認的ForkJoinPool(分支/合併框架),它默認的線程數量就是你的處理器數量,這個值是由Runtime.getrRuntime().acailable-Processors()獲得。

你能夠經過系統屬性java.util.concurrent.ForkJoinPool.common.parallelism來改變線程池大小,以下

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");,這是一個全局設置,所以會影響代碼中全部的並行流(目前還沒法專門爲某個並行流指定該值,通常而言,讓ForkJoinPool的大小等於處理器數量是個不錯的默認值)。

高效使用並行流

  • 並行流並非老是比順序流快
  • 留意裝箱。自動裝箱和拆箱操做會大大下降性能,Java8 中有原始類型流(IntStream、LongStream...)來避免這種操做
  • 有些操做自己在並行流上的性能就比順序流差,特別是 limit 和 findFirst 等依賴元素順序的操做,他們在並行流上執行的代價就很是大
  • 還要考慮流的操做流水線的總計算成本
  • 對於較小的數據量,不必使用並行流
  • 要考慮流背後的數據結構是否易於分解,好比,ArrayList 的拆分效率比 LinkedList 高不少,前者無需遍歷
  • 還要考慮終端操做中合併步驟的代價是大是小(好比Collector中的combiner方法)

4. Fork/Join 框架

並行流背後使用的基礎框架就是 Java7 中引入的分支/合併框架

Fork/Join(分支/合併)框架的目的是以遞歸方式將能夠並行的任務拆分(fork)成更小的任務,而後將每一個任務的結果合併 (join)起來生成總體效果。它是ExectorService接口的一個實現,把子任務分配給線程池(稱爲ForkJoinPool)中的工做線程。

Fork/Join 框架:就是在必要的狀況下,將一個大任務,進行拆分(fork)成若干個小任務(拆到不可再拆時),再將一個個的小任務運算的結果進行 join 彙總

fork-join.png

// 用分支/合併框架 並行求和
public class ForkJoinSumCalculator extends RecursiveTask<Long> {

    private final long[] numbers;
    private final int start;
    private final int end;

    //再也不將任務分解爲子任務的數組大小
    public static long THRESHOLD = 100;

    //公共構造器用於建立主任務
    public ForkJoinSumCalculator(long[] numbers) {
        this(numbers, 0, numbers.length);
    }

    //私有構造器用於以遞歸方式爲主任務建立子任務
    private ForkJoinSumCalculator(long[] numbers, int start, int end) {
        this.numbers = numbers;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        int length = end - start;
        //若是大小小於等於閾值,順序計算結果
        if (length <= THRESHOLD) {
            return computerSequntially();
        }

        ForkJoinSumCalculator leftTask = new ForkJoinSumCalculator(numbers, start, start + length / 2);

        leftTask.fork();

        ForkJoinSumCalculator rightTask = new ForkJoinSumCalculator(numbers, start + length / 2, end);

        Long rightResult = rightTask.compute();   //同步執行第二個任務,
        Long leftResult = leftTask.join(); // 讀取第一個子任務的結果,若是還沒有完成就等待
        return rightResult + leftResult;
    }


    // 子任務再也不可分時計算和
    private long computerSequntially() {
        long sum = 0;
        for (int i = start; i < end; i++) {
            sum += numbers[i];
        }
        return sum;
    }

    public static long forkJoimSum(long n) {
        long[] numbers = LongStream.rangeClosed(1, n).toArray();
        ForkJoinTask<Long> task = new ForkJoinSumCalculator(numbers);
        return new ForkJoinPool().invoke(task);
    }

    public static void main(String[] args) {
        System.out.println("sum:" + forkJoimSum(10000));
    }
}
複製代碼

Fork/Join 框架與傳統線程池的區別

採用 「工做竊取」模式(work-stealing): 當執行新的任務時它能夠將其拆分紅更小的任務執行,並將小任務加到線程隊列中,而後再從一個隨機線程的隊列中偷一個並把它放在本身的隊列中。

相對於通常的線程池實現,fork/join框架的優點體如今對其中包含的任務的處理方式上,在通常的線程池中,若是一個線程正在執行的任務因爲某些緣由沒法繼續運行,那麼該線程會處於等待狀態,而在fork/join框架實現中,若是某個子問題因爲等待另一個子問題的完成而沒法繼續運行,那麼處理該子問題的線程會主動尋找其餘還沒有運行的子問題來執行,這種方式減小了線程的等待時間,提升了性能。

使用Fork/Join框架的最佳作法

  • 對一個任務調用join方法會阻塞調用方,直到該任務做出結果。所以,有必要在兩個子任務的計算都開始以後再調用它
  • 不該該在 RecursiveTask 內部使用 ForkJoinPool 的 invoke 方法。相反,你應該始終直接調用 compute 或fork 方法,只有順序代碼才應該用 invoke 來啓動並行計算

工做竊取

fork-join-steal.jpg

5. Spliterator

「可分迭代器」——spliterator,和Iterator同樣,也用於遍歷數據源中的元素,它是爲了並行執行而設計。

Java8 爲集合框架中包含的全部數據結構都提供了一個默認的 Spliterator 方法。集合實現了Spliterator接口,接口提供了一個Spliterator方法。

spliterator

5、接口中的默認方法與靜態方法

傳統上,Java中實現接口的類必須爲接口中定義的每一個方法提供一個實現類,或者從父類中繼承它的實現。但若是類庫的設計者須要修改接口,加入新的方法,這種方式就會出現問題。全部使用該接口的實體類爲了適配新的接口約定都須要進行修改(要是這麼不兼容的話,早晚被淘汰)。因此,Java8爲了解決這一問題引入了一種新的機制。Java8中的接口支持在聲明方法的同時提供實現。其一,Java8容許在接口中聲明靜態方法。其二,Java8引入的新功能——默認方法,經過默認方法能夠指定接口方法的默認實現(所以,實現接口的類若是不顯式的提供該方法的具體實現,就會自動繼承默認的實現,這種機制可使你平滑的進行接口的優化和升級)。

默認方法

Java 8中容許接口中包含具備具體實現的方法,該方法稱爲 「默認方法」,默認方法使用 default 關鍵字修飾。

interface MyFunc<T>{
    T func(int a);

    default String getName(){
        return "hello java8";
    }
}
複製代碼
@Test
public void test1(){
    List<Integer> list = Arrays.asList(22,11,33,55,4);
    //sort是List接口中的默認方法,naturalOrder是Comparator的靜態方法
    list.sort(Comparator.naturalOrder());
    for (Integer integer : list) {
        System.out.println(integer);
    }
}
複製代碼

default-method.png

默認方法的」類優先」原則

若一個接口中定義了一個默認方法,而另一個父類或接口中又定義了一個同名的方法時

  • 選擇父類中的方法。若是一個父類提供了具體的實現,那麼接口中具備相同名稱和參數的默認方法會被忽略。

  • 接口衝突。若是一個父接口提供一個默認方法,而另外一個接口也提供了一個具備相同名稱和參數列表的方法(無論方法是不是默認方法),那麼必須覆蓋該方法來解決衝突

interface MyFunc<T> {
    default String getName() {
        return "hello java8";
    }
}

interface MyFunc1 {
    default String getName() {
        return "hello Java情報局";
    }
}

class MyClass implements MyFunc, MyFunc1 {

    @Override
    public String getName() {
        return MyFunc1.super.getName();
    }
}
複製代碼

JavaAPI的設計者們充分利用了默認方法,爲集合接口和類新增了不少新的方法。


6、Optional 類

1. 用 Optional 取代 null

當你碰到程序中有一個NullPointerException時的第一衝動是不就是趕忙找到代碼,添加一個if語句,檢查下??

NullPointerException是Java程序開發中典型的異常。爲了不這種異常,咱們的代碼有可能充斥着一層又一層的深度嵌套的null檢查,代碼可讀性極差。

Optional類(java.util.Optional) 是一個容器類,表明一個值存在或不存在, 原來用 null 表示一個值不存在,如今 Optional 能夠更好的表達這個概念。而且能夠避免空指針異常。

變量存在時,Optional類知識對類簡單封裝。變量不存在時,缺失的值就會被建模成一個「空」的Optional對象,由方法Optional.empty()返回。

經常使用方法:

  • Optional.of(T t) : 建立一個 Optional 實例
  • Optional.empty() : 建立一個空的 Optional 實例
  • Optional.ofNullable(T t):若 t 不爲 null,建立 Optional 實例,不然建立空實例
  • isPresent() : 判斷是否包含值 orElse(T t) : 若是調用對象包含值,返回該值,不然返回t
  • orElseGet(Supplier s) :若是調用對象包含值,返回該值,不然返回 s 獲取的值
  • map(Function f): 若是有值對其處理,並返回處理後的Optional,不然返回 Optional.empty()
  • flatMap(Function mapper):與 map 相似,要求返回值必須是Optional

2. Optional 實例

2.1 建立Optional對象

@Test
public void test(){

    Optional<Person> optional = Optional.empty();  //建立一個空Optional

    Optional<Person> op = Optional.of(new Person());
    Person p = op.get();
    System.out.println(p);   //Person{name='null', birthday=null, gender=null, emailAddress='null'}

    Person person = null;
    Optional<Person> op1 = Optional.of(person); //person爲null,拋出NullPointerException

    Optional<Person> op2 = Optional.ofNullable(person);   //建立容許null值得Optional對象

}
複製代碼

2.2 optional 對象操做

@Test
public void test4(){
    Person person = new Person("Tom",IsoChronology.INSTANCE.date(1999, 7, 15),Person.Sex.FEMALE, "Tom@360.com")
    Optional<Person> op = Optional.ofNullable(person);

    Optional<String> op1 = op.map(Person::getName);
    System.out.println(op1.get());
    
    /** * 使用 map 從 optional 對象中提取和轉換值 * 若是想提取人員姓名,以前須要判斷persion !=null,Optional提供了一個map方法,對其處理 **/
    Optional<String> op2 = op.map(Person::getName);
    System.out.println(op2.get());

    //使用 flatMap 連接 optional 對象
    Optional<String> op3 = op.flatMap((e) -> Optional.of(e.getName()));
    System.out.println(op3.get());
    
    //TODO
}
複製代碼

7、CompletableFuture —— 組合式異步編程

1. Future接口

Future接口在 Java 5 中被引入,設計初衷是對未來某個時刻會發生的結果進行建模。它建模了一種異步計算,返回一個執行運算結果的引用,當運算結束後,這個引用被返回給調用方。在 Future中觸發那些潛在耗時的操做把調用線程解放出來,讓它能繼續執行其餘有價值的工做, 再也不須要等待耗時的操做完成。打個比方,你能夠把它想象成這樣的場景:你拿了一袋衣服到你中意的乾洗店去洗衣服。乾洗店員工會給你張發票,告訴你何時你的衣服會洗好(這就 是一個Future事件)。衣服乾洗的同時,你能夠去作其餘的事情。Future的另外一個優勢是它比 更底層的Thread更易用。要使用Future,一般你只須要將耗時的操做封裝在一個Callable對象中,再將它提交給ExecutorService,就能夠了。下面這段代碼展現了Java 8以前使用 Future的一個例子。

ExecutorService executor = Executors.newCachedThreadPool();
Future<Double> future = executor.submit(new Callable<Double>() {
    public Double call() {
        return doSomeThings();    //異步方式在新的線程中執行操做
    }
});
//doSomethingElse(); //異步操做進行的同時,能夠作其餘事情
try {
    //獲取異步操做的結果,若是阻塞,等1秒後退出
    Double result = future.get(1, TimeUnit.SECONDS);   
} catch (ExecutionException | InterruptedException | TimeoutException e) {
}
複製代碼

1.1 Future接口的侷限性

雖然Future以及相關使用方法提供了異步執行任務的能力,可是對於結果的獲取倒是很不方便,只能經過阻塞或者輪詢的方式獲得任務的結果。阻塞的方式顯然和咱們的異步編程的初衷相違背,輪詢的方式又會耗費無謂的CPU資源,並且也不能及時地獲得計算結果,爲何不能用觀察者設計模式當計算結果完成及時通知監聽者呢?

Java的一些框架,好比Netty,本身擴展了Java的 Future接口,提供了addListener等多個擴展方法。Google guava也提供了通用的擴展Future:ListenableFuture、SettableFuture 以及輔助類Futures等,方便異步編程。

做爲正統的Java類庫,是否是應該作點什麼,增強一下自身庫的功能呢?

在Java 8中, 新增長了一個包含50個方法左右的類: CompletableFuture,提供了很是強大的Future的擴展功能,能夠幫助咱們簡化異步編程的複雜性,提供了函數式編程的能力,能夠經過回調的方式處理計算結果,而且提供了轉換和組合CompletableFuture的方法。

好比實現下面一些例子:

  • 將兩個異步計算合併爲一個——這兩個異步計算之間相對獨立,同時第二個又依賴於第一個的結果
  • 等待Future集合中的全部任務都完成
  • 僅等待Future集合中最快結束的任務完成(有可能由於它們試圖經過不一樣的方式計算同 一個值),並返回它的結果
  • 經過編程方式完成一個Future任務的執行(即以手工設定異步操做結果的方式)
  • 應對Future的完成事件(即當Future的完成事件發生時會收到通知,並能使用Future 計算的結果進行下一步的操做,不僅是簡單地阻塞等待操做的結果)

1.2 使用CompletableFuture 構建異步應用

public class TestCompletableFuture {
    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");
        f.complete(100);
        System.in.read();
    }
}
複製代碼

8、新時間日期 API

1. 使用 LocalDate、LocalTime、LocalDateTime

  • LocalDate、LocalTime、LocalDateTime 類的實例是不可變的對象,分別表示使用 ISO-8601日曆系統的日期、時間、日期和時間。它們提供了簡單的日期或時間,並不包含當前的時間信息。也不包含與時區相關的信息。

    @Test
    public void test1(){
        LocalDate date = LocalDate.of(2020,01,03);
        Month month = date.getMonth();
        System.out.println(month);    //JANUARY
    
        DayOfWeek dayOfWeek = date.getDayOfWeek();
        System.out.println(dayOfWeek);   //FRIDAY
    
        int len = date.lengthOfMonth();
        System.out.println(len);  //31
        //使用TemporalField(ChronoField枚舉實現了該接口)讀取LocalDate的值
        int year = date.get(ChronoField.YEAR);
        System.out.println(year);  //2020
    
        LocalDate ld = LocalDate.parse("2020-01-03");
        System.out.println(ld);   //2020-01-03
    
        LocalTime time = LocalTime.of(19,56,11);
        System.out.println(time);  //19:56:11
    
        LocalDateTime ldt = LocalDateTime.now();
        LocalDateTime l1 = LocalDateTime.of(2020,01,03,18,48);
        System.out.println(l1);  //2020-01-03T18:48
    
        LocalDateTime l2 = l1.plusYears(3);
        System.out.println(l2);     //2023-01-03T18:48
    
        LocalDateTime l3 = l1.minusMonths(1);
        System.out.println(l3);  //2019-12-03T18:48
        System.out.println(l3.getMinute()+","+l3.getYear());   //48,2019
    }
    複製代碼

2. Instant 時間戳

  • 用於「時間戳」的運算。它是以Unix元年(傳統的設定爲UTC時區1970年1月1日午夜時分)開始所經歷的秒數進行計算

    @Test
    public void test2(){
        Instant ins = Instant.now();  //默認使用 UTC 時區
        OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));
        System.out.println(odt);
        System.out.println(ins.getNano());
        Instant ins2 = Instant.ofEpochSecond(5);
        System.out.println(ins2);
    }
    複製代碼

3. Duration 和 Period

  • Duration:用於計算兩個「時間」間隔

  • Period:用於計算兩個「日期」間隔

    @Test
    public void test3(){
        Instant ins1 = Instant.now();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        Instant ins2 = Instant.now();
        System.out.println("所耗費時間爲:" + Duration.between(ins1, ins2));   
    
        System.out.println("----------------------------------");
        LocalDate ld1 = LocalDate.now();
        LocalDate ld2 = LocalDate.of(2019, 1, 1);
    
        Period pe = Period.between(ld2, ld1);
        System.out.println(pe.getYears());  
        System.out.println(pe.getMonths());
        System.out.println(pe.getDays());
    }
    複製代碼

4. 日期的操縱

  • 經過 withXXX 方法修改 LocalDate 的屬性

  • TemporalAdjuster : 時間校訂器。有時咱們可能須要獲取例如:將日期調整到「下個週日」等操做。

  • TemporalAdjusters : 該類經過靜態方法提供了大量的常 用 TemporalAdjuster 的實現。

    @Test
    public void test(){
        LocalDate date = LocalDate.now();
        //經過withAttributer方法修改LocalDate的屬性
        LocalDate date1 = date.with(ChronoField.ALIGNED_WEEK_OF_YEAR,9);
        LocalDate date2 = date.withYear(2019);
        LocalDate date3 = date.withDayOfMonth(11);  //修改成11號
        System.out.println(date1);
        //下週日
        LocalDate nextSunday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
        System.out.println(nextSunday);
    }
    複製代碼

5. 解析與格式化

java.time.format.DateTimeFormatter 類:該類提供了三種格式化方法:

  • 預約義的標準格式

  • 語言環境相關的格式

  • 自定義的格式

    @Test
    public void test(){
        //DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");
    
        LocalDateTime ldt = LocalDateTime.now();
        String strDate = ldt.format(dtf);
    
        System.out.println(strDate); //2020年01月03日 20:32:14 星期五
    
        LocalDateTime newLdt = ldt.parse(strDate, dtf);
        System.out.println(newLdt);  //2020-01-03T20:32:14
    }
    複製代碼

時區的處理

  • Java8 中加入了對時區的支持,帶時區的時間爲分別爲: ZonedDate、ZonedTime、ZonedDateTime

    其中每一個時區都對應着 ID,地區ID都爲 「{區域}/{城市}」的格式 例如 :Asia/Shanghai 等

    ZoneId:該類中包含了全部的時區信息

    • getAvailableZoneIds() : 能夠獲取全部時區時區信息
  • of(id) : 用指定的時區信息獲取 ZoneId 對象

    @Test
    public void test(){
        Set<String> set = ZoneId.getAvailableZoneIds();  //遍歷時區
        set.forEach(System.out::println);
        LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        System.out.println(ldt);
    
        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("US/Pacific"));
        System.out.println(zdt);
    }
    複製代碼

9、重複註解與類型註解

註解

Java 8 對註解處理提供了兩點改進:可重複的註解可用於類型的註解

Java中註解是一種對程序元素進行配置,提供附加信息的機制(Java8以前,註解只能被用在聲明上)

重複註解

Java8 以前不容許上邊這樣的重複註解,因此通常會經過一些慣用手法繞過這一限制。能夠聲明一個新的註解,它包含了你但願重複的註解數組。

建立一個重複註解

  1. 將註解標記爲**@Repeatable**
  2. 提供一個註解的容器
import java.lang.annotation.Repeatable;
@Repeatable(Authors.class)
public @interface Author {
    String name();
}
複製代碼
public @interface Authors {
    Author[] value();
}
複製代碼
@Author(name = "Java")
@Author(name = "Android")
public class Book {
    public static void main(String[] args) {
        Author[] authors = Book.class.getAnnotationsByType(Author.class);
        Arrays.asList(authors).forEach(s->{
            System.out.println(s.name());
        });
    }
}
複製代碼

類型註解

Java8 開始,註解能夠應用於任何類型。包括new操做符、類型轉換、instanceof檢查、範型類型參數,以及implemtnts和throws子句。

@NotNull String name = person.getName();    //getName不返回空

List<@NotNull Person> persons = new ArrayList<>();  //persons老是非空
複製代碼

10、其餘語言特性

原子操做

java.util.concurrent.atomic 包提供了多個對數字類型進行操做的類,好比AtomicInteger和AtomicLong,它們支持對單一變量的原子操做。這些類在Java 8中新增了更多的方法支持。

  • getAndUpdate——以原子方式用給定的方法更新當前值,並返回變動以前的值

  • updateAndGet——以原子方式用給定的方法更新當前值,並返回變動以後的值

  • getAndAccumulate——以原子方式用給定的方法對當前及給定的值進行更新,並返回變動以前的值

  • accumulateAndGet——以原子方式用給定的方法對當前及給定的值進行更新,並返回變動以後的值

Adder和Accumulator

多線程的環境中,若是多個線程須要頻繁地進行更新操做,且不多有讀取的動做(好比,在統計計算的上下文中),Java API文檔中推薦大使用新的類LongAdder、LongAccumulator、Double-Adder以及DoubleAccumulator,儘可能避免使用它們對應的原子類型。這些新的類在設計之初就考慮了動態增加的需求,能夠有效地減小線程間的競爭。

LongAddr 和 DoubleAdder 類都支持加法操做 , 而 LongAccumulator 和 DoubleAccumulator可使用給定的方法整合多個值。

ConcurrentHashMap

ConcurrentHashMap類的引入極大地提高了HashMap現代化的程度,新引入的ConcurrentHashMap對併發的支持很是友好。ConcurrentHashMap容許併發地進行新增和更新操做,由於它僅對內部數據結構的某些部分上鎖。所以,和另外一種選擇,即同步式的Hashtable比較起來,它具備更高的讀寫性能。

  1. 性能

    爲了改善性能,要對ConcurrentHashMap的內部數據結構進行調整。典型狀況下,map的條目會被存儲在桶中,依據鍵生成哈希值進行訪問。可是,若是大量鍵返回相同的哈希值,因爲桶是由List實現的,它的查詢複雜度爲O(n),這種狀況下性能會惡化。在Java 8中,當桶過於臃腫時,它們會被動態地替換爲排序樹(sorted tree),新的數據結構具備更好的查詢性能(排序樹的查詢複雜度爲O(log(n)))。注意,這種優化只有當鍵是能夠比較的(好比String或者Number類)時纔可能發生。

  2. 類流操做

    ConcurrentHashMap支持三種新的操做,這些操做和你以前在流中所見的很像:

  • forEach——對每一個鍵值對進行特定的操做

  • reduce——使用給定的􏰤簡函數(reduction function),將全部的鍵值對整合出一個結果􏰝

  • search——對每個鍵值對執行一個函數,直到函數的返回值爲一個非空值

    以上每一種操做都支持四種形式,接受使用鍵、值、Map.Entry以及鍵值對的函數:

  • 使用鍵和值的操做(forEach、reduce、search)

  • 使用鍵的操做(forEachKey、reduceKeys、searchKeys)

  • 使用值的操做 (forEachValue、reduceValues、searchValues)

  • 使用Map.Entry對象的操做(forEachEntry、reduceEntries、searchEntries)

注意,這些操做不會對ConcurrentHashMap的狀態上鎖。它們只會在運行過程當中對元素進行操做。應用到這些操做上的函數不該該對任何的順序,或者其餘對象,或在計算過程發生變化的值,有依賴。 除此以外,你須要爲這些操做指定一個併發閾值。若是通過預預估當前map的大小小於設定的閾值,操做會順序執行。使用值1開開啓基於通用線程池的最大並行。使用值Long.MAX_VALUE設定程序以單線程執行操做。下面這個例子中,咱們使用reduceValues試圖找出map中的最大值:

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); 
Optional<Integer> maxValue = Optional.of(map.reduceValues(1, Integer::max));
複製代碼

注意,對int、long和double,它們的reduce操做各有不一樣(好比reduceValuesToInt、reduceKeysToLong等)。

  1. 計數

    ConcurrentHashMap類提供了一個新的方法,名叫mappingCount,它以長整型long返回map中映射的數目。咱們應該儘可能使用這個新方法,而不是老的size方法,size方法返回的類型爲int。這是由於映射的數量多是int沒法表示的。

  2. 集合視圖

    ConcurrentHashMap類還提供了一個名爲KeySet的新方法,該方法以Set的形式返回ConcurrentHashMap的一個視圖(對map的修改會反映在該Set中,反之亦然)。你也可使用新的靜態方法newKeySet,由ConcurrentHashMap建立一個Set。

Arrays

Arrays類提供了不一樣的靜態方法對數組進行操做。如今,它又包括了四個新的方法(它們都有特別重載的變量)

  • parallelSort:parallelSort方法會以併發的方式對指定的數組進行排序,你可使用天然順序,也能夠

    爲數組對象定義特別的Comparator

  • setAll和parallelSetAll:setAll和parallelSetAll方法能夠以順序的方式也能夠用併發的方式,使用提供的函數 計算每個元素的值,對指定數組中的全部元素進行設置

  • parallelPrefix:parallelPrefix方法以併發的方式,用用戶提供的二進制操做符對給定數組中的每一個元素

    進行累積計算

Number

Number類中新增方法

  • Short、Integer、Long、Float和Double類提供了靜態方法sum、min和max
  • Integer和Long類提供了compareUnsigned、divideUnsigned、remainderUnsigned 和toUnsignedLong方法來處理無符號數。
  • Integer和Long類也分別提供了靜態方法parseUnsignedInt和parseUnsignedLong 將字符解析爲無符號int或者long類型。
  • Byte和Short類提供了toUnsignedInt和toUnsignedLong方法經過無符號轉換將參數轉化爲 int 或 者 long 類型。相似地, Integer 類如今也提供了靜態方法 toUnsignedLong。
  • Double和Float類提供了靜態方法isFinite,能夠檢查參數是否爲有限浮點數。
  • Boolean類如今提供了靜態方法logicalAnd、logicalOr和logicalXor,能夠在兩個 boolean之間執行and、or和xor操做。
  • BigInteger 類提供了 byteValueExact 、 shortValueExact 、 intValueExact 和 longValueExact,能夠將BigInteger類型的值轉換爲對應的基礎類型。不過,若是在轉換過程當中有信息的丟失,方法會拋出算術異常。

Math

若是Math中的方法在操做中出現ຼ出,Math類提供了新的方法能夠拋出算術異常。支持這一異常的方法包括使用int和long參數的addExact、subtractExact、multipleExact、 incrementExact、decrementExact和negateExact。此外,Math類還新增了一個靜態方法 toIntExact,能夠將long值轉換爲int值。其餘的新增內容包括靜態方法floorMod、floorDiv 和nextDown。

Files

Files類最引人注目的改變是,你如今能夠用文件直接產生流

  • Files.list——生成由指定目錄中全部條目構成的Stream<Path>。這個列表不是遞歸包含的。因爲流是延遲消費的,處理包含內容很是龐大的目錄時,這個方法很是有用
  • Files.walk——和Files.list有些相似,它也生成包含給定目錄中全部條目的 Stream<Path>。不過這個列表是遞歸的,你能夠設定遞歸的深度。注意,該遍歷是依照深度優先進行的
  • Files.find—— 經過遞歸地遍歷一個目錄找到符合條件的條目,並生成一個 Stream<Path> 對象

String

String類也新增􏱗了一個靜態方法,名叫join。它能夠用一個分隔符將多個字符串􏶘接起來。和咱們之前使用的apache提供的StringUtils.join同樣。

Reflection

Reflection API的變化就是爲了支持Java 8中註解機制的改變。 除此以外,Relection接口的另外一個變化是新增了能夠查詢方法參數信息的API,好比,你如今可使用新的java.lang.reflect.Parameter類查詢方法參數的名稱和修飾符。


FAQ

  • 說說你知道的Java8 有哪寫新特性?

  • 什麼是lambda表達式?有啥優勢?

  • ConcurrentHashMap 在Java8 和 Java7的實現區別?

  • 能說說 Java 8 改進的JVM 不?

  • hashMap原理,java8作了哪些改變?

  • 外部迭代和內部迭代,你曉得吧?


參考

《Java 8實戰》

《Java 8函數式編程》

Java 8官方文檔

某免費視頻學習網站

相關文章
相關標籤/搜索