java8-lamdba

目標

基礎概念

引入兩個概念: 函數式接口,接口中的默認方法

  • 函數式接口:如果一個接口中,只包含一個抽象方法,就可以認爲是一個函數式接口。其中可以使用註解

    @FunctionalInterface 標識這個接口是一個函數式接口,當然不使用這個註解標識也是可以的。

    /**
     * 函數式接口,只包含一個抽象方法
     */
    @FunctionalInterface
    public interface MyPrint<T> {
        String output(T str);
    }
    
  • 默認方法

    默認方法用於擴展接口中的方法,jdk8 之後,爲了在接口中引入其他的功能,需要在接口中提供額外的方法,不能直接在原來的接口中,添加方法,這樣會導致實現該接口的類,都要做修改,所以就引入了默認方法,使用 default 標識。默認方法是一個非抽象的方法,實現該接口的類,也都繼承默認方法。也可以在接口中添加靜態方法,用來擴展接口的功能。

    // List 接口中的 sort 就是一個默認方法
    @SuppressWarnings({"unchecked", "rawtypes"})
        default void  sort(Comparator<? super E> c) {
            Object[] a = this.toArray();
            Arrays.sort(a, (Comparator) c);
            ListIterator<E> i = this.listIterator();
            for (Object e : a) {
                i.next();
                i.set((E) e);
            }
        }
     
      // Comparator 接口的中的 靜態方法
        public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
            return Collections.reverseOrder();
        }
    

lamdba 表達式

語法:(param1,param2) -> { return xxx }

首先,函數式接口,纔可以用lamdba 表達式,來表示。函數式接口中的默認方法或者靜態方法用來擴展,其他的功能。

示例:

函數式接口:

/**
* 函數式接口,只包含一個抽象方法
*/
@FunctionalInterface
public interface MyPrint<T> {
 
    String output(T str);
 
    default void outputinfo(){
        System.out.println("info");
    }
}

lamdba 表達式演示:

@Test
    public void fun2() {
        String strw = "aaa";
 
        // 使用匿名內部類
         System.out.println(new MyPrint<String>() {
            @Override
            public String output(String str) {
                return str+"1";
            }
        }.output(strw));
 
       // 使用lamdba 表達式
        MyPrint<String> myPrint = (str) -> str+"2";
        System.out.println(myPrint.output(strw));
    }

從上面演示效果,lamdba 表達式,相當於把匿名內部類中需要實現的方法實現了。那如果接口中,有兩個需要被實現的方法,就不能使用lamdba 表達式,因爲lamdba表達式不知道你要實現那個方法,所以只能是 函數式接口,才能用lamdba 表達式表示。

再演示一些其他例子:

排序:

對於lamdba 表達式,參數類型,return,或者花括號,在有時,都可以省略,看如下代碼:

List<String> strs = Arrays.asList("c", "a", "d");
// 之前的實現方式
Collections.sort(strs, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return o1.compareTo(o2);
    }
});
 
// lamdba 實現,將匿名內部類代碼改爲 lamdba 表達式
Collections.sort(strs, (String o1, String o2) -> {
    return o2.compareTo(o1);
});
// 只有一句,省略 return  和 花括號
Collections.sort(strs, (String o1, String o2) -> o2.compareTo(o1));
// 可以省略參數類型,會自動判斷
Collections.sort(strs, (o1, o2) -> o2.compareTo(o1));
 
// ---- 上面的lamdba 表達式,idea 會提示 黃色標識,因爲還可以更加簡單
 
// 自然順序的比較
Collections.sort(strs, Comparator.naturalOrder());
// 自然順序相反的比較
Collections.sort(strs, Comparator.reverseOrder());
 

線程創建

// 將原來  new Runnable 的代碼,變成了 lamdba 表達式  
@Test
    public void fun6() {
        new Thread(
                () -> System.out.println(Thread.currentThread().getName())
        ,"thread100").start();
    }

雙冒號 :: 關鍵字

雙冒號:: 關鍵字,用於引用方法和構造函數

方法引用是與lambda表達式相關的一個重要特性。它提供了一種不執行方法的方法。爲此,方法引用需要由兼容的函數接口組成的目標類型上下文。

oracle官方介紹:

Method References
You use lambda expressions to create anonymous methods. Sometimes, however, a lambda expression does nothing but call an existing method. In those cases, it’s often clearer to refer to the existing method by name. Method references enable you to do this; they are compact, easy-to-read lambda expressions for methods that already have a name.

方法引用

使用lambda表達式創建匿名方法。但是,有時lambda表達式只調用現有方法。在這些情況下,按名稱引用現有方法通常更清楚。方法引用使您能夠做到這一點;對於已經有名稱的方法,它們是緊湊、易於讀取的lambda表達式。

// TODO 對於方法引用,還不是很理解,但是在語義上,其實能看懂做了什麼事情,有了新理解,再補充

具體示例:參考 Java8新特性2:方法引用–深入理解雙冒號::的使用

在代碼中的體現,更突顯在java8 stream 中的應用

實現一個小例子:根據身份證號碼,將性別自動設置到對象的性別字段中

通用的身份證號碼設置性別方法

// 兩個參數一個是 idcard ,一個是   Consumer  接口
// 簡單介紹一下 Consumer 是一個函數式接口,接受一個參數,但是不返回結果。 執行的方法 accept
private void setSexInfo(String idcard, Consumer<String> user) {
        int genderByIdCard = IdcardUtil.getGenderByIdCard(idcard); // 根據身份證,獲取性別(參見 hutool 這個類庫)
        if (genderByIdCard == 1) {  // 男
            user.accept("M");
        }
        user.accept("F");  // 女
    }
 

用戶對象:

public class Person{
    String sex;
   public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
}

測試:

    @Test
    public void fun09(){
        Person person = new Person();
        // 使用匿名函數
        setSexInfo("410327188510154456", new Consumer<String>() {
            @Override
            public void accept(String s) {
                person.setSex(s);
            }
        });
        // 使用 lamdba 表達式
        setSexInfo("410327188510154456", s -> myComparator.setSex(s));
 
        // 使用 :: 雙冒號
        setSexInfo("410327188510154456", person::setSex);
    }

對於 Consumer 接口中執行的流程,可以看做是,當 user.accept(“M”); 執行時,M 會作爲參數,調用對接口的實現,就是 person.setSex(s); 的邏輯

在idea 中,會給出對應的優化建議,點代碼左邊提示的 黃色選項 。上述代碼,就可以從匿名函數到lamdba表達式到雙冒號的形式的優化

tada3F.png

Lambda的範圍

參見: java8簡明教程文檔

對於lambdab表達式外部的變量,其訪問權限的粒度與匿名對象的方式非常類似。 你能夠訪問局部對應的外部區域的局部final變量,以及成員變量和靜態變量。

一些函數式接口的簡單介紹

具體代碼示例:參考 java8 實戰 3.4.1 使用函數接口 這一章 或者 java8 簡明教程中的介紹

Predicate

接受一個輸入參數,返回一個boolean 的結果。在操作stream中,filter方法中接受的參數就是 Predicate 接口

    boolean test(T t);

示例:

/**
     * Predicate 斷言,一個布爾類型的函數
     * 可以用於 集合類,filter 的過濾等等
     */
    @Test
    public void fun() {
        // Predicate是一個布爾類型的函數
        Predicate<String> predicate = (s) -> s.length() > 0;
        boolean str1 = predicate.test("hello world");
        boolean str2 = predicate.test("");
 
        System.out.println(str1);
        System.out.println(str2);
 
        // 短路與 &&
        boolean test = predicate.and((s) -> s.equals("hello")).test("aaa");
        System.out.println(test);
        // 邏輯非
        boolean hello_world = predicate.negate().test("hello world");
        System.out.println(hello_world);
        // 邏輯或
        boolean aaa = predicate.or(predicate).test("aaa");
        System.out.println(aaa);
        // 判斷兩個對象是否相等
        boolean test2 = Predicate.isEqual("a2").test("a3");
        System.out.println(test2);
        boolean test3 = Predicate.isEqual("a3").test("a3");
        System.out.println(test3);
        boolean test4 = Predicate.isEqual(null).test("a3");
        System.out.println(test4);
 
        Predicate<String> ceshi1 = String::isEmpty;
        boolean test1 = ceshi1.test("");
        System.out.println(test1);
    }

Function

接收一個參數,返回一個結果。可以理解爲數學公式 y = f(x),傳入參數x,得到結果y. 在集合stream中,map方法接收的參數就是 Function

    R apply(T t);

示例:

/**
     * 表示接受一個參數,並返回一個結果
     * 可以理解爲  y = f(x)
     * 接受x 參數,輸出y,那麼 f(x) 這個函數,我們自己定義就可以了
     */
    @Test
    public void fun02() {
 
        // 定義 f(x) 函數
        Function<Integer, Integer> function = (x) -> x + 1;
        // 獲取結果
        Integer result = function.apply(5);
        System.out.println(result);
 
        // 定義 f(x) 函數
        Function<Integer, Integer> function1 = (x) -> x * 5;
 
        // compose 代表先執行 compose 傳入的邏輯,再執行apply 的邏輯
        // 6
        Integer apply = function.compose(function1).apply(1);
        System.out.println(apply);
 
        // andThen 代表先執行當前的邏輯,再執行,andthen 傳入的邏輯
        Integer apply1 = function.andThen(function1).apply(1);
        System.out.println(apply1);
 
        // ((1+1)+1)*5*5  建造者模式
        Integer apply2 = function.andThen(function1).andThen(function1).compose(function).apply(1);
        System.out.println(apply2);
 
        Function<String, Integer> toInteger = Integer::valueOf;
        Function<String, String> backToString = toInteger.andThen(String
                ::valueOf);
        backToString.apply("123"); // "123"
    }

Supplier

不接收參數,返回一個結果。可以理解爲 實體字段的get 方法

    T get();

示例:

    /**
     * 接口產生一個給定類型的結果
     */
    @Test
    public void fun03() {
        Supplier<String> str = String::new;
        String s = str.get();
        Supplier<Person> str1 = Person::new;
        // 在執行get 方法時,纔會拿到person 對象 ,spring beanfactory  在執行 getbean 的時候,纔會創建該對象
        // 延遲加載的功能
        Person person = str1.get();
    }

Consumer

接收一個參數,但是不返回結果。可以理解爲 實體字段的set 方法。在集合forEach 時,傳入的參數就是 Consumer 接口

void accept(T t);

示例:

    /**
     * 接受一個參數輸入且沒有任何返回值的操作
     * 在 集合的 foreach 中 就需要填這個接口
     */
    @Test
    public void fun04() {
        Consumer<Person> personConsumer = (t) -> System.out.println("第一打印" + t.toString());
        Consumer<Person> personConsumer2 = (t) -> System.out.println("第二打印" + t.toString());
        // 執行
        personConsumer.accept(new Person());
        // 現在執行 accpect ,再執行 addthen 添加的
        personConsumer.andThen(personConsumer2).accept(new Person());
        // foreach 代碼中,就調用改的 action.accept(t);
        Arrays.asList("1", "2").forEach((x) -> {
            System.out.println(x);
        });
    }

ToIntFunction

接收一個參數,返回一個int 類型的結果,跟Function 類似,這裏限定了返回的結果類型是 int

    int applyAsInt(T var1);

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章