Java 8 - 03 Lambda 函数式接口Predicate Consumer Function Supplier
文章目錄
- Pre
- Predicate 斷言型函數(shù)式接口
- Consumer 消費(fèi)型函數(shù)式接口
- Function 功能型函數(shù)式接口
- Supplier 供給型函數(shù)式接口
- 小結(jié)
- 函數(shù)式接口如何處理異常信息
Pre
Java 8 - 02 Lambda Expression中我們討論了函數(shù)式接口, 函數(shù)式接口定義且只定義了一個(gè)抽象方法。因?yàn)槌橄蠓椒ǖ暮灻梢悦枋鯨ambda表達(dá)式的簽名。函數(shù)式接口的抽象方法的簽名稱為函數(shù)描述符。
所以為了應(yīng)用不同的Lambda表達(dá)式,我們需要一套能夠描述常見函數(shù)描述符的函數(shù)式接口Java API中已經(jīng)有了幾個(gè)函數(shù)式接口,比如 Comparable 、 Runnable 和Callable 。
Java 8 在 java.util.function 包中引入了幾個(gè)新的函數(shù)式接口,比比較常用的Predicate 、 Consumer 和 Function 等 。
Predicate 斷言型函數(shù)式接口
package java.util.function;import java.util.Objects;/*** Represents a predicate (boolean-valued function) of one argument.** <p>This is a <a href="package-summary.html">functional interface</a>* whose functional method is {@link #test(Object)}.** @param <T> the type of the input to the predicate** @since 1.8*/ @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);} }標(biāo)注了@FunctionalInterface , 抽象方法只能包含一個(gè) , default 方法 和 static的方法除外 , 比如 Predicate ,只有test是抽象方法,其他的幾個(gè)都是default級(jí)別和static修飾的方法,所以Predicate也是一個(gè)函數(shù)式接口 。
java.util.function.Predicate<T> 接口定義了一個(gè)名叫 test 的抽象方法,它接受泛型T 對(duì)象,并返回一個(gè) boolean 。
如果需要表示一個(gè)涉及類型 T 的布爾表達(dá)式時(shí),就可以使用這個(gè)接口
舉個(gè)例子
import com.artisan.domain.Enginner;import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Predicate;/*** @author 小工匠* @version v1.0* @create 2020-05-16 9:13* @motto show me the code ,change the word* @blog https://artisan.blog.csdn.net/* @description**/public class PredicateDemo {/*** 過濾符合規(guī)則的泛型類* @param list* @param predicate* @param <T>* @return*/private static <T> List<T> filter(List<T> list , Predicate<T> predicate){List<T> targetList = new ArrayList<>();for (T t :list){if (predicate.test(t)){targetList.add(t);}}return targetList;}public static void main(String[] args) {List<Enginner> enginnerList = Arrays.asList(new Enginner("Java", 18), new Enginner("GO", 20));List<Enginner> goEngineerList = filter(enginnerList, enginner -> enginner.getJob().equals("GO"));System.out.println(goEngineerList);} }我們通過 filter(enginnerList, (enginner) -> enginner.getJob().equals("GO")); 第二個(gè)參數(shù) (enginner) -> enginner.getJob().equals("GO") 方法簽名返回的是Boolean,所以可以使用Predicate這個(gè)JDK8中提供的接口 。
當(dāng)然了你也可以直接寫一個(gè)類似Predicate的函數(shù)式接口來供自己調(diào)用 ,如下
@FunctionalInterface public interface Filter<T> {boolean filter(T t);}那么就變成了如下的樣子
private static <T> List<T> filterCustom(List<T> list , Filter<T> filter){List<T> targetList = new ArrayList<>();for (T t :list){if (filter.filter(t)){targetList.add(t);}}return targetList;}main方法中調(diào)用如下
List<Enginner> javaEngineerList= filterCustom(enginnerList, enginner -> enginner.getJob().equals("Java"));System.out.println(javaEngineerList);Consumer 消費(fèi)型函數(shù)式接口
package java.util.function;import java.util.Objects;/*** Represents an operation that accepts a single input argument and returns no* result. Unlike most other functional interfaces, {@code Consumer} is expected* to operate via side-effects.** @since 1.8*/ @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); };} }標(biāo)注了@FunctionalInterface , 抽象方法只能包含一個(gè) , default 方法 和 static的方法除外 , 比如 Consumer,只有accept是抽象方法Consumer是default級(jí)的方法,所以Consumer也是一個(gè)函數(shù)式接口 。
java.util.function.Consumer<T> 定義了一個(gè)名叫 accept 的抽象方法,它接受泛型 T的對(duì)象,沒有返回( void )。
如果需要訪問類型 T 的對(duì)象,并對(duì)其執(zhí)行某些操作,就可以使用這個(gè)接口
舉個(gè)例子
比如,你可以用它來創(chuàng)建一個(gè) forEach 方法,接受一個(gè) Integers 的列表,并對(duì)其中每個(gè)元素執(zhí)行操作。 假設(shè)我們要使用這個(gè) forEach 方法,并配合Lambda來打印列表中的所有元素。
import java.util.Arrays; import java.util.List; import java.util.function.Consumer;/*** @author 小工匠* @version v1.0* @create 2020-05-16 20:20* @motto show me the code ,change the word* @blog https://artisan.blog.csdn.net/* @description**/public class ComusmerDemo {public static <T> void doForEach(List<T> tList, Consumer<T> consumer){for (T t: tList ) {consumer.accept(t);}}public static void main(String[] args) {// 第二個(gè)參數(shù) Lambda是 Consumer 中accept 方法的實(shí)現(xiàn)doForEach(Arrays.asList(1,2,3,5,7),(Integer i) -> System.out.println(i));} }Function 功能型函數(shù)式接口
package java.util.function;import java.util.Objects;/*** Represents a function that accepts one argument and produces a result.** @param <T> the type of the input to the function* @param <R> the type of the result of the function** @since 1.8*/ @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;} }java.util.function.Function<T, R> 接口定義了一個(gè)叫作 apply 的方法,它接受一個(gè)泛型 T 的對(duì)象,并返回一個(gè)泛型 R 的對(duì)象。
如果我們需要定義一個(gè)Lambda,將輸入對(duì)象的信息映射到輸出,就可以使用這個(gè)接口 ,舉個(gè)例子提取工程師的職位或把字符串映射為它的長(zhǎng)度等等
來個(gè)小demo : 利用Function 來創(chuàng)建一個(gè) map 方法,以將一個(gè) String 列表映射到包含每個(gè)
String 長(zhǎng)度的 Integer 列表
Supplier 供給型函數(shù)式接口
package java.util.function;/*** Represents a supplier of results.** <p>There is no requirement that a new or distinct result be returned each* time the supplier is invoked.* * @param <T> the type of results supplied by this supplier** @since 1.8*/ @FunctionalInterface public interface Supplier<T> {/*** Gets a result.** @return a result*/T get(); }無(wú)參數(shù),返回一個(gè)結(jié)果。
import com.artisan.domain.Enginner;import java.util.function.Supplier;/*** @author 小工匠* @version v1.0* @create 2020-05-16 21:08* @motto show me the code ,change the word* @blog https://artisan.blog.csdn.net/* @description**/public class SupplierDemo {public static <T> T doGet(Supplier<T> supplier) {return supplier.get();}public static void main(String[] args) {Enginner enginneer = new Enginner("JAVA", 18);String s = doGet(() -> enginneer.getJob());System.out.println(s);} }小結(jié)
我們介紹了4個(gè)泛型函數(shù)式接口: Predicate<T> 、 Consumer<T> 、Function<T,R> 、Supplier<T>
還有些函數(shù)式接口專為某些類型而設(shè)計(jì)。
回顧一下:Java類型要么是引用類型(比如 Byte 、 Integer 、 Object 、 List ),要么是原始類型(比如 int 、 double 、 byte 、 char ).
但是泛型(比如 Consumer<T> 中的 T )只能綁定到引用類型。這是由泛型內(nèi)部的實(shí)現(xiàn)方式造成的。因此,在Java里有一個(gè)將原始類型轉(zhuǎn)換為對(duì)應(yīng)的引用類型的機(jī)制。這個(gè)機(jī)制叫作裝箱(boxing)。相反的操作,也就是將引用類型轉(zhuǎn)換為對(duì)應(yīng)的原始類型,叫作拆箱(unboxing).
Java還有一個(gè)自動(dòng)裝箱機(jī)制來幫助程序員執(zhí)行這一任務(wù):裝箱和拆箱操作是自動(dòng)完成的。
比如,這就是為什么下面的代碼是有效的(一個(gè) int 被裝箱成為Integer )
List<Integer> list = new ArrayList<>(); for (int i = 0; i < 100; i++){list.add(i); }但這在性能方面是要付出代價(jià)的。裝箱后的值本質(zhì)上就是把原始類型包裹起來,并保存在堆里。因此,裝箱后的值需要更多的內(nèi)存,并需要額外的內(nèi)存搜索來獲取被包裹的原始值。
Java 8為我們前面所說的函數(shù)式接口帶來了一個(gè)專門的版本,以便在輸入和輸出都是原始類型時(shí)避免自動(dòng)裝箱的操作。
比如,在下面的代碼中,使用 IntPredicate 就避免了對(duì)值 1000 進(jìn)行裝箱操作,但要是用 Predicate<Integer> 就會(huì)把參數(shù) 1000 裝箱到一個(gè) Integer 對(duì)象中:
public class IntPredicateDemo {public static void main(String[] args) {// 無(wú)裝箱IntPredicate intPredicate = (int i) -> i % 2 == 0;intPredicate.test(1000);// 裝箱Predicate<Integer> predicate = (Integer i) -> i % 2 == 0;predicate.test(1000);} }一般來說,針對(duì)專門的輸入?yún)?shù)類型的函數(shù)式接口的名稱都要加上對(duì)應(yīng)的原始類型前綴,比如 DoublePredicate 、 IntConsumer 、 LongBinaryOperator 、 IntFunction 等。
Function接口還有針對(duì)輸出參數(shù)類型的變種: ToIntFunction<T> 、 IntToDoubleFunction 等
來個(gè)小測(cè)驗(yàn)
請(qǐng)構(gòu)造一個(gè)可以利用這些函數(shù)式接口的有效Lambda 表達(dá)式: (1) T->R (2) (int, int)->int (3) T->void (4) ()->T (5) (T, U)->R (1) Function<T,R> 不錯(cuò)。它一般用于將類型 T 的對(duì)象轉(zhuǎn)換為類型 R 的對(duì)象(比如 Function<Apple, Integer> 用來提取蘋果的重量)。 (2) IntBinaryOperator 具有唯一一個(gè)抽象方法,叫作 applyAsInt ,它代表的函數(shù)描述 符是 (int, int) -> int 。 (3) Consumer<T> 具有唯一一個(gè)抽象方法叫作 accept ,代表的函數(shù)描述符是 T -> void 。 (4) Supplier<T> 具有唯一一個(gè)抽象方法叫作 get ,代表的函數(shù)描述符是 ()-> T 。或者, Callable<T> 具有唯一一個(gè)抽象方法叫作 call ,代表的函數(shù)描述符是 () -> T 。 (5) BiFunction<T, U, R> 具有唯一一個(gè)抽象方法叫作 apply ,代表的函數(shù)描述符是 (T, U) -> R 。最后 總結(jié)關(guān)于函數(shù)式接口和Lambda
函數(shù)式接口如何處理異常信息
Note : ,任何函數(shù)式接口都不允許拋出受檢異常(checked exception)。如果你需要Lambda表達(dá)式來拋出異常,有兩種辦法:定義一個(gè)自己的函數(shù)式接口,并聲明受檢異常,或者把Lambda包在一個(gè) try/catch 塊中。
自己的函數(shù)式接口如下:
@FunctionalInterface public interface BufferedReaderProcessor {String process(BufferedReader b) throws IOException; }調(diào)用
BufferedReaderProcessor p = (BufferedReader br) -> br.readLine();但是你可能是在使用一個(gè)接受函數(shù)式接口的API,比如 Function<T, R> ,沒有辦法自己創(chuàng)建一個(gè)。這種情況下, 可以顯式捕捉受檢異常:
Function<BufferedReader, String> f = (BufferedReader b) -> { try {return b.readLine(); } catch(IOException e) {throw new RuntimeException(e);} }; 《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的Java 8 - 03 Lambda 函数式接口Predicate Consumer Function Supplier的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 8 - 02 Lambda E
- 下一篇: Java 8 - 04 类型检查、类型推