Lambda的基礎(chǔ)使用

Lambda是java8出的新特性,之前很少用Lambda表達(dá)式寫代碼,現(xiàn)在慢慢習(xí)慣用Lambda表達(dá)式,并且記得Lambda表達(dá)式的知識,方便日后查閱和溫習(xí)。

Lambda表達(dá)式

Lambda表達(dá)式是在java8版本中引入的一種新的語法元素和操作符,這個操作符為“->”。

為了更加掌握Lambda表達(dá)式的使用,從那下面幾個方面去學(xué)習(xí)lambda表達(dá)式:

  • 什么是Lambda
  • Lambda的用途意義
  • Lambda的語法
  • Lambda表達(dá)式的語法格式
  • Lambda的函數(shù)式接口@FunctionalInterface的使用說明
  • 了解Java內(nèi)置函數(shù)式接口
  • 方法引用
  • 構(gòu)造器引用
  • 數(shù)組引用
  • 集合排序
  • list遍歷
  • 返回集合中符合條件的元素
  • 對象間的元素比較

什么是Lambda

Lambda就是一個匿名函數(shù),其實(shí)可理解為沒有函數(shù)名的函數(shù)。其實(shí)就是函數(shù)式接口的一種表現(xiàn)形式。

Lambda的用途意義

就是讓你的代碼更加簡潔,使用起來更加方便。同時也是簡化了匿名委托的使用,這里出現(xiàn)了一個專有名詞匿名委托,那么什么是匿名委托呢?下面通過代碼了解匿名委托。

public class LambdaTest{
    public static void main(Strign[] args){
       //一個匿名內(nèi)部類 java7之前
       Runnable runnable = new Runnable(){
            @Override
            public void run(){
                System.out.println("普通寫法");
            }
        } ;
        //java8 用lambda表達(dá)式
        Runnable rb = ()->{
            System.out.println("lambda表達(dá)式寫法");
        };
    }
} 

上面代碼看出,new Runnable(){}就是一個匿名委托。如果都將一塊代碼塊賦值給變量,那么java7之前寫法與java8的lambda表達(dá)式的寫法,很顯然lambda表達(dá)式寫法更加簡潔優(yōu)雅,代碼量也少,也達(dá)到了簡化匿名委托的效果。

Lambda的語法

Lambda表達(dá)式形式:(參數(shù))->{}

  • 左側(cè):指定了Lambda表達(dá)式所需要的所有參數(shù)。
  • 右側(cè):指定了Lambda體,Lambda體中是Lambda表達(dá)式執(zhí)行的功能代碼塊。

下面通過一個無參與帶參的Lambda表達(dá)式作例子說明,并與普通寫法作對比,這樣更加容易理解和掌握lambda表達(dá)式。

//無參寫法
Runnable runnable = new Runnable(){
    @Override
    public void run(){
        System.out.println("普通寫法");
    }
} ;
//java8 用lambda表達(dá)式
Runnable rb = ()->System.out.println("lambda表達(dá)式寫法");

//帶參寫法
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        //執(zhí)行的功能代碼塊
    }
});
mListView.setOnItemClickListener((parent, view, position, id) -> {
    //執(zhí)行的功能代碼塊
});

上述代碼簡單易懂,那么我們進(jìn)一步去學(xué)習(xí)Lambda表達(dá)式的語法和用法。

Lambda表達(dá)式的語法格式

下面我們就了解一下Lambda表達(dá)式的語法格式有那些:

  • 語法格式一:無參數(shù),無返回值。

    ()->System.out.println("lambda表達(dá)式寫法");
    
  • 語法格式二:有一個參數(shù),并且無返回值。

    (a)->System.out.println(a);
    
  • 語法格式三:只有一個參數(shù),小括號可以省略不寫。

    a->System.out.println(a);
    
  • 語法格式四:如果Lambda體中只有一條代碼語句,return和大括號都可以省略不寫。

    (int a,int b)->a+b;
    
  • 語法格式五:有兩個或兩個以上的參數(shù),有返回值,并且Lambda體中有多條代碼語句。

    (int a,int b)->{
        System.out.println("函數(shù)式接口");
        return a+b;
    };
    
  • 語法格式六:Lambda表達(dá)式的參數(shù)列表的數(shù)據(jù)類型可以省略不寫,因?yàn)镴VM編譯器通過上下文推斷出數(shù)據(jù)類型,即“類型推斷”。

    (Integer a,Integer b)->Integer.compare(a,b);
    

下面通過代碼實(shí)現(xiàn)上面的六種語法格式去學(xué)習(xí),當(dāng)然還有一些高級用法(如Stream中使用Lambda表達(dá)式,Stream放到一下次去學(xué)習(xí)),我們先從簡單的基礎(chǔ)的開始學(xué)習(xí):

public class TestLambda {
    public static void main(String []args){
        //語法格式一
        LambdaCallback1 callback1 = ()->System.out.println("無參數(shù),無返回值");
        callback1.test();
        //語法格式二
        LambdaCallback2 callback21 = (s)->System.out.println(s);
        callback21.test("語法格式二");
        //語法格式三
        LambdaCallback2 callback22 = s->System.out.println(s);
        callback22.test("語法格式三");
        //語法格式四
        LambdaCallback4 callback31 = (a,b)->a+b;
        System.out.println("語法格式四 "+callback31.test(4,5));
        //語法格式五
        LambdaCallback4 callback32 = (a,b)->{
            System.out.println("語法格式五 函數(shù)式接口");
            return a+b;
        };
        System.out.println("語法格式五 "+callback32.test(1,9));
        //語法格式六
        LambdaCallback3 callback4= (s,a)->{
            System.out.println("s: "+s);
            System.out.println("s: "+a);
        };
        callback4.test("語法格式六",110);

    }

    @FunctionalInterface
    public interface LambdaCallback1{
        void test();
    }
    @FunctionalInterface
    public interface LambdaCallback2{
        void test(String str);
    }
    @FunctionalInterface
    public interface LambdaCallback3{
        void test(String str,int a);
    }
    @FunctionalInterface
    public interface LambdaCallback4{
        int test(int str,int a);
    }
}

Lambda的函數(shù)式接口@FunctionalInterface的使用說明

  1. Lambda表達(dá)式需要“函數(shù)式接口“的支持,函數(shù)式接口:接口中有且僅有一個抽象方法的接口,稱作為函數(shù)式接口。
  2. 使用注解@FunctionalInterface修飾,表示檢查此接口是一個符合Java語言規(guī)范定義的函數(shù)式接口
  3. 函數(shù)式接口默認(rèn)繼承java.lang.Object,如果重寫了java.lang.Object中的抽象方法,也不算是抽象方法

如果一個接口中包含不止一個抽象方法,那么使用@FunctionalInterface注解編譯時會報(bào)錯。例子如下代碼:

@FunctionalInterface
public interface LambdaCallback5{
    void test();
    int sum(int a,int b);
}

上述LambdaCallback5類編譯時報(bào)錯:multiple non-overriding abstract methods found in interface Action

@FunctionalInterface的正確使用如上述代碼的LambdaCallback1~4接口。下面這個接口也是一個正確的函數(shù)式接口:

@FunctionalInterface
public interface LambdaCallback6 {
    // 抽象方法
    void add();
    // java.lang.Object中的方法不是抽象方法  屬于重寫Object中的方法
    boolean equals(Object var1);
    // default 不是抽象方法  默認(rèn)方法,通過實(shí)現(xiàn)類直接調(diào)用,不需要實(shí)現(xiàn)類實(shí)現(xiàn)該方法,提高了擴(kuò)展性。
    default void defaultMethod(){
    }
    // static 不是抽象方法  靜態(tài)方法,直接接口調(diào)用。跟普通的static方法是一樣的,不需要實(shí)現(xiàn)類去調(diào)用。
    static void staticMethod(){
    }
}

默認(rèn)(default)方法只能通過接口的實(shí)現(xiàn)類來調(diào)用,不需要實(shí)現(xiàn)方法,也就是說接口的實(shí)現(xiàn)類可以繼承或者重寫默認(rèn)方法。
靜態(tài)(static)方法只能通過接口名調(diào)用,不可以通過實(shí)現(xiàn)類的類名或者實(shí)現(xiàn)類的對象調(diào)用。就跟普通的靜態(tài)類方法一樣,通過方法所在的類直接調(diào)用。

有人會想如果不添加@FunctionalInterface注解的接口能不能支持lambda表達(dá)式,其實(shí)通過上面使用說明的第二點(diǎn)特點(diǎn),就可以知道,不添加@FunctionalInterface注解,只要接口符合函數(shù)式接口也是支持lambda表達(dá)式:看下面代碼:

public class TestLambda {
    @Test
    public void mainTest(){
        LambdaCallback7 callback7= (name,age)->"名字: "+name+" 年齡: "+age;
        System.out.println(callback7.test("lambda",3));
        onTest(callback7,"android",5);
    }
    private void onTest(LambdaCallback7 callback7,String name,int age){
        System.out.println(callback7.test(name,age));
    }
    public interface LambdaCallback7{
        String test(String name,int age);
    }
}

//打印出的結(jié)果為
I/System.out: 名字: lambda 年齡: 3
I/System.out: 名字: android 年齡: 5

找到源碼驗(yàn)證,其實(shí)就是JDK中有默認(rèn)支持處理。如果是單接口(單接口是指一個接口類里有且僅有一個抽象方法)就可以轉(zhuǎn)lambda表達(dá)式。@FunctionalInterface注解不是必須的,加不加都無所有了,如果添加了這個注解只是方便了編譯器檢查和提示作用而已,如果不符合函數(shù)式接口的定義,則注解會報(bào)錯,這樣減少一些誤操作。同時也驗(yàn)證了@FunctionalInterface的使用說明中的第二點(diǎn)特點(diǎn)。

了解Java內(nèi)置函數(shù)式接口

平時我們在寫安卓代碼時,你也會發(fā)現(xiàn)那些xxxListener接口也是內(nèi)置的函數(shù)式接口,如OnClickListener、OnLongClickListener、OnTouchListener等等。前面寫到過的Runnable接口也是內(nèi)置的函數(shù)式接口。下面例舉一些java內(nèi)置的函數(shù)式接口。

接口 參數(shù) 返回類型 示例
Predicate<T> T boolean 我是斷言函數(shù)式接口
Consumer<T> T void 輸出一個值
Function<T,R> T R 獲取Student對象的分?jǐn)?shù)
Supplier<T> None T 工廠方法
UnaryOperator<T> T T 邏輯非(!)
BinaryOperator<T> (T,T) T 求兩個數(shù)的最大值

例子:

//xxxListener
//普通寫法
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //TODO
    }
});
//lambda表達(dá)式寫法
findViewById(R.id.button).setOnClickListener( v->{
    //TODO
});

//Consumer
Consumer<String> consumer1 = new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
};
consumer1.accept("普通寫法");

Consumer<String> consumer2 = s->System.out.println(s);
consumer2.accept("lambda內(nèi)置寫法");

//對象的方法引用 下面會詳細(xì)講
Consumer<String> consumer3 = System.out::println;
consumer3.accept("對象的方法引用的寫法");

方法引用

當(dāng)要傳遞給Lambda體的操作已經(jīng)有了實(shí)現(xiàn)的方法時,可以使用方法引用。(實(shí)現(xiàn)抽象方法的參數(shù)列表必須與方法引用方法的參數(shù)列表保持一致)

方法引用:使用操作符“ :: ”將方法名和對象或類的名字分隔開( ClassName::MethodName )。有三種主要使用情況,如下:

  • 對象::實(shí)例方法
  • 類::靜態(tài)方法
  • 類::實(shí)例方法
/**
 * Consumer的作用顧名思義,是給定義一個參數(shù),對其進(jìn)行(消費(fèi))處理,處理的方式可以是任意操作。
 * accept(T t)是一個抽象方法。對給定的參數(shù)T執(zhí)行定義的操作。
 */
Consumer<String> consumer2 = s->System.out.println(s);
consumer2.accept("lambda內(nèi)置寫法");
Consumer<String> consumer3 = System.out::println;
consumer3.accept("對象的方法引用的寫法");

/**
 * BinaryOperator表示對兩個相同類型的操作數(shù)的操作。產(chǎn)生與操作數(shù)相同類型的結(jié)果。
 * 這是BiFunction的一個特殊例子,它的結(jié)果和操作數(shù)都是相同的數(shù)據(jù)類型。
 * 這個是一個函數(shù)式接口,該接口有一個函數(shù)方法apply(Object,Object)。
 */
//取兩個數(shù)的最大值
BinaryOperator<Double> bo1 = (d1,d2)->Math.max(d1,d2);
System.out.println("最大值為:"+bo1.apply(10.2d,20d));
BinaryOperator<Double> bo2 = Math::max;
System.out.println("最大值為:"+bo2.apply(11.9d,10.11d));

上述代碼:consumer2與consumer3和bo1與bo2的效果都是一樣的,只是他們所用的語法糖不一樣而已。

構(gòu)造器引用

格式:ClassName :: new

與函數(shù)式接口相結(jié)合,自動與函數(shù)式接口中方法兼容。可以把構(gòu)造器引用賦值給定義方法,與構(gòu)造器參數(shù)列表要與接口中抽象方法的參數(shù)列表一致。

public class Student{
    private String name;
    private String subject;
    private Integer score;

    public Student(){}
    public Student(String name) {
        this.name = name;
    }
    public Student(String name, String subject, Integer score) {
        this.name = name;
        this.subject = subject;
        this.score = score;
   }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSubject() {
        return subject;
    }
    public void setSubject(String subject) {
        this.subject = subject;
    }
    public Integer getScore() {
        return score;
    }
    public void setScore(Integer score) {
        this.score = score;
    }

    //函數(shù)式接口
    public interface ScoreCompareInterface<T> {
        //比較分?jǐn)?shù)
        Integer compareScore(T t1,T t2);
    }

    public static void main(String []args){
        Supplier<Student> sl1 = ()->new Student();
        Supplier<Student> sl2 = Student::new;
        Function<String,Student> f1 = name->new Student(name);
        Function<String,Student> f2 = Student::new;
    }

}

數(shù)組引用

格式:type[] :: new

Function<Integer,Student[]> fun1 = size->new Student[size];
Function<Integer,Student[]> fun2 = Student[]::new;

集合排序

對學(xué)生對象集合按數(shù)學(xué)科目分?jǐn)?shù)進(jìn)行排序。

public static void main(String []args){
    Student s1 = new Student("張三","數(shù)學(xué)",90);
    Student s2 = new Student("李四","數(shù)學(xué)",96);
    Student s3 = new Student("王老五","數(shù)學(xué)",10);

    List<Student> studentList = new ArrayList<>();
    studentList.add(s1);
    studentList.add(s2);
    studentList.add(s3);

    //排序
    Collections.sort(studentList,(st1, st2)->st1.getScore().compareTo(st2.getScore()));
    System.out.println(studentList);
}

List遍歷

//list遍歷
studentList.forEach(item-> System.out.print(item.getScore() +"  "));

返回集合中符合條件的元素

返回集合中符合條件的元素(返回分?jǐn)?shù)大于或等于90分的學(xué)生對象)

//返回集合中符合條件的元素(返回分?jǐn)?shù)大于或等于90分的學(xué)生對象)
studentList.removeIf(item->item.getScore()<90);
studentList.forEach(item-> System.out.print(item.getScore() +"  "));

removeIf的源碼,將集合中的每個元素放入test方法中,返回true則移除。

default boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    boolean removed = false;
    final Iterator<E> each = iterator();
    while (each.hasNext()) {
        if (filter.test(each.next())) {
            each.remove();
            removed = true;
        }
    }
    return removed;
}

對象間的元素比較

比較兩個學(xué)生對象的分?jǐn)?shù) 如果不用lambda表達(dá)式,那么就要寫匿名內(nèi)部類,下面用lambda表達(dá)式:

ScoreCompareInterface<Student> scoreCompare =
    (stu1,stu2)-> stu1.getScore().compareTo(stu2.getScore());
int compare = scoreCompare.compareScore(s1,s2);
if(compare==-1){
    System.out.println(String.format("s1學(xué)生%s分?jǐn)?shù)%s 低于 s2學(xué)生%s分?jǐn)?shù)%s",
                                     s1.getSubject(),s1.getScore(),s2.getSubject(),s2.getScore()));
}else if(compare==1){
    System.out.println(String.format("s1學(xué)生%s分?jǐn)?shù)%s 高于 s2學(xué)生%s分?jǐn)?shù)%s",
                                     s1.getSubject(),s1.getScore(),s2.getSubject(),s2.getScore()));
}else{
    System.out.println(String.format("s1學(xué)生%s分?jǐn)?shù)%s 等于 s2學(xué)生%s分?jǐn)?shù)%s",
                                     s1.getSubject(),s1.getScore(),s2.getSubject(),s2.getScore()));
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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