天天看點

Java8 Lambda等新特性

      Java8 是 Java 語言開發的一個主要版本。它新增了非常多的特性,這裡主要認識常用的幾個:

  • Lambda表達式
  • 函數式接口
  • 方法引用 
  • 接口中的預設方法
  • 新的日期 API
  • Stream API
  • Optional 類 

一、Lambda 表達式

      Lambda 表達式的簡單demo:

// 根據數組中元素的長度自然排序
    public static void main(String[] args) {
        String[] strs = new String[]{"as", "lambda", "s", "java", "ad"};

        //1、匿名内部類實作
        Arrays.sort(strs, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return Integer.compare(o1.length(), o2.length());
            }
        });
        System.out.println("匿名内部類實作:"+ Arrays.toString(strs));

        // 2、轉化為Lambda 表達式1
        Arrays.sort(strs, (String o1, String o2) -> { return Integer.compare(o1.length(), o2.length()); });

        // 3、轉化為Lambda 表達式2
        Arrays.sort(strs, (String o1, String o2) -> Integer.compare(o1.length(), o2.length()));

        // 4、轉化為Lambda 表達式3
        Arrays.sort(strs, (o1, o2) -> Integer.compare(o1.length(), o2.length()));

        // 5、轉化為Lambda 表達式4
        Arrays.sort(strs, Comparator.comparingInt(String::length));
        System.out.println(Arrays.toString(strs));
    }           

Lambda:λ(波長機關)允許把函數作為一個方法的參數(函數作為參數傳遞進方法中)。

Lambda表達式:也可稱為閉包,把帶有參數變量的表達式稱為Lambda表達式。 

使用 Lambda 表達式可以使代碼變的更加簡潔緊湊。注意:使用 Lambda 表達式的前提:接口中有且隻有一個必須需要被實作的抽象方法(後面的函數式接口)。

1、Lambda表達式的基本文法

     Lambda表達式的基本文法:參數清單 -> 表達式;

文法形式為 () -> {}   其中 () 用來描述參數清單,{} 用來描述方法體,-> 為 Lambda運算符 ,讀作(goes to)。

     1)參數清單

        (1)如果沒有參數,直接使用 () 括号來表示,() 不能省略。

        (2)如果有一個參數,有兩種寫法:

                     如果參數寫了參數類型,則必須要使用 () 括号表示,

                     如果參數不寫參數類型,則 () 括号可以省略。

        (3)如果有兩個以上參數,不管參數是否寫參數類型,都必須使用 () 括号表示,不能省略。

        (4)如果參數要加修飾符或者标簽,參數一定要加上完整類型,即:(修飾符 參數類型 參數, ... )

     2)表達式:  demo 有展現,這裡不再舉例

        (1)如果表達式代碼塊隻有一行代碼,{} 可以省略

                 若方法有傳回值,{}省略時,不能使用return關鍵字傳回,會編譯報錯;{} 不省略時,{}代碼塊中必須使用 return傳回。

        (2)如果表達式代碼塊有多行,{} 不能省略

                 若方法有傳回值,{}代碼塊中必須使用 return傳回一個傳回值。 

3、Lambda表達式中的變量和變量作用域

     Lambda表達式中的變量有三種:

         - 參數:參數清單中的參數

         - 局部變量:表達式代碼塊中的定義的局部變量

         - 自由變量:不是參數也不是局部變量的外部變量

    結論:

          1)操作參數和局部變量的使用方式和普通的變量使用方式相同

          2)自由變量在 Lambda表達式中使用時,不能被修改(隐式 final修飾),操作自由變量的代碼塊稱為閉包

          3)變量作用域:Lambda表達式隻能引用标記了 final 的外層局部變量(即自由變量)

Lambda表達式中的自由變量會被儲存(Lambda會使用一種技術儲存),無論什麼時候執行 Lambda表達式,都可以直接使用。

          4)Lambda表達式中的 this是指:建立 Lambda表達式的方法中的 this,指方法的該類對象。

public class Demo {
    public static void main(String[] args) {
        User user = new User();
        user.printByCount("abc",5);
    }
}

public class User  {
    public void printByCount(String str, int count) {
        /*Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < count; i++) {
                    System.out.println(i + str);
                }
            }
        };*/
        Runnable runnable = () -> {
            //User tt = this; // // [email protected]
            for (int i = 0; i < count; i++) {
                //str = "aaa"; // 編譯會報錯:Variable used in lambda expression should be final or effectively final
                System.out.println(i + str); // [email protected]
                System.out.println(this);
            }
        };
        new Thread(runnable).start(); // 線程結束,可能for循環還沒結束,Lambda表達式可通路
    }
}
           

4、方法引用:demo 有展現,下面有說明。

     方法引用通過方法的名字來指向一個方法。使用一對冒号 :: 。 

5、構造器/構造方法引用

     對于一個現有構造函數,可以利用它的名稱和關鍵字new來建立它的一個引用ClassName::new

     基本文法:類名::new    

     構造器引用對應的函數式接口的方法格式一定是:傳回一個對象并且方法沒有參數。

public class Demo {

    public static void main(String[] args) throws UnsupportedEncodingException {

        //List<Integer> list2 = Utils.asList(()->{ return new LinkedList<>(); }, 1, 5, 4, 2, 4);
        // 構造方法引用
        List<Integer> list2 = Utils.asList(LinkedList::new, 1, 5, 4, 2, 4);
        System.out.println(list2.toString()); 

        list2 = Utils.asList(ArrayList::new, 1, 5, 4, 2, 4);
        System.out.println(list2.toString()); 
    }
}

// 根據傳入的不同list對象,傳回List
public class Utils {
    public static <T> List<T> asList(MyCreator<List<T>> creator,T... a) {
        List<T> list = creator.create(); // 得到傳入的對象
        System.out.println(list.getClass());
        for (T t : a)  list.add(t);
        return list; // 把元素放到list并傳回
    }
}

@FunctionalInterface
public interface MyCreator<T extends List<?>> { // extends 傳進來的T必須是List的子類
    T create(); //傳回一個對象并且方法沒有參數
}           
Java8 Lambda等新特性

6、使用 forEach(...)周遊集合元素

public class Demo {

    public static void main(String[] args) throws UnsupportedEncodingException {
        List<Integer> list = new ArrayList<>();
        list.add(5);
        list.add(4);
        list.add(6);
        list.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
        list.forEach(x -> System.out.println(x)); 
        list.forEach(System.out::println); // System.out 等價于 PrintStream對象

        Map<String, Object> map1 = new HashMap<>();
        Map<String, Object> map2 = new HashMap<>();
        Map<String, Object> map3 = new HashMap<>();
        map1.put("key1", "v1");
        map2.put("key2", "v2");
        map3.put("key3", "v3");
        List<Map<String, Object>> list1 = new ArrayList<>();
        list1.add(map1);
        list1.add(map2);
        list1.add(map3);
        list1.forEach(new Consumer<Map<String, Object>>() {
            @Override
            public void accept(Map<String, Object> stringObjectMap) {
                stringObjectMap.forEach(new BiConsumer<String, Object>() {
                    @Override
                    public void accept(String key, Object value) {
                        System.out.println(key + ":" + value);
                    }
                });
            }
        });
        list1.forEach(map -> {
            map.forEach((key, value) -> {
                System.out.println(key + ":" + value);
            });
        });
        
        list1.forEach(Demo::accept);
    }

    private static void accept(Map<String, Object> map) {
        map.forEach((key, value) -> {
            System.out.println(key + ":" + value);
        });
    }
}           

最後,使用Lambda 表達式來建立線程啟動:

public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(i);
                }
            }
        }).start();

        new Thread(() -> {
            for (int i = 5; i < 10; i++) {
                System.out.println(i);
            }
        }).start();
    }           

二、函數式接口(Functional Interface)

1、  函數式接口介紹     

      在 Java中,把有且僅有一個抽象方法的接口稱為函數式接口,但是可以有多個非抽象方法的接口。

      函數式接口是Java支援函數式程式設計的基礎,也是使用 Lambda 表達式調用的前提。

      定義了一個函數式接口如下:

@FunctionalInterface
public interface Runnable {

    public abstract void run();
}           

1. 推論:

  •      1)我們能夠寫 Lambda表達式的地方?
  •            需要傳遞的參數是一個接口,并且該接口裡面有且僅有一個抽象方法需要實作,即函數式接口。JAVA 8 之前一般是用匿名類實作的。
  •      2)可以在函數式接口上添加 @FunctionInreface标簽(好處:檢查和javadoc文檔),表明這是一個函數式接口。
  •      3)無論是否标示@FunctionalInterface,隻要滿足函數式接口的接口,Java都會直接識别為函數式接口,
  •      4)簡化函數式接口的使用是 Java中提供 Lambda表達式唯一的作用。
  •      5)可是使用接口來引用一個 Lambda表達式(反推:不知道“我是誰”)。
  •      6)函數式接口裡面可以寫 Object對象中的方法(了解有且僅有一個抽象方法)。

2. 函數式接口可以對現有的函數友好地支援 Lambda表達式

     JDK 1.8 之前已有的函數式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

     JDK 1.8 新增加的函數接口:java.util.function 包,專門用來支援 Java的 函數式程式設計,該包中的函數式接口有:

序号 接口 & 描述
1

BiConsumer<T,U>

代表了一個接受兩個輸入參數的操作,并且不傳回任何結果

2

BiFunction<T,U,R>

代表了一個接受兩個輸入參數的方法,并且傳回一個結果

3

BinaryOperator<T>

代表了一個作用于于兩個同類型操作符的操作,并且傳回了操作符同類型的結果

4

BiPredicate<T,U>

代表了一個兩個參數的boolean值方法

5

BooleanSupplier

代表了boolean值結果的提供方

6

Consumer<T>

代表了接受一個輸入參數并且無傳回的操作

7

DoubleBinaryOperator

代表了作用于兩個double值操作符的操作,并且傳回了一個double值的結果。

8

DoubleConsumer

代表一個接受double值參數的操作,并且不傳回結果。

9

DoubleFunction<R>

代表接受一個double值參數的方法,并且傳回結果

10

DoublePredicate

代表一個擁有double值參數的boolean值方法

11

DoubleSupplier

代表一個double值結構的提供方

12

DoubleToIntFunction

接受一個double類型輸入,傳回一個int類型結果。

13

DoubleToLongFunction

接受一個double類型輸入,傳回一個long類型結果

14

DoubleUnaryOperator

接受一個參數同為類型double,傳回值類型也為double 。

15

Function<T,R>

接受一個輸入參數,傳回一個結果。

16

IntBinaryOperator

接受兩個參數同為類型int,傳回值類型也為int 。

17

IntConsumer

接受一個int類型的輸入參數,無傳回值 。

18

IntFunction<R>

接受一個int類型輸入參數,傳回一個結果 。

19

IntPredicate

:接受一個int輸入參數,傳回一個布爾值的結果。

20

IntSupplier

無參數,傳回一個int類型結果。

21

IntToDoubleFunction

接受一個int類型輸入,傳回一個double類型結果 。

22

IntToLongFunction

接受一個int類型輸入,傳回一個long類型結果。

23

IntUnaryOperator

接受一個參數同為類型int,傳回值類型也為int 。

24

LongBinaryOperator

接受兩個參數同為類型long,傳回值類型也為long。

25

LongConsumer

接受一個long類型的輸入參數,無傳回值。

26

LongFunction<R>

接受一個long類型輸入參數,傳回一個結果。

27

LongPredicate

R接受一個long輸入參數,傳回一個布爾值類型結果。

28

LongSupplier

無參數,傳回一個結果long類型的值。

29

LongToDoubleFunction

接受一個long類型輸入,傳回一個double類型結果。

30

LongToIntFunction

接受一個long類型輸入,傳回一個int類型結果。

31

LongUnaryOperator

接受一個參數同為類型long,傳回值類型也為long。

32

ObjDoubleConsumer<T>

接受一個object類型和一個double類型的輸入參數,無傳回值。

33

ObjIntConsumer<T>

接受一個object類型和一個int類型的輸入參數,無傳回值。

34

ObjLongConsumer<T>

接受一個object類型和一個long類型的輸入參數,無傳回值。

35

Predicate<T>

接受一個輸入參數,傳回一個布爾值結果。

36

Supplier<T>

無參數,傳回一個結果。

37

ToDoubleBiFunction<T,U>

接受兩個輸入參數,傳回一個double類型結果

38

ToDoubleFunction<T>

接受一個輸入參數,傳回一個double類型結果

39

ToIntBiFunction<T,U>

接受兩個輸入參數,傳回一個int類型結果。

40

ToIntFunction<T>

接受一個輸入參數,傳回一個int類型結果。

41

ToLongBiFunction<T,U>

接受兩個輸入參數,傳回一個long類型結果。

42

ToLongFunction<T>

接受一個輸入參數,傳回一個long類型結果。

43

UnaryOperator<T>

接受一個參數為類型T,傳回值類型也為T。

2、對上面幾個函數式接口的簡單使用,明白其中一個思想,其他的雷同。

1. Predicate接口

Predicate <T> 接口:它接受一個輸入參數 T,傳回一個布爾值結果。

該接口包含多種預設方法來将Predicate組合成其他複雜的邏輯(比如:與,或,非)。

test(T t)  在給定的參數上計算這個謂詞。

public class LambdaDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
//        LambdaDemo.findEven(list, new Predicate<Integer>() {
//            @Override
//            public boolean test(Integer num) {
//                return num % 2 != 0;
//            }
//        });
        // Lambda表達式
        LambdaDemo.findEven(list, (num) -> {
            return num % 2 != 0;
        });
        System.out.println(list);   //
    }

    // 定義一個方法,擷取集合的偶數等
    public static void findEven(List<Integer> list, Predicate<Integer> predicate) {
        Iterator<Integer> it = list.iterator();
        while (it.hasNext()) {
            Integer integer = it.next();
            if (predicate.test(integer)) {
                it.remove();
            }
        }
    }
}           

2. Function接口

Function<T,R>接口,表示接受一個參數并産生結果的函數。

  • T -功能的輸入類型 
  • R -函數的結果類型 
    • default <V> Function<T,V>

      andThen(Function<? super R,? extends V> after)

      傳回一個由功能首次采用該函數的輸入,然後将

      after

      函數的結果。

      R

      apply(T t)

      将此函數應用于給定的參數。

      default <V> Function<V,R>

      compose(Function<? super V,? extends T> before)

      傳回一個由功能,首先應用

      before

      函數的輸入,然後将該函數的結果。

      static <T> Function<T,T>

      identity()

      傳回一個函數,它總是傳回它的輸入參數。

compose與andThen及indentity三個方法在其他函數式接口中也有,作用是一樣的。

public static void main(String[] args) {
        // 定義兩個Function的計算函數
        Function<Integer, Integer> f = s -> s+1;
        Function<Integer, Integer> g = s -> s * 2;

        /**
         * 表示執行F時,先執行G,并且執行F時使用G的輸出當作輸入。
         * 相當于以下代碼:
         * Integer a = g.apply(1);
         * System.out.println(f.apply(a));
         */
        System.out.println(f.compose(g).apply(1)); // 3

        /**
         * 表示執行F的Apply後使用其傳回的值當作輸入再執行G的Apply;
         * 相當于以下代碼
         * Integer a = f.apply(1);
         * System.out.println(g.apply(a));
         */
        System.out.println(f.andThen(g).apply(1)); // 4

        /**
         * identity方法會傳回一個不進行任何處理的Function,即輸出與輸入值相等;
         */
        System.out.println(Function.identity().apply("123")); // 123
    }           

3. BiFunction接口

BiFunction<T,U,R>接口,表示接受兩個參數并産生結果的函數。

  • T -函數的第一個參數的類型 
  • U -函數的第二個參數的類型 
  • R -函數的結果類型

BiFunction中有一個andThen預設方法和apply(T t, U u) 抽象方法

public static void main(String[] args) {
        System.out.println(LambdaDemo.compute1(2, 3, (a, b) -> a * b)); // 6
        System.out.println(LambdaDemo.compute2(2, 3, (a, b) -> a * b, r -> r + 4)); // 10
    }

    // 定義一個計算兩個數的方法,傳回結果。計算方式自己實作
    public static int compute1(int a, int b, BiFunction<Integer, Integer, Integer> biFunction) {
        return biFunction.apply(a, b);
    }
    public static int compute2(int a, int b, BiFunction<Integer, Integer, Integer> biFunction, Function<Integer, Integer> function) {
        return biFunction.andThen(function).apply(a, b);
    }           

4. Supplier接口

Supplier<T>接口,表示結果的供應商/供給者。隻出不入,可以了解為,用來存儲資料的容器,可提供其他方法使用。

  • T -結果由該供應商的類型 
    • T

      get()

      得到一個結果。
public static void main(String[] args) {
        Supplier supplier = new Supplier() {
            @Override
            public Object get() {
                return new Random().nextInt();
            }
        };
        System.out.println(supplier.get());

        supplier = () -> new Random().nextInt();
        System.out.println(supplier.get());
    }           

5. Consumer接口

Consumer<T>接口,表示接受一個輸入參數,并傳回沒有結果的操作。隻有輸入無輸出。

  • T -操作的輸入類型 

抽象accept方法,還有一個andThen預設方法。

public static void main(String[] args) {
        Consumer f = n -> System.out.println(n + "來了老弟");
        Consumer f2 = n -> System.out.println(n + "來了老妹");

        f.andThen(f2).accept("admin");
    }           

6. Comparator接口

Consumer<T>接口,表示接受一個輸入參數,并傳回沒有結果的操作。隻有輸入無輸出。

  • T -操作的輸入類型 
public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(4);
        list.add(3);
        list.add(2);

//        list.sort(new Comparator<Integer>() {
//            @Override
//            public int compare(Integer o1, Integer o2) {
//                return o1 - o2;
//            }
//        });

        list.sort((o1, o2) -> o1 - o2);
        System.out.println(list);
    }           

3、自定義一個函數式接口

     需求:傳回輸入字元串的大寫

//業務應用類
public class Demo {
    public static void main(String[] args) { 
        User user = new User();

        /*String upperCaseStr = user.printUpperCase("adCdefG", new InterfaceDemo() {
            @Override
            public String doUpperCase(String str) {
                return str.toUpperCase();
            }
        });*/

        //String upperCaseStr = user.printUpperCase("adCdefG",(str) -> { return str.toUpperCase(); });

        //String upperCaseStr = user.printUpperCase("adCdefG",(str) -> str.toUpperCase());

        String upperCaseStr = user.printUpperCase("adCdefG",String::toUpperCase);
        System.out.println(upperCaseStr);
       // 輸出結果:printUpperCase:adCdefG
       // ADCDEFG

    }

// 操作工具類
public class User  {
    public String printUpperCase(String str,InterfaceDemo interfaceDemo) {
        System.out.println("printUpperCase:" + str);
        return interfaceDemo.doUpperCase(str);
    }
}

// 函數式接口
@FunctionalInterface
public interface InterfaceDemo {
    String doUpperCase(String str);

    boolean equals(Object obj);  // Object類中的方法
}           

三、方法引用

方法引用提供了非常有用的文法,可以直接引用已有Java類或對象(執行個體)的方法或構造器。

方法引用與Lambda 表達式一起使用,可以是代碼更緊湊簡潔,減少備援。

java8裡引入的一個方法引用符 ::,使用一對冒号 。 表示當Lambda 表達式建立函數式接口的實作類對象,正好Lambda 要寫的抽象體是引用其他類的方法時,就可以使用。

Arrays.sort(strs, Comparator.comparingInt(String::length));           

1、靜态方法引用

格式:類名::方法名

注意事項:

  • 被引用的方法參數清單和函數式接口中抽象方法的參數清單要一緻。
  • 接口的抽象方法沒有傳回值,引用的方法可以有傳回值也可以沒有。
  • 接口的抽象方法有傳回值,引用的方法必須有相同類型的傳回值。

2、對象方法引用

格式:對象名::非靜态方法名

注意事項與靜态方法引用完全一緻。

3、構造方法引用

格式:類名::new

注意事項:

  • 被引用的類必須存在一個構造方法與函數式接口的抽象方法參數清單一緻。

4、數組構造方法引用

格式:資料類型[ ]::new

5、特定類型的方法引用

格式:類名::非靜态方法

比如:在Comparator函數式接口的抽象方法中傳入的參數有兩個,可是compareToIgnoreCase()方法參數隻有一個,第一個傳入的參數作調用對象。這就滿足了特定類型的方法引用,是以可以簡化成類名::非靜态方法的形式。

public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "sasdfa", "ASDsda", "aa", "Aa");
        
//        Collections.sort(list, String::compareToIgnoreCase);
        list.sort(String::compareToIgnoreCase);
        System.out.println(list);
    }           

6、類中方法調用父類或本類方法引用

格式:

    this::方法名

    super::方法名

注意:

  • 在有繼承關系的類中,若方法想調用本類或父類的成員方法,在函數式接口抽象方法與成員方法參數清單相同,且傳回值類型相同的情況下,也可以使用this和super的方法引用來簡寫原本的lambda代碼。

四、接口中的預設方法

預設方法就是一個在接口裡面有了一個實作的方法。這些方法不需要接口的實作類去強制實作。隻需在方法名前面加個 default 關鍵字即可實作預設方法。

1、為什麼要有這個特性?

        首先,之前的接口是個雙刃劍,好處是面向抽象而不是面向具體程式設計,缺陷是,當需要修改接口時候,需要修改全部實作該接口的類,目前的 java 8 之前的集合架構沒有 foreach 方法,通常能想到的解決辦法是在JDK裡給相關的接口添加新的方法及實作。然而,對于已經釋出的版本,是沒法在給接口添加新方法的同時不影響已有的實作。是以引進的預設方法。他們的目的是為了解決接口的修改與現有的實作不相容的問題。

2、為了解決這一問題Java 8引入了一種新的機制。

Java 8允許在接口内聲明靜态方法,也可以在在接口内聲明預設方法,通過預設方法可以指定接口方法的預設實作。

有了預設方法後,實作接口的類如果不顯式地提供該方法的具體實作,就會自動繼承預設的實作。

這種機制可以平滑地進行接口的優化和演進。同時,常見的一些設計模式被改變了,比如:工具類,擴充卡模式等。

3、解決沖突方法

Java 使用的是單繼承、多實作的機制,為的是避免多繼承帶來的調用歧義的問題。

當接口的子類同時擁有具有相同簽名的方法時,比如:多個有預設方法的接口被一個類實作的時候,預設方法的多繼承問題。就需要考慮解決沖突的方案。

  1. 如果兩個接口中,有兩個方法簽名有相同的預設方法,子類必須實作沖突的預設方法,并指定使用哪個接口中的預設方法。接口名.super.預設方法();的方式手動調用需要的接口預設方法。
  2. 如果父類裡面有一個方法和接口中的一個預設方法有相同的方法簽名,那麼使用父類裡面的方法。
  3. 永遠不要期望通過接口中的預設方法來改變 Object對象中的方法。
public class Forg extends BigMouth implements IWaterAnimal,ILandAnimal{
    @Override
    public void run() {
        System.out.println("對不起,我隻會跳");
    }

    @Override
    public void swim() {
        System.out.println("對不起,我隻會蛙泳");
    }

    @Override
    public void breath() {
        //IWaterAnimal.super.breath();
        ILandAnimal.super.breath();
    }

    public static void main(String[] args) {
        Forg forg = new Forg();
        forg.swim();
        forg.run();
        forg.breathInWater();
        forg.breathInAir();
        forg.breath();
        forg.openMouth();
        IWaterAnimal.staticMethod();
    }
}

public interface IWaterAnimal {
    void swim();
    // 預設方法
    default void breathInWater(){
        System.out.println("IWaterAnimal->breathInWater");
    }
    default void breath(){
        System.out.println("IWaterAnimal->breath");
    }
    default void openMouth(){
        System.out.println("IWaterAnimal->openMouth");
    }

    // 靜态方法
    static void staticMethod(){
        System.out.println("IWaterAnimal->靜态方法");
    }
}

public interface ILandAnimal {
    void run();

    default void breathInAir(){
        System.out.println("ILandAnimal->breathInAir");
    }
    default void breath(){
        System.out.println("ILandAnimal->breath");
    }
}

public class BigMouth {
    public void openMouth(){
        System.out.println("大嘴巴嘴巴大");
    }
}           
Java8 Lambda等新特性

五、新的Date-Time API:Java8中的時間和日期(下)

      Java 8通過釋出新的Date-Time API (JSR 310)來進一步加強對日期與時間的處理。

      在舊版的 Java 中,日期時間 API 存在諸多問題,其中有:

  • 非線程安全 − java.util.Date 是非線程安全的,所有的日期類都是可變的,這是Java日期類最大的問題之一。
  • 設計很差 − Java的日期/時間類的定義并不一緻,在java.util和java.sql的包中都有日期類,此外用于格式化和解析的類在java.text包中定義。java.util.Date同時包含日期和時間,而java.sql.Date僅包含日期,将其納入java.sql包并不合理。另外這兩個類都有相同的名字,這本身就是一個非常糟糕的設計。
  • 時區處理麻煩 − 日期類并不提供國際化,沒有時區支援,是以Java引入了java.util.Calendar和java.util.TimeZone類,但他們同樣存在上述所有的問題。

   Java 8 在 java.time 包下提供了很多新的 API。以下為兩個比較重要的 API:

  • Local(本地) − 簡化了日期時間的處理,沒有時區的問題。
  • Zoned(時區) − 通過制定的時區處理日期時間。

   新的java.time包涵蓋了所有處理日期,時間,日期/時間,時區,時刻(instants),過程(during)與時鐘(clock)的操作。

六、Stream流API操作:Java8 Stream API詳細操作   

七、Optional<T> 類 

Java 8 引入的 Optional類。主要用來解決空指針異常(NullPointerException),空指針異常是導緻Java應用程式失敗的最常見原因。

Optional類可以了解為一個容器:它既可以儲存類型T的對象,也可以儲存null。

Optional類提供了很多有用的方法,并實作了檢查空值的功能,程式員就不用顯式地寫空值檢測的代碼,再結合函數式程式設計,使得代碼更加幹淨,簡潔。

1、建立一個Optional對象

static <T> Optional<T>

of(T value)

傳回與指定非空值的

Optional

目前。

static <T> Optional<T>

ofNullable(T value)

傳回一個

Optional

描述指定的值,如果非零,則傳回一個空

Optional

static <T> Optional<T>

empty()

傳回一個空

Optional

執行個體。
public static void main(String[] args) {
        Optional<Integer> integer = Optional.of(10);
        Optional<String> s = Optional.ofNullable("10");
        Optional<Object> n = Optional.ofNullable(null);
        System.out.println(n); // Optional.empty
        // 如果儲存的是null值,不能直接使用get方法擷取,會NPE
        if (n.isPresent()) {
            System.out.println(n.get()); // 無輸出
        }
    }           

注意:Optional.empty() 所有儲存null包裝成的Optional對象,而且是單例模式的。與泛型無關。

Object o1 = Optional.<Integer>empty();
        Object o2 = Optional.<String>empty();
        System.out.println(o1 == o2);// true           

 2、Optional類的常用方法

Optional<T>

filter(Predicate<? super T> predicate)

如果一個值是存在的,而價值比對給定謂詞,傳回一個

Optional

描述值,否則傳回一個空

Optional

<U> Optional<U>

flatMap(Function<? super T,Optional<U>> mapper)

如果一個值是存在的,将提供

Optional

-bearing映射函數,傳回結果,否則傳回一個空

Optional

T

get()

如果一個值是在這

Optional

,傳回值,否則将

NoSuchElementException

int

hashCode()

傳回目前值的哈希代碼值,如果有的話,或0(0),如果沒有價值。

void

ifPresent(Consumer<? super T> consumer)

如果一個值存在,調用指定的值的指定的使用者,否則什麼都不做。

boolean

isPresent()

傳回

true

如果有存在價值,否則

false

<U> Optional<U>

map(Function<? super T,? extends U> mapper)

如果一個值是存在的,申請提供的映射函數,如果結果不為空,傳回一個

Optional

描述結果。

T

orElse(T other)

如果儲存的值不是null,則傳回原來的值,否則傳回

other

T

orElseGet(Supplier<? extends T> other)

如果儲存的值不是null,則傳回原來的值,否則調用 生産者函數 并傳回調用結果的。

<X extends Throwable> T

orElseThrow(Supplier<? extends X> exceptionSupplier)

如果儲存的值不是null,則傳回原來的值,否則抛出一個由提供的供應商建立的異常。
public static void main(String[] args) {
        Object orElse1 = Optional.ofNullable(null).orElse(getValue("orElse1"));
        String s = Optional.ofNullable("fill").orElse(getValue("orElse2"));
        System.out.println(orElse1);
        System.out.println(s);

        System.out.println("============");
        Object o1 = Optional.ofNullable(null).orElseGet(() -> getValue("orElseGet1"));
        String o2 = Optional.ofNullable("fill").orElseGet(() -> getValue("orElseGet2"));
        System.out.println(o1);
        System.out.println(o2);

        Optional.ofNullable("fill").orElseThrow(RuntimeException::new);
        Optional.ofNullable(null).orElseThrow(RuntimeException::new);
    }
    private static String getValue(String s) {
        System.out.println(s + ":我執行了");
        return s + new Random().nextInt(100);
    }           

注意:orElse和orElseGet區:

  • 如果Optional儲存的是null值時,兩者調用方法的效果一樣的。
  • 如果Optional儲存的不是null值時,那麼orElse方法裡面的代碼會執行,而orElseGet方法裡面的代碼不會執行。
  • 另外orElse還可以放任何類型 T的值,而orElseGet必須是 Supplier函數。 
Java8 Lambda等新特性

 熟悉函數式接口的作用,使用Optional就很容易,上面使用三個方法。

也可以參考文章:了解、學習與使用 Java 中的 Optional

八、Base64 : Base64加密和解密JDK8         

       在 Java8中 Base64編碼已經成為Java類庫的标準,且内置了Base64編碼的編碼器和解碼器。

       新的Base64API也支援URL和MINE的編碼解碼。

    站在前輩的肩膀上,每天進步一點點

ends~