您好,登錄后才能下訂單哦!
今天小編給大家分享一下Java函數式編程怎么應用的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
Java 根據常用需求場景的用例,抽象出了幾個內置的函數式接口給開發者使用,比如Function
、 Supplier
等等,Stream 中各種操作方法的參數或者是返回值類型往往就是這些內置的函數式接口。
比如 Stream 中 map 操作方法的參數類型就是 Function
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
那為什么我們在平時使用 Stream 操作的 map 方法時,從來沒有見過聲明這個類型的參數呢?比如下面這個通過 map 方法把流中的每個元素轉換成大寫的例子。
List<String> list = new ArrayList<String>();
Stream<String> stream = list.stream();
Stream<String> streamMapped = stream.map((value) -> value.toUpperCase());
map 方法的參數直接是一個 Lambada 表達式:
(value) -> value.toUpperCase()
這個Lambda 表達式就是Function
接口的實現。
函數式接口的載體通常是 Lambda 表達式,通過 Lambda 表達式,編譯器會根據 Lambda 表達式的參數和返回值推斷出其實現的是哪個函數式接口。使用 Lambda 表達式實現接口,我們不必像匿名內部類那樣--指明類要實現的接口,所以像 Stream 操作中雖然參數或者返回值類型很多都是 Java 的內置函數式接口,但是我們并沒有顯示的使用匿名類實現它們。
雖然Lambda 表達式使用起來很方便,不過這也從側面造成了咋一看到那些 Java 內置的函數式接口類型時,我們會有點迷惑“這貨是啥?這貨又是啥?”的感覺。
下面我們先說一下函數式編程、Java 的函數式接口、Lambda 為什么只能實現函數式接口這幾個問題,把這些東西搞清楚了再梳理 Java 內置提供了哪些函數式接口。
函數式編程中包含以下兩個關鍵的概念:
函數是第一等公民
函數要滿足一下約束
函數的返回值僅取決于傳遞給函數的輸入參數。
函數的執行沒有副作用。
即使我們在寫程序的時候沒有一直遵循所有這些規則,但仍然可以從使用函數式編程思想編寫程序中獲益良多。
接下來,我們來看一下這兩個關鍵概念再 Java 函數編程中的落地。
在函數式編程范式中,函數是語言中的第一等公民。這意味著可以創建函數的“實例”,對函數實例的變量引用,就像對字符串、Map 或任何其他對象的引用一樣。函數也可以作為參數傳遞給其他函數。
在 Java 中,函數顯然不是第一等公民,類才是。所以 Java 才引入 Lambda 表達式,這個語法糖從表現層上讓 Java 擁有了函數,讓函數可以作為變量的引用、方法的參數等等。為啥說是從表現層呢?因為實際上在編譯的時候 Java 編譯器還是會把 Lambda 表達式編譯成類。
函數編程中,有個純函數(Pure Function)的概念,如果一個函數滿足以下條件,才是純函數:
該函數的執行沒有副作用。
函數的返回值僅取決于傳遞給函數的輸入參數。
下面是一個 Java 中的純函數(方法)示例
public class ObjectWithPureFunction{ public int sum(int a, int b) { return a + b;
}
}
上面這個sum()
方法的返回值僅取決于其輸入參數,而且sum()
是沒有副作用的,它不會在任何地方修改函數之外的任何狀態(變量)。
相反,這里是一個非純函數的例子:
public class ObjectWithNonPureFunction{ private int value = 0; public int add(int nextValue) { this.value += nextValue; return this.value;
}
}
add()
方法使用成員變量value
來計算其返回值,并且它還修改了value
成員變量的狀態,這代表它有副作用,這兩個條件都導致add
方法不是一個純函數
正如我們看到的,函數式編程并不是解決所有問題的銀彈。尤其是“函數是沒有副作用的”這個原則就使得在一些場景下很難使用函數式編程,比如要寫入數據庫的場景,寫入數據庫就算是一個副作用。所以,我們需要做的是了解函數式編程擅長解決哪些問題,把它用在正確的地方。
Java中的函數式接口在 Lambda 表達式那篇文章里提到過,這里再詳細說說。函數式接口是只有一個抽象方法的接口(抽象方法即未實現方法體的方法)。一個 Interface 接口中可以有多個方法,其中默認方法和靜態方法都自帶實現,但是只要接口中有且僅有一個方法沒有被實現,那么這個接口就可以被看做是一個函數式接口。
下面這個接口只定義了一個抽象方法,顯然它是一個函數式接口:
public interface MyInterface { public void run();
}
下面這個接口中,定義了多個方法,不過它也是一個函數式接口:
public interface MyInterface2 {
public void run();
public default void doIt() {
System.out.println("doing it");
}
public static void doItStatically() {
System.out.println("doing it statically");
}
}
因為doIt
方法在接口中定義了默認實現,靜態方法也有實現,接口中只有一個抽象方法run
沒有提供實現,所以它滿足函數式接口的要求。
這里要注意,如果接口中有多個方法沒有被實現,那么接口將不再是函數式接口,因此也就沒辦法用 Java 的 Lambda 表達式實現接口了。
編譯器會根據 Lambda 表達式的參數和返回值類型推斷出其實現的抽象方法,進而推斷出其實現的接口,如果一個接口有多個抽象方法,顯然是沒辦法用 Lambda 表達式實現該接口的。
這里擴充一個標注接口是函數式接口的注解@FunctionalInterface
@FunctionalInterface
// 標明接口為函數式接口
public interface MyInterface { public void run();
//抽象方法}
一旦使用了該注解標注接口,Java 的編譯器將會強制檢查該接口是否滿足函數式接口的要求:“確實有且僅有一個抽象方法”,否則將會報錯。
需要注意的是,即使不使用該注解,只要一個接口滿足函數式接口的要求,那它仍然是一個函數式接口,使用起來都一樣。該注解只起到--標記接口指示編譯器對其進行檢查的作用。
Java 語言內置了一組為常見場景的用例設計的函數式接口,這樣我們就不必每次用到Lambda 表達式、Stream 操作時先創建函數式接口了,Java 的接口本身也支持泛型類型,所以基本上 Java 內置的函數式接口就能滿足我們平時編程的需求,我自己在開發項目時,印象里很少見過有人自定義函數式接口。
Function
接口(全限定名:java.util.function.Function)是Java中最核心的函數式接口。 Function
接口表示一個接受單個參數并返回單個值的函數(方法)。以下是 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;
}
Function
接口本身只包含一個需要實現的抽象方法apply
,其他幾個方法都已在接口中提供了實現,這正好符合上面我們講的函數式接口的定義:“有且僅有一個抽象方法的接口”。
Function 接口中的其他三個方法中compse
、andThen
這兩個方法用于函數式編程的組合調用,identity
用于返回調用實體對象本身。
Function
接口用Java 的類這么實現
public class AddThree implements Function<Long, Long> {
@Override
public Long apply(Long aLong) {
return aLong + 3;
}
public static void main(String[] args) {
Function<Long, Long> adder = new AddThree();
Long result = adder.apply(4L);
System.out.println("result = " + result);
}
}
不過現實中沒有這么用的,前面說過 Lambda 表達式是搭配函數式接口使用的,用Lambda表達式實現上Function 接口只需要一行,上面那個例子用 Lambda 實現的形式是:
Function<Long, Long> adder = (value) -> value + 3;Long resultLambda = adder.apply(8L);
System.out.println("resultLambda = " + resultLambda);
是不是簡潔了很多。后面的接口示例統一用 Lambda 表達式舉例,不再用類實現占用太多篇幅。
Function
接口的常見應用是 Stream API 中的 map 操作方法,該方法的參數類型是Function
接口,表示參數是一個“接收一個參數,并返回一個值的函數”。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
所以我們在代碼里常會見到這樣使用 map 操作:
stream.map((value) -> value.toUpperCase())
Predicate 接口 (全限定名:java.util.function.Predicate)表示一個接收單個參數,并返回布爾值 true 或 false 的函數。以下是 Predicate 功能接口定義:
public interface Predicate<T> { boolean test(T t);
}
Predicate 接口里還有幾個提供了默認實現的方法,用于支持函數組合等功能,這里不再贅述。 用 Lambda 表達式實現 Predicate 接口的形式如下:
Stream API 中的 filter 過濾操作,接收的就是一個實現了 Predicate 接口的參數。Predicate predicate = (value) -> value != null;
Stream<T> filter(Predicate<? super T> predicate);
寫代碼時,會經常見到這樣編寫的 filter 操作:
Stream<String> longStringsStream = stream.filter((value) -> {
// 元素長度大于等于3,返回true,會被保留在 filter 產生的新流中。
return value.length() >= 3;
});
Supplier 接口(java.util.function.Supplier),表示提供某種值的函數。其定義如下:
@FunctionalInterfacepublic interface Supplier<T> {
T get();
}
Supplier接口也可以被認為是工廠接口,它產生一個泛型結果。與 Function 不同的是,Supplier 不接受參數。
Supplier<Integer> supplier = () -> new Integer((int) (Math.random() * 1000D));
上面這個 Lambda 表達式的 Supplier 實現,用于返回一個新的 Integer 實例,其隨機值介于 0 到 1000 之間。
Consumer 接口(java.util.function.Consume)表示一個函數,該函數接收一個參數,但是不返回任何值。
Consumer 接口常用于表示:要在一個輸入參數上執行的操作,比如下面這個用Lambda 表達式實現的 Consumer,它將作為參數傳遞給它的@FunctionalInterfacepublic interface Consumer<T> { void accept(T t);
}
value
變量的值打印到System.out
標準輸出中。Consumer<Integer> consumer = (value) -> System.out.println(value);
Stream API 中的 forEach、peek 操作方法的參數就是 Consumer 接口類型的。
Stream<T> peek(Consumer<? super T> action);
void forEach(Consumer<? super T> action);
比如,Stream API 中的 forEach 操作,會像下面這樣使用 Consume 接口的實現
Stream<String> stream = stringList.stream();
// 下面是Lambda 的簡寫形式
// 完整形式為:value -> System.out.println(value);
stream.forEach(System.out::println);
Optional 接口并不是一個函數式接口,這里介紹它主要是因為它經常在一些 Stream 操作中出現,作為操作的返回值類型,所以趁著學習函數式編程的契機也學習一下它。
Optional 接口是預防NullPointerException
的好工具,它是一個簡單的容器,其值可以是 null 或非 null。比如一個可能返回一個非空結果的方法,方法在有些情況下返回值,有些情況不滿足返回條件返回空值,這種情況下使用 Optional 接口作為返回類型,比直接無值時返回 Null 要更安全。
接下來我們看看 Optional 怎么使用:
// of 方法用于構建一個 Optional 容器
Optional<String> optional = Optional.of("bam");
// 判斷值是否為空
optional.isPresent(); // true
// 取出值,如果不存在直接取會拋出異常
optional.get(); // "bam"
// 取值,值為空時返回 orElse 提供的默認值
optional.orElse("fallback"); // "bam"
// 如果只存在,執行ifPresent參數中指定的方法
optional.ifPresent((s) -> System.out.println(s.charAt(0)));// "b"
Stream 操作中像 findAny、 findFirst這樣的操作方法都會返回一個 Optional 容器,意味著結果 Stream 可能為空,因此沒有返回任何元素。我們可以通過 Optional 的 isPresent() 方法檢查是否找到了元素。
以上就是“Java函數式編程怎么應用”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。