java8 – Functional Interfaces

什么是Functional interfaces

Functional interfaces 也被稱作Single Abstract Method interfaces (SAM Interfaces). 顧名思義孤钦,它們有且只有一個(gè)抽象方法. Java 8引入了一個(gè)注釋西轩,即@FunctionalInterface汹想,當(dāng)你使用@FunctionalInterface注釋的接口違反了Functional Interface的規(guī)定時(shí)绍弟,編譯器將會(huì)報(bào)錯(cuò)录粱。

在Java 8中摔桦,F(xiàn)unctional interfaces也可以使用lambda表達(dá)式靠益,方法引用和構(gòu)造函數(shù)引用來(lái)表示疮鲫。

一個(gè)典型的Functional Interface示例如下:

@FunctionalInterface
public interface MyFirstFunctionalInterface {
    public void firstWork();
}

我們?cè)囍偌尤胍粋€(gè)抽象方法:

@FunctionalInterface
public interface MyFirstFunctionalInterface
{
    public void firstWork();
    public void doSomeMoreWork();   //error
}

編譯器報(bào)錯(cuò):
D:\workspace\ideaws\test\java8\src\main\java\com\ancs\java8\MyFirstFunctionalInterface.java
Error:Error:line (3)java: 意外的 @FunctionalInterface 注釋
com.ancs.java8.MyFirstFunctionalInterface 不是函數(shù)接口
在 接口 com.ancs.java8.MyFirstFunctionalInterface 中找到多個(gè)非覆蓋抽象方法


Note: 即使省略@FunctionalInterface注釋,F(xiàn)unctional Interface也是有效的昏苏。 注釋的作用僅僅是告訴編譯器尊沸,檢查接口是否只有一個(gè)抽象方法。

此外贤惯,由于java8 引入了默認(rèn)方法洼专,默認(rèn)方法不屬于抽象方法,所以你可以根據(jù)需要在Functional Interface隨意增加默認(rèn)默認(rèn)方法孵构。如下所示:

@FunctionalInterface
public interface MyFirstFunctionalInterface
{
    public void firstWork();
 
    default void doSomeMoreWork1(){
    //Method body
    }
 
    default void doSomeMoreWork2(){
    //Method body
    }
}

另外需要注意的是屁商,如果接口聲明的抽象方法來(lái)自java.lang.Object,那么它也不會(huì)計(jì)入抽象方法的計(jì)數(shù)颈墅。因?yàn)槿魏谓涌诘膶?shí)現(xiàn)都默認(rèn)繼承自java.lang.Object蜡镶。例如,下面的Functional Interface也是有效的:

@FunctionalInterface
public interface MyFirstFunctionalInterface
{
    public void firstWork();
 
    @Override
    public String toString();                //Overridden from Object class
 
    @Override
    public boolean equals(Object obj);        //Overridden from Object class
}

java8 中常用函數(shù)式接口

Java API中已經(jīng)有了幾個(gè)函數(shù)式接口恤筛,比如Comparable官还、Runnable和 Callable。 而且再java.util.function包中引入了幾個(gè)新的函數(shù)式接口毒坛。

Note : (T,U) -> R的表達(dá)方式展示了應(yīng)當(dāng)如何思考 一個(gè)函數(shù)描述符望伦。表的左側(cè)代表了參數(shù)類型林说。這里它代表一個(gè)函數(shù),具有兩個(gè)參數(shù)屯伞,分別為泛型 T和U腿箩,返回類型為R。

Java API中提供的常用的函數(shù)式接口及其函數(shù)描述符列表如下:

函數(shù)式接口 函數(shù)描述符 原始類型特化
Predicate<T> T->boolean IntPredicate
LongPredicate
DoublePredicate
Consumer<T> T->void IntConsumer
LongConsumer
DoubleConsumer
Function<T,R> T->R IntFunction<R>
IntToDoubleFunction
IntToLongFunction
LongFunction<R>
LongToDoubleFunction
LongToIntFunction
DoubleFunction<R>
ToIntFunction<T>
ToDoubleFunction<T>
ToLongFunction<T>
Supplier<T> ()->T BooleanSupplier
IntSupplier
LongSupplier
DoubleSupplier
UnaryOperator<T> T->T IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
BinaryOperator<T> (T,T)->T IntBinaryOperator
LongBinaryOperator
DoubleBinaryOperator
BiPredicate<L,R> (L,R)->boolean
BiConsumer<T,U> (T,U)->void ObjIntConsumer<T>
ObjLongConsumer<T>
ObjDoubleConsumer<T>
BiFunction<T,U,R> (T,U)->R ToIntBiFunction<T,U>
ToLongBiFunction<T,U>
ToDoubleBiFunction<T,U>

我們接下來(lái)會(huì)介紹Predicate劣摇、Consumer和Function.

Predicate

在數(shù)學(xué)中, predicate 通常被理解為布爾值函數(shù)' 'P: X? {true, false}', 稱之為 predicate on X. 它可以被認(rèn)為是一個(gè)返回值為true或false的運(yùn)算符或函數(shù).
在Java 8中, Predicate 是一個(gè) functional interface 珠移,因此可以用作賦值給 lambda expression 或者方法引用. 那么你認(rèn)為我們會(huì)在日常編程當(dāng)中使用這些返回true/false得函數(shù)么?我可以肯定得告訴你饵撑,你可以使用predicates在你需要判斷任何組/集合中的對(duì)象是 true/false 的地方使用.
例如剑梳,您可以在這些實(shí)時(shí)用例中使用Predicate:

  1. 查找特定日期之后出生的所有孩子
  2. 查找特定日期訂購(gòu)的披薩訂單
  3. 超過(guò)特定年齡的員工等等
    所以Predicate似乎是一個(gè)很有意思的類,我們接下來(lái)深入了解一下滑潘。
    正如我們所說(shuō)的那樣, Predicate是一個(gè)functional interface. 這意味著我們可以在需要使用predicate的地方使用lambda 表達(dá)式. 例如 Stream 接口中的filter() 方法.
/**
 * Returns a stream consisting of the elements of this stream that match
 * the given predicate.
 *
 * <p>This is an <a href="package-summary.html#StreamOps">intermediate
 * operation</a>.
 *
 * @param predicate a non-interfering stateless predicate to apply to each element to determine if it
 * should be included in the new returned stream.
 * @return the new stream
 */
Stream<T> filter(Predicate<? super T> predicate);

我們可以把stream看作一個(gè)可以用來(lái)創(chuàng)建支持串行和并行聚合操作的元素序列的機(jī)制垢乙。就是說(shuō),我們可以隨時(shí)對(duì)流中的元素做一些操作.
所以我們可以使用streampredicate做如下操作:

  • 先對(duì)集合中的元素做過(guò)濾操作
  • 然后對(duì)過(guò)濾之后的元素做其他操作

在集合中使用Predicate

為了演示语卤,我們有一個(gè)Employee類追逮,如下所示:

package predicateExample;

public class Employee {

    public Employee(Integer id, Integer age, String gender, String fName, String lName){
        this.id = id;
        this.age = age;
        this.gender = gender;
        this.firstName = fName;
        this.lastName = lName;
    }

    private Integer id;
    private Integer age;
    private String gender;
    private String firstName;
    private String lastName;

    //Please generate Getter and Setters

    //To change body of generated methods, choose Tools | Templates.
    @Override
    public String toString() {
        return this.id.toString()+" - "+this.age.toString();
    }
}
1. 查找所有年齡超過(guò)21歲的男性員工
public static Predicate<Employee> isAdultMale()
{
    return p -> p.getAge() > 21 && p.getGender().equalsIgnoreCase("M");
}
2. 查找所有年齡超過(guò)18歲的女性員工
public static Predicate<Employee> isAdultFemale()
{
    return p -> p.getAge() > 18 && p.getGender().equalsIgnoreCase("F");
}
3. 查找超過(guò)給定年齡的員工
public static Predicate<Employee> isAgeMoreThan(Integer age)
{
    return p -> p.getAge() > age;
}

你可以根據(jù)需求創(chuàng)建更多的Predicate,EmployeePredicates.java包含了上面的三個(gè)方法粹舵,如下所示:

package predicateExample;
 
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
 
public class EmployeePredicates
{
    public static Predicate<Employee> isAdultMale() {
        return p -> p.getAge() > 21 && p.getGender().equalsIgnoreCase("M");
    }
     
    public static Predicate<Employee> isAdultFemale() {
        return p -> p.getAge() > 18 && p.getGender().equalsIgnoreCase("F");
    }
     
    public static Predicate<Employee> isAgeMoreThan(Integer age) {
        return p -> p.getAge() > age;
    }
     
    public static List<Employee> filterEmployees (List<Employee> employees,
                                                Predicate<Employee> predicate)
    {
        return employees.stream()
                    .filter( predicate )
                    .collect(Collectors.<Employee>toList());
    }
}  

我寫了一個(gè) filterEmployees()方法來(lái)說(shuō)明 predicate filter的使用方式. 它使得代碼看起來(lái)更簡(jiǎn)潔钮孵,重復(fù)性更低. 你可以可以創(chuàng)建多個(gè) predicate 鏈, 類似于 builder設(shè)計(jì)模式.
filterEmployees()方法中有兩個(gè)參數(shù),List<Employee>Predicate<Employee> ,返回滿足Predicate條件的一個(gè)新的Employee集合.
下面是測(cè)試類TestEmployeePredicates.java,如下所示:

package predicateExample;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static predicateExample.EmployeePredicates.*;
 
public class TestEmployeePredicates
{
    public static void main(String[] args)
    {
        Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
        Employee e2 = new Employee(2,13,"F","Martina","Hengis");
        Employee e3 = new Employee(3,43,"M","Ricky","Martin");
        Employee e4 = new Employee(4,26,"M","Jon","Lowman");
        Employee e5 = new Employee(5,19,"F","Cristine","Maria");
        Employee e6 = new Employee(6,15,"M","David","Feezor");
        Employee e7 = new Employee(7,68,"F","Melissa","Roy");
        Employee e8 = new Employee(8,79,"M","Alex","Gussin");
        Employee e9 = new Employee(9,15,"F","Neetu","Singh");
        Employee e10 = new Employee(10,45,"M","Naveen","Jain");
         
        List<Employee> employees = new ArrayList<Employee>();
        employees.addAll(Arrays.asList(new Employee[]{e1,e2,e3,e4,e5,e6,e7,e8,e9,e10}));
                
        System.out.println( filterEmployees(employees, isAdultMale()) );
         
        System.out.println( filterEmployees(employees, isAdultFemale()) );
         
        System.out.println( filterEmployees(employees, isAgeMoreThan(35)) );
         
        //Employees other than above collection of "isAgeMoreThan(35)"
        //can be get using negate()
        System.out.println(filterEmployees(employees, isAgeMoreThan(35).negate()));
    }
}
 
Output:
 
[1 - 23, 3 - 43, 4 - 26, 8 - 79, 10 - 45]
[5 - 19, 7 - 68]
[3 - 43, 7 - 68, 8 - 79, 10 - 45]
[1 - 23, 2 - 13, 4 - 26, 5 - 19, 6 - 15, 9 - 15]

Predicates是java 8中一個(gè)非常好的新工具類眼滤,每當(dāng)有使用場(chǎng)景時(shí)巴席,我都會(huì)用到它。

Consumer

java.util.function.Consumer<T>定義了一個(gè)名叫accept的抽象方法诅需,它接受泛型T的對(duì)象參數(shù)漾唉,沒(méi)有返回值(void)。你如果需要訪問(wèn)類型T的對(duì)象堰塌,并對(duì)其執(zhí)行某些操作赵刑,就可以使用 這個(gè)接口。比如场刑,你可以用它來(lái)創(chuàng)建一個(gè)forEach方法般此,接受一個(gè)Integers的列表,并對(duì)其中 每個(gè)元素執(zhí)行操作牵现。你可以使用這個(gè)forEach方法铐懊,并配合Lambda來(lái)打印列表中的所有元素。示例代碼TestConsumer 如下所示:

package consumerExample;

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

public class TestConsumer {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Larry", "Steve", "James");

        //use lambda expression
        System.out.println("Consumer test with lambda expression");
        Consumer<String> printConsumer = s -> System.out.println(s);
        names.forEach(printConsumer);

        // use method reference
        System.out.println("Consumer test with method reference");
        names.forEach(System.out::println);
    }
}

輸出如下:

Consumer test with lambda expression
Larry
Steve
James
Consumer test with method reference
Larry
Steve
James

ConsumerPredicate更容易理解瞎疼,它也是java 8 中新增的工具類科乎。

Function

java.util.function.Function<T, R>接口定義了一個(gè)叫作apply的方法,它接受一個(gè) 泛型T的對(duì)象丑慎,并返回一個(gè)泛型R的對(duì)象。

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

如果你需要定義一個(gè)Lambda,將輸入對(duì)象的信息映射 到輸出竿裂,就可以使用這個(gè)接口玉吁。在下面的代碼中,我們向你展示如何利用它來(lái)創(chuàng)建一個(gè)map方法腻异,以將一個(gè)String列表映射到包含每個(gè)String長(zhǎng)度的Integer列表进副。 TestFunction如下所示:

package functionExample;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

public class TestFunction {
    public static void main(String[] args) {
        List<Integer> result = map(Arrays.asList("lambdas","in","action"),
                (String s) -> s.length()
        );
        System.out.println(result);
    }
    public static <T, R> List<R> map(List<T> list, Function<T, R> f) {
        List<R> result = new ArrayList<>();
        for(T s: list){
            result.add(f.apply(s));
        }
        return result;
    }
}

輸出如下:

[7, 2, 6]

由于本章節(jié)主要介紹Function Interfaces,所以例如Function的其他三個(gè)default 方法:addThen(),compose(),identity()就不在此詳細(xì)論述了悔常。大家可以參考TestFunctionAndThen,TestFunctionComposeTestFunctionIdentity.

原始類型特化

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ī)制來(lái)幫助程序員執(zhí)行這一任務(wù):裝 箱和拆箱操作是自動(dòng)完成的。比如都弹,這就是為什么下面的代碼是有效的(一個(gè)int被裝箱成為 Integer):

List<Integer> list = new ArrayList<>(); 
for (int i = 300; i < 400; i++)
{    
    list.add(i);
} 

但這在性能方面是要付出代價(jià)的娇豫。裝箱后的值本質(zhì)上就是把原始類型包裹起來(lái),并保存在堆 里畅厢。因此冯痢,裝箱后的值需要更多的內(nèi)存,并需要額外的內(nèi)存搜索來(lái)獲取被包裹的原始值框杜。
Java 8為我們前面所說(shuō)的函數(shù)式接口帶來(lái)了一個(gè)專門的版本浦楣,以便在輸入和輸出都是原始類 型時(shí)避免自動(dòng)裝箱的操作。比如咪辱,在下面的代碼中振劳,使用IntPredicate就避免了對(duì)值1000進(jìn)行 裝箱操作,但要是用Predicate<Integer>就會(huì)把參數(shù)1000裝箱到一個(gè)Integer對(duì)象中:

// true(無(wú)裝箱)
IntPredicate evenNumbers = (int i) -> i % 2 == 0; 
evenNumbers.test(1000); 
//false(裝箱)
Predicate<Integer> oddNumbers = (Integer i) -> i % 2 == 1; 
oddNumbers.test(1000); 

一般來(lái)說(shuō)油狂,針對(duì)專門的輸入?yún)?shù)類型的函數(shù)式接口的名稱都要加上對(duì)應(yīng)的原始類型前历恐,比 如DoublePredicate寸癌、IntConsumer、LongBinaryOperator弱贼、IntFunction等蒸苇。Function 接口還有針對(duì)輸出參數(shù)類型的變種:ToIntFunction<T>、IntToDoubleFunction等吮旅。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末溪烤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子庇勃,更是在濱河造成了極大的恐慌檬嘀,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件责嚷,死亡現(xiàn)場(chǎng)離奇詭異鸳兽,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)再层,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門贸铜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人聂受,你說(shuō)我怎么就攤上這事蒿秦。” “怎么了蛋济?”我有些...
    開(kāi)封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵棍鳖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我碗旅,道長(zhǎng)渡处,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任祟辟,我火速辦了婚禮医瘫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘旧困。我一直安慰自己醇份,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布吼具。 她就那樣靜靜地躺著僚纷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拗盒。 梳的紋絲不亂的頭發(fā)上怖竭,一...
    開(kāi)封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音陡蝇,去河邊找鬼痊臭。 笑死哮肚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的广匙。 我是一名探鬼主播绽左,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼艇潭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起戏蔑,我...
    開(kāi)封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蹋凝,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后总棵,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鳍寂,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年情龄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了迄汛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骤视,死狀恐怖鞍爱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情专酗,我是刑警寧澤睹逃,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站祷肯,受9級(jí)特大地震影響沉填,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜佑笋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一翼闹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蒋纬,春花似錦猎荠、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至琼掠,卻和暖如春拒垃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瓷蛙。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工悼瓮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留戈毒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓横堡,卻偏偏與公主長(zhǎng)得像埋市,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子命贴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容