Java8新特性學習筆記(1)----Lamda表達式

1. Lambda表達式

1.1 示例

Thread thread = new Thread(() -> {
    System.out.println("HelloWorld");
});
thread.start();
複製代碼

1.2 定義

Lambda表達式理解爲簡潔地表示可傳遞的匿名函數的一種方式:它沒有名稱,但它 有參數列表函數主體返回類型,可能還有一個能夠拋出的異常列表」(參考Java8實戰)java

Lambda表達式包含3部分express

  • 參數列表:函數參數列表
  • 箭頭:分割參數列表和主體
  • Lambda主體:函數主體

1.2.1 參數列表

參數列表使用括號()來表示,其中參數能夠聲明參數類型,也能夠不聲明參數類型(編譯器會根據上下文推斷)。bash

1.2.2 函數體(Lambda主體)

Lambda表達式的函數體,既能夠是代碼塊,也能夠是表達式代碼塊:和普通方法的方法主體一致; 表達式表達式會被執行並返回結果,其本質上是一種return語句的簡寫(省略了return和{})app

1.2.3 特殊表達

Lambda不只能夠省略參數類型用表達式表示方法體,其實還能夠直接使用方法引用來替代Lambda表達式,例如以下三種方式是等價的:dom

Consumer<String> consumer1 = (String str) -> {
    System.out.println(str);
};
Consumer<String> consumer2 = (str) -> {
    System.out.println(str);
};
Consumer<String> consumer3 = System.out::println;
複製代碼
  • 暫時仍是沒太理解第三種的本質。。

1.3 特色

Lambda表達式擁有以下特色:ide

  • 匿名:和普通方法相比,沒有明確的方法名
  • 函數:Lambda函數和方法區別是不屬於某個特定的類
  • 傳遞:Lambda表達式能夠做爲參數傳遞給方法,或者存儲在變量中。
  • 簡潔:無需匿名內部類那樣寫不少複雜的函數。

1.4 Lambda表達式和匿名內部類

Lambda表達式用來爲某個抽象方法提供實現,我的能夠粗略地講其理解爲一種匿名內部類更加通俗易懂,但二者並不徹底相同。函數

相同點

二者有以下相同點:ui

  • 都沒有表現出來的類名(匿名內部類會生成.class文件,Lambda表達式好像會在第一次被使用時動態生成一個類?);
  • 均可以訪問外部的變量和方法。
  • 只能訪問外部final變量,不能修改外部變量。
final String word = "Hello";
Runnable runnable = () -> {
    // 不能夠在Lambda表達式中修改外部變量,不然會提示Variable used in lambda expression should be final or effectively final
    // word = "HelloWorld";
    function1();
    System.out.println(word);
};
runnable.run();

Runnable runnable1 = new Runnable() {
    @Override
    public void run() {
        function1();
        // 不能夠在內部類中修改外部變量,不然會提示Variable is accessed within inner class. Needs to be final or effectively final
        // word = "HelloWorld";
        System.out.println(word);
    }
};
runnable1.run();
複製代碼

不一樣點

  • 實現父類(或接口)不一樣。Lambda表達式只能夠實現函數式接口,而匿名內部類能夠實現擁有多個抽象方法的接口、抽象類和普通類。
  • 匿名內部類能夠調用接口中其它方法,但Lambda表達式不能夠。(本質上應該是下一個不一樣點的第2小點)
  • 二者做用域不一樣。(1)Lamda表達式中this指的是外部類的對象,而內部類中this指的是內部類對象。見做用域示例1。 (2)Lamda表達式中調用的方法也都是外部類的,但內部類中調用的方法是優先內部的。
做用域示例1
public class HelloLambda {

    Runnable r1 = () -> {
        System.out.println("Lambda, 1: " + this);
        System.out.println("Lambda, 2: " + toString());
    };

    Runnable r2 = new Runnable() {
        @Override
        public void run() {
            System.out.println("Inner class, 1: " + this);
            System.out.println("Inner class, 2: " + toString());
        }
    };

    @Override
    public String toString() {
        return "Hello, lambda!";
    }

    public static void main(String[] args) {
        new HelloLambda().r1.run();
        new HelloLambda().r2.run();
    }
}
複製代碼

輸出:this

做用域示例2

Lambda表達式r1因爲外部類中沒有run()方法,因此報錯;但r2因爲內部類中有run()方法,全部能夠經過編譯,實際上就是循環調用了。 編碼

2. 函數式接口

函數式接口就是有且僅有一個抽象方法的接口
Lambda表達式容許直接之內聯的形式爲函數式接口的抽象方法提供實現,並把整個表達式做爲函數式接口的實例。(其實能夠將Lambda表達式看作匿名內部類,該匿名內部類實現了函數式接口)。

特殊說明:

  • 函數式接口中除了特定的抽象方法外,還能夠有非抽象方法
  • 若是在接口中有重寫了Obejct的public方法的抽象方法,那麼該方法也不影響函數式接口抽象方法的計數。

接下來介紹幾種常見函數式接口。

2.1 Runnable和Callable接口

Runnable

Runnable接口源碼

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
複製代碼

觀察Runnable源碼,能夠發現該接口中只有一個抽象方法run(),故能夠使用Lambda表達式來做爲該函數式接口的實例,並提供抽象方法的實現。

Runnable runnable = () -> {
    System.out.println("Hello world!");
};
複製代碼

Callable

Callable接口源碼:

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
複製代碼

Callable接口的Lambda表達式示例:

Callable<String> callable = () -> {
    return "HelloWorld";
};
複製代碼

Callable接口有返回值,Lambda主體能夠直接使用表達式替代return語句,以下:

Callable<String> callable = () -> "HelloWorld";
複製代碼

2.2 Comparator接口

源碼(部分)

@FunctionalInterface
public interface Comparator<T> {
    // 抽象方法
    int compare(T o1, T o2);
    // 重寫Obejct類public方法的抽象方法,不進行計數
    boolean equals(Object obj);
    // 非抽象方法
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
}
複製代碼

觀察源碼,能夠發現Comparator實現類:(1)須要聲明泛型;(2)須要實現compare方法,且參數列表有兩個,參數類型與泛型類上聲明一致。

Lambda表達式示例

簡單示例

Comparator<Integer> comparator = (o1, o2) -> o1.compareTo(o2);
複製代碼

列表倒序排序示例:

List<Integer> numbers = Lists.newArrayList(1, 2, 3);
numbers.sort((x1, x2) ->  x2.compareTo(x1));
numbers.forEach((x) -> {
    System.out.println(x);
});
複製代碼

輸出:

按照對象屬性排序

按user年齡倒序排序:

List<User> users = Lists.newArrayList(new User(18), new User(22), new User(24));
users.sort((u1, u2) -> u2.age.compareTo(u1.age));
users.forEach((u) -> {
    System.out.println(u.age);
});
複製代碼

輸出:

2.3 Consumer

Consumer接口是一種有參無返回值的消費型接口

源碼

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
複製代碼

Consume接口能夠理解爲一種通用消費型接口能夠接收參數,但不能返回),經過lambda表達式定義好要執行的動做,經過調用accept方法執行。

簡單示例

Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Hello world1!");
複製代碼

拿其與Runable接口比較更好理解,Runnable接口也能夠直接執行run方法(就沒有線程特性了),可是沒法接受傳入的參數。比較

Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Hello world1!");

Runnable runnable = () -> {
    System.out.println("Hello world2!");
};
runnable.run();
複製代碼

回調示例

我的理解,Consumer接口最多見的用途是做爲回調
以下所示,function1接收參數list、以及回調函數consumer,function1自己不關心如何處理、直接執行consumer.accept(),具體的處理由傳入的consumer變量(lambda表達式、回調函數)決定,能夠經過修改傳入的consumer變量來修改函數的實現。

public static void function1(List<Integer> list, Consumer<List<Integer>> consumer) {
    consumer.accept(list);
}

public static void main(String[] args) {
    // 存儲lambda表達式
    Consumer<List<Integer>> consumer1 = list -> {
        list.sort((x1, x2) -> x2.compareTo(x1));
    };
    Consumer<List<Integer>> consumer2 = list -> {
        list.sort((x1, x2) -> x1.compareTo(x2));
    };
    
    
    List<Integer> numbers = Lists.newArrayList(1, 2, 3);
    // 傳遞lambda表達式
    function1(numbers, consumer1);
    numbers.forEach((x) -> {
        System.out.println(x);
    });
    
    function1(numbers, consumer2);
    numbers.forEach((x) -> {
        System.out.println(x);
    });
}
複製代碼

BiConsumer

BiConsumer接口則是Consumer接口升級版,能夠接受兩個參數:

BiConsumer<String, Integer> biConsumer = (str, num) -> {
    System.out.println(str + num);
};
biConsumer.accept("Hello, ", 123);
複製代碼

還有其餘消費型接口,好比IntConsumer。

2.4 Supplier接口

Supplier接口是一種無參有返回值的供給型接口。能夠理解爲一個容器,能夠生成、存儲對象,供其它方法調用其中get()方法獲取對象。

源碼

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
複製代碼

簡單示例

Supplier<User> supplier = () -> {
    return new User(123);
};
User user = supplier.get();
System.out.println(user.age);
複製代碼

回調示例

以下所示,getId方法用於返回一個int型整數,但不一樣的地方可能使用不一樣的生成策略,因此使用Supplier接口做爲參數,將id生成策略回調出去。

public static Integer getId(Supplier<Integer> supplier) {
    return supplier.get();
}

public static void main(String[] args) {
    Supplier<Integer> supplier1 = () -> (new Random()).nextInt(10);
    Supplier<Integer> supplier2 = () -> (new Random()).nextInt(100);
    System.out.println(getId(supplier1));
    System.out.println(getId(supplier2));
}
複製代碼

可是我的理解上Supplier接口意義很低,不能接受參數、直接return,即使做爲回調也難以有太多實際使用場景。

2.5 Predicate接口

Predicate接口一種有參數返回值類型爲布爾型的斷言型接口

源碼

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
複製代碼

觀察源碼能夠發現,須要實現test()方法,而且還支持and/or聯合判斷條件。

使用示例

Predicate<Integer> predicate = (x) -> x < 100;
Predicate<Integer> predicate1 = (x) -> x > 0;
System.out.println(predicate.test(1));
System.out.println(predicate.and(predicate1).test(-1));
複製代碼

輸出結果:

2.6 Function接口

源碼

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
複製代碼

觀察源碼獲得須要實現其中apply()方法,而lambda表達式的參數<T, R>,其中T表明函數的參數類型,R表明返回類型(第一個是參數,第二個是返回值)。
Function接口使用的場景較多Consumer/Predicate/Supplier接口能實現的功能用Function接口也均可以實現。

簡單示例

Function<Integer, String> function = (x) -> {
    if (x < 0) {
        return "小於0";
    } else {
        return "大於等於0";
    }
};
System.out.println(function.apply(3));
複製代碼

BiFunction

Function接口一樣也有用來接收兩個參數的BiFunction接口。

BiFunction<Integer, Integer, String> function = (x1, x2) -> {
    return new StringBuilder("Receive x1=").append(x1).append(", x2=").append(x2).toString();
};
複製代碼

3. 流

Java8新增了一個接口java.util.Stream,能夠將Collection、List、Set、Map等使用流進行處理,編碼更加方便快捷。Stream接口依賴於Lambda表達式。 詳見:。。。。。。。。。

相關文章
相關標籤/搜索