Rxjava和lambda語法

本文大部分代碼基於lambdaexpressions

寫在前面的話

本文只講Lambda語法,不會涉及到API講解,也不會涉及到RxJava原理介紹。個人感覺Lambda表達式是RxJava的基礎,只有明白Lambda表達式才能理解RxJava的一些函數的含義。

大概是在一年前知道RxJava項目,於是興致勃勃的上網去搜索各種關於RxJava的各種教程。當看到類似下面的代碼時,總感覺跟平常寫的代碼有些不一樣,感覺除了Builder模式一般不會出現這麼多的函數串聯調用。但是又不是Builder模式實在是有點費解。仔細看有Func1 Action1這樣的接口類,實在是費解如此命名下的類的含義,爲何如此大名鼎鼎的框架會違背Java命名規範?

Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        subscriber.onNext("HelloWorld");
    }
}).map(new Func1<String, String>() {
    @Override
    public String call(String s) {
        return s+" From Jiangbin";
    }
}).subscribe(new Action1<String>() {
    @Override
    public void call(String s) {
        System.out.println(s);
    }
});

之後由於時間精力有限,也就沒有再深入學習RxJava。但是RxJava的一些疑問點還是一直存留在腦海中。不明白的始終還是不明白。直到有一天看到了一篇關於Lambda的文章。才豁然開朗,然後再對RxJava二進宮。果然事半功倍,很快就掌握了RxJava基礎。所以Lambda是RxJava的基礎是成立的。

那麼什麼是Lambda表達式呢?

我們在寫Android程序或者GUI程序時,按鈕的點擊事件代碼是信手拈來

Button clickButton = 初始化button;
clickButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        System.out.println("你點擊了按鈕");
    }
});

Java8加入了對Lambda表達式的支持,上面代碼的Lambda表達式爲

Button clickButton = 初始化button;
clickButton.setOnClickListener((View v)->System.out.println("你點擊了按鈕");

Lambda表達式是多麼的簡潔原本七行的代碼用兩行代碼就輕輕鬆鬆搞定.(View v)可以將類型省略掉,因爲v類型可以自動推倒

Button clickButton = 初始化button;
clickButton.setOnClickListener((View v)->System.out.println("你點擊了按鈕");

上述Lamda是Android內置的函數,那麼我們如何編寫屬於自己的Lambda表達式下面我們將通過一個實例來一步步講解

假設我們有一個Person類,定義如下
public  class Person{
    public enum Sex{
        MALE,FEMALE
    }
    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge(){
        // ...
    }

    //打印Person的信息
    public void printPerson(){
        // ...
    }
}

現在我們有一個Person的集合List roster。我們要在roster中打印出符合某些條件的Person的信息有如下場景

1.打印出年紀大於18歲的人的信息,我們可能會編寫如下代碼

//函數定義
public static void printPersonOlderThan(List<Person> roster,int age){
    for(Person p:roster){
        if(p.getAge()>age){
            p.printPerson();
        }
    }
}
//函數調用
List<Person> roster = ...;
printPersonOlder(roster,18);

2.打印出18到25歲的人的信息,然後我們添加一個方法

//函數定義
public static void printPersonsWithinAgeRange(List<Person> roster, int low, int high){
    for (Person p : roster) {
        if (low <= p.getAge() && p.getAge() < high) {
            p.printPerson();
        }
    }
}
//函數調用
List<Person> roster = ...;
printPersonsWithinAgeRange(roster,18,25);

3.通過前面兩個例子我們發現如果需要查找符合新的條件的人的信息時就需要添加新的方法。於是我們決定使用接口來做判斷

public static void printPersons(
    List<Person> roster, CheckPerson tester) {
        for (Person p : roster) {
            if (tester.test(p)) {
                p.printPerson();
            }
    }
}
//定義接口
interface CheckPerson {
    boolean test(Person p);
}   
//尋找18歲到25之間的男性
class CheckPersonEligibleForSelectiveService implements CheckPerson {
    public boolean test(Person p) {
        return p.gender == Person.Sex.MALE &&
            p.getAge() >= 18 &&
            p.getAge() <= 25;
     }
}
//具體調用
printPersons(roster, new CheckPersonEligibleForSelectiveService());

4. 使用匿名內部類

printPersons(roster, new CheckPersonEligibleForSelectiveService());
#等價於
printPersons(roster,new CheckPerson() {
    public boolean test(Person p) {
        return p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25;
    }
});

5.使用Lambda表達式

printPersons(
    roster,
    (Person p) -> p.getGender() == Person.Sex.MALE
                  && p.getAge() >= 18
                  && p.getAge() <= 25
);

6.使用更通用的Predicate

假設我們現在有個Teacher類,我們也需要根據一些條件打印Teacher的一些信息。我們可能會定義一個接口

//定義接口
interface CheckTeacher {
    boolean test(Teacher t);
}

其實我們發現CheckTeacher和CheckPerson的定義其實完全一樣,完全可以用泛型定義成一個接口。所以Java類庫考慮到這點定義了Predicate

interface Predicate<T> {
    boolean test(T t);
}

於是函數定義變成了

public static void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
             p.printPerson();
        }
    }
}

函數調用依然不變

printPersonsWithPredicate(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

7.使用Comsumer,Comsumer源碼如下

@FunctionalInterface
public interface Consumer<T> {
    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

接下來我們重新定義printPersons方法

//注意我們這裏不在是printPersons 因爲通過使用Consume我們可以在查找到符合條件的對象後我們可以自定義如何處理這些對象
//我們不僅僅侷限於打印出這些人的信息這樣一個動作了
public static void processPersons(List<Person> roster,Predicate<Person> tester, Consumer<Person> block) {
    for (Person p : roster) {
        if (tester.test(p)) {
            block.accept(p);
        }
    }
}
//調用如下
List<Person> roster = ...;
processPerson(roster,//第一個參數
              (Person p)->{p.getAge()>18;},//第二個參數
              (Person p)->{//第三個參數
                            p.printPerson();
                            System.out.println("還可以做任何額外")
                            }
            );

8.上面的例子都是打印Person的全部信息,那如果我只想打印出符合條件的人的Email該怎麼辦,有兩種辦法

第一種 直接將7中的最後一個參數改成 (Person p)->System.out.println(p.getEmailAddress())
第二種 在block.accept(p) 想辦法將p 變成String

if (tester.test(p)) {
   //在這裏我們應該想辦法獲取到p的Email
    block.accept(p);
 }
 將這些改成
 if (tester.test(p)) {
    String email = p.getEmailAddress();
    block.accept(email);
 }
 但是如果都是這樣寫的話那麼代碼的侵入性太強了,Java提供了Function<T,R>接口用來轉換,跟RxJava的map方法是不是有點像
 完整定義如下
 public static <X, Y> void processElements(Iterable<X> source,Predicate<X> tester,Function <X, Y> mapper,
                                                Consumer<Y> block) {
    for (X p : source) {
        if (tester.test(p)) {
            Y data = mapper.apply(p);
            block.accept(data);
        }
     }
} 
調用如下
processElements(roster,//第一個參數
                (Person p)->p.getAge()>18,//第二個參數
                (Persion p)->p.getEmailAddress(),//第三個參數,此時已經將Person轉換成String了
                (String s)->System.out.println(s),//第四個參數,s的類型已經轉換成String了
                );

9.進一步精簡。上述我們發現函數式編程會導致有很多匿名內部對象作爲參數,代碼可讀性不強,容易出錯誤。Java通過Stream解決這一個問題

roster
    .stream()
    .filter(p->p.getAge()>18)//等價於8中的第二個參數
    .map(p->p.getEmailAddress())//等價於8中的第三個參數
    .forEach(email->System.out.println(email));//等價於8中的第四個參數

我們最終看一個RxJava的例子,讀者可以對比例子9

Integer[] list = {1,2,3,4,5};
Observable
    .from(list)
    .filter(integer->integer%2==0)//挑選出偶數
    .map(integer -> "number is"+integer)//轉換成String
    .subscribe(s->System.out.println(s));//相當於forEach(s->System.out.println(s));
    //forEach是同步的 subscribe是異步的

總結

第一次寫文章,不對之處望指正

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章