從java8 提及函數式編程

寫在前面

爲何要用函數式編程。看例子:java

final List<BigDecimal> prices = Arrays.asList(
    new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"),
    new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"),
    new BigDecimal("45"), new BigDecimal("12"));

BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO;
for(BigDecimal price : prices) {
    if(price.compareTo(BigDecimal.valueOf(20)) > 0)
        totalOfDiscountedPrices = totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9)));
}
System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);

更優雅的方式,是使用聲明式的代碼:編程

final BigDecimal totalOfDiscountedPrices = 
    prices.stream()
          .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0)
          .map(price -> price.multiply(BigDecimal.valueOf(0.9)))
          .reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);

使用函數式代碼的好處:數組

  • 減小了可變量(Immutable Variable)的聲明
  • 可以更好的利用並行(Parallelism)
  • 代碼更加簡潔和可讀

函數式接口

函數式接口就是僅聲明瞭一個方法的接口,好比咱們熟悉的Runnable,Callable,Comparable等均可以做爲函數式接口。固然,在Java 8中,新添加了一類函數式接口,如Function,Predicate,Consumer,Supplier等。ide

@FunctionalInterface
public static interface IntHandler{
    void handle(int i);
}

在函數式接口中,能夠聲明0個或者多個default方法,這些方法在接口內就已經被實現了。所以,接口的default方法也是Java 8中引入的一個新概念。函數式編程

default method 默認方法,又稱做虛擬擴展方法或保衛者方法,例子:函數

import java.util.Objects;
import java.util.function.Consumer;

public interface Iterable<T> {
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
}

爲何須要default method?ui

  • 即便你的API已經發布出去了,你依然能夠爲接口添加新方法而且無需考慮向後兼容問題。
  • java8 對Lambda的支持必然會影響JDK API的接口,若是直接在接口中添加方法,就會致使全部實現該接口的類或者接口沒法經過編譯。

函數式接口使用 @FunctionalInterface註解進行標註。雖然這個註解的使用不是強制性的,可是使用它的好處是讓此接口的目的更加明確,同時編譯器也會對代碼進行檢查,來確保被該註解標註的接口的使用沒有語法錯誤。this

lambda表達式

lambda表達式即匿名函數,它是一段沒有函數名的函數體,能夠做爲參數直接傳遞給相關調用者。lua

Lambda 表達式一般使用 (argument) -> (body) 語法書寫,例如:線程

(arg1, arg2...) -> { body }
(type1 arg1, type2 arg2...) -> { body }

舉例來講:

(int a, int b) -> {  return a + b; }
() -> System.out.println("Hello World");
(String s) -> { System.out.println(s); }
() -> 42
() -> { return 3.1415 };

Lambda 表達式的結構

  1. 一個 Lambda 表達式能夠有零個或多個參數
  2. 參數的類型既能夠明確聲明,也能夠根據上下文來推斷。例如:(int a)與(a)效果相同
  3. 全部參數需包含在圓括號內,參數之間用逗號相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
  4. 空圓括號表明參數集爲空。例如:() -> 42
  5. 當只有一個參數,且其類型可推導時,圓括號()可省略。例如:a -> return a*a
  6. Lambda 表達式的主體可包含零條或多條語句
  7. 若是 Lambda 表達式的主體只有一條語句,花括號{}可省略。匿名函數的返回類型與該主體表達式一致
  8. 若是 Lambda 表達式的主體包含一條以上語句,則表達式必須包含在花括號{}中(造成代碼塊)。匿名函數的返回類型與代碼塊的返回類型一致,若沒有返回則爲空

每一個 Lambda 表達式都能隱式地賦值給函數式接口,例如,咱們能夠經過 Lambda 表達式建立 Runnable 接口的引用。

Runnable r = () -> System.out.println("hello world");

方法引用

方法引用是Java 8中提出的用來簡化lambda表達式的一種手段。它經過類名和方法名來定位到一個靜態方法或者實例方法。 方法引用在Java 8中的使用很是靈活。總的來講,能夠分爲如下幾種:

  1. 靜態方法引用:ClassName::methodName

    List<Integer> ints = Arrays.asList(1, 2, 3);
     ints.sort(Integer::compare);
  2. 實例上的實例方法引用 instanceReference::methodName,方法引用使用」::」定義,」::」的前半部分表示類名或者實例名,後半部分表示方法名稱。若是是構造函數,則使用new表示。

    words.forEach(System.out::println);
    
     words.stream().map(word -> word.length()); // lambda
     words.stream().map(String::length); // method reference
    
     // lambda
     words.stream().map(word -> {
         return new StringBuilder(word);
     });
     // constructor reference
     words.stream().map(StringBuilder::new);

Lambda 表達式舉例

1.線程初始化

//舊方法:
new Thread(new Runnable() {
@Override
public void run() {
    System.out.println("Hello from thread");
}
}).start();

//新方法:
new Thread(
() -> System.out.println("Hello from thread")
).start();

2.打印數組全部元素,在下面的例子中,咱們先是用經常使用的箭頭語法建立 Lambda 表達式,以後,使用 Java 8 全新的雙冒號(::)操做符將一個常規方法轉化爲 Lambda 表達式:

//Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
   System.out.println(n);
}

//New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));


//or we can use :: double colon operator in Java 8
list.forEach(System.out::println);

3.斷言函數式接口使用

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Test {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
        System.out.println("Print all numbers:");
        evaluate(list, n -> true);

        System.out.println("Print no numbers:");
        evaluate(list, n -> false);

        System.out.println("Print even numbers:");
        evaluate(list, n -> n % 2 == 0);

        System.out.println("Print odd  numbers:");
        evaluate(list, n -> n % 2 == 1);

        System.out.println("Print numbers greater than 5:");
        evaluate(list, n -> n > 5);
    }

    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for (Integer n : list) {
            if (predicate.test(n))
                System.out.print(n + " ");
        }
    }
}

Lambda 表達式與匿名類的區別

使用匿名類與 Lambda 表達式的一大區別在於關鍵詞的使用。對於匿名類,關鍵詞 this 解讀爲匿名類,而對於 Lambda 表達式,關鍵詞 this 解讀爲寫就 Lambda 的外部類。

Lambda 表達式與匿名類的另外一不一樣在於二者的編譯方法。Java 編譯器編譯 Lambda 表達式並將他們轉化爲類裏面的私有函數,它使用 Java 7 中新加的 invokedynamic 指令動態綁定該方法

接口與抽象類

雖然 Java 8 的接口的默認方法就像抽象類,能提供方法的實現,可是他們倆仍然是 不可相互代替的:

  • 接口能夠被類多實現(被其餘接口多繼承),抽象類只能被單繼承。
  • 接口中沒有 this 指針,沒有構造函數,不能擁有實例字段(實例變量)或實例方法,沒法保存 狀態(state),抽象方法中能夠。
  • 抽象類不能在 java 8 的 lambda 表達式中使用。
  • 從設計理念上,接口反映的是 「like-a」 關係,抽象類反映的是 「is-a」 關係。
相關文章
相關標籤/搜索