文章目錄
- 什麼是函數式程式設計
- Lambda表達式
- @FunctionalInterface函數式接口
- Lambda表達式的格式
- 方法引用
相信大家都使用過面向對象的程式設計語言,面向對象程式設計是對資料進
行抽象,而函數式程式設計是對行為進行抽象。函數式程式設計讓程式員能夠寫出更加容易閱讀的代碼。那什麼時候函數式程式設計呢?
函數式程式設計是一種程式設計的方法論,主要是将行為編寫成一個個的函數。
什麼是函數?
函數就是對輸入的值進行處理,傳回另外的值。
在Java 8 中引入的Labmda表達式是函數式程式設計的一種實作。
什麼是Lambda表達式呢?我們舉個例子
下面的代碼如果使用Java 7 的話應該這樣寫:
//sort using java 7
private void sortUsingJava7(List<String> names){
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
}
代碼裡面需要實作一個匿名類,看起來是不是很複雜? 下面我們用java 8 的lambda表達式将其改寫:
//sort using java 8
private void sortUsingJava8(List<String> names){
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
}
}
其中(s1, s2) -> s1.compareTo(s2) 是Comparator的compare方法的實作。
這裡我們使用了Lambda表達式替換了Comparator的匿名類。為什麼可以這樣做?什麼樣的匿名類才能被Lambda表達式替換呢? 這裡我們引入一個概念,叫做函數式接口。
Lambda表達式需要一個函數式接口作為其對應類型,而它的方法體就是函數接口的實作。每一個該接口類型的Lambda表達式都會被比對到該接口的抽象方法。
所謂函數是接口是指包括如下特征的接口:
- 接口有且僅有一個抽象方法
- 接口允許定義靜态方法
- 接口允許定義預設方法
- 接口允許java.lang.Object中的public方法
我們看一下上面的Comparator的實作:
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
default <U> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
return thenComparing(comparing(keyExtractor, keyComparator));
}
default <U extends Comparable<? super U>> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor)
{
return thenComparing(comparing(keyExtractor));
}
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
return thenComparing(comparingInt(keyExtractor));
}
default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
return thenComparing(comparingLong(keyExtractor));
}
default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
return thenComparing(comparingDouble(keyExtractor));
}
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(true, comparator);
}
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
...
}
public static <T, U> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
...
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
...
}
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
...
}
public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
...
}
public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
...
}
}
我們可以看到Comparator接口裡面有static方法,有default方法,有一個來自Object的boolean equals(Object obj);有一個需要實作的抽象方法int compare(T o1, T o2)
default方法是java 8添加的最新的關鍵詞,表示實作這個接口的類如果不自己實作這個方法,那麼就用接口自己的吧,其作用主要是向下相容。
JDK自帶了一些有用的函數式接口:
- java.lang.Runnable,
- java.awt.event.ActionListener,
- java.util.Comparator,
- java.util.concurrent.Callable
- java.util.function包下的接口,如Consumer、Predicate、Supplier等
一般來說Lambda的表達式是這樣的格式:
parameter -> expression body
檢視下面的代碼:
public static void main(String[] args) {
Runnable noArguments = () -> System.out.println("Hello World");
ActionListener oneArgument = event -> System.out.println("button clicked");
Runnable multiStatement = () -> {
System.out.print("Hello");
System.out.println(" World");
};
BinaryOperator<Long> add = (x, y) -> x + y;
BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
}
上面是我們經常在Lambda中使用的幾種格式。
-
不包含參數的格式
Runnable noArguments = () -> System.out.println(“Hello World”);
-
隻包含一個參數,并可以省略括号
ActionListener oneArgument = event -> System.out.println(“button clicked”);
-
Lambda的主體是一段代碼塊
Runnable multiStatement = () -> {
System.out.print(“Hello”);
System.out.println(" World");
};
-
多個參數
BinaryOperator add = (x, y) -> x + y;
-
顯式指定參數的類型
BinaryOperator addExplicit = (Long x, Long y) -> x + y;
所有Lambda 表達式中的參數類型都是由編譯器推斷得出的。如果編譯器無法推斷你的參數類型,則需要手動指定。
在第一個例子中我們講到了如下的Lambda表達式:
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
其中(s1, s2) -> s1.compareTo(s2) 表示的是兩個字元串的比較,調用了String類的compareTo方法。為了更簡潔的表示業務邏輯,可以是用方法引用來替換Lambda表達式。
Collections.sort(names, String::compareTo);
這樣比原來的代碼更短,而且更加簡潔。
有三種方法可以被引用:
- 靜态方法
- 執行個體方法
- 使用new的構造函數方法如:(TreeSet::new)
更多教程請參考 flydean的部落格