函數(shù)式編程
在開篇我需要介紹一下什么叫函數(shù)式編程轨香,我先引用網(wǎng)上的一個(gè)概念:
函數(shù)編程語言最重要的基礎(chǔ)是 λ 演算(lambda calculus)朝聋。而且λ演算的函數(shù)可以接受函數(shù)當(dāng)作輸入(參數(shù))和輸出(返回值)弱贼。
好吧,這樣說扬跋,太過于抽象了桩砰,我們先舉個(gè)簡單的例子拓春,區(qū)分一下面向過程释簿,面向?qū)ο笱怯纾秃瘮?shù)式變成。
面向過程
以下是一個(gè)求階乘的實(shí)現(xiàn):
public static void main(String[] args) {
int m = 10;
int result = 1;
for(int i = 1;i<=10;i++){
result = result*i;
}
System.out.println(result);
}
面向?qū)ο?/h2>
還是上面提到過的求階乘的概念:
public interface Function {
public int call(int x);
}
public class MultiplyFunction implements Function {
@Override
public static int call(int x) {
int result = 1;
for(int i = 1;i<=x;i++){
result = result*i;
}
return result;
}
}
public static void main(String[] args) {
System.out.println(new MultiplyFunction().call(10));
}
函數(shù)式編程
public static void main(String[] args) {
System.out.println(call(10));
}
public static int call(int x) {
int result = 1;
while(x>=1){
result =multiply(result,x);
x--;
}
return result;
}
public static int multiply(int x庶溶,int y) {
return x*y
}
總結(jié)
例子有些簡單煮纵,不知道是否恰當(dāng)懂鸵,容易理解行疏。相對于面向?qū)ο蟮木幊蹋瘮?shù)式編程無副作用,內(nèi)部不存在狀態(tài),易于并發(fā)。
再通俗點(diǎn)說,函數(shù)式編程會將函數(shù)也被當(dāng)作一種數(shù)據(jù)對象。下面來看一下Android中RXJava的實(shí)現(xiàn)爹土。
RXJava
RxJava我們可以理解為是一個(gè)觀察者模式的擴(kuò)展挟阻,什么是觀察者模式?
觀察者模式
舉個(gè)簡單例子,我們都會給Button設(shè)置一個(gè)Click事件對吧,對設(shè)置 OnClickListener 來說, Button 是被觀察者皿哨, OnClickListener 是觀察者,二者通過 setOnClickListener() 方法產(chǎn)生關(guān)系椎例。
OnClickListener一直觀察著Button挨决,當(dāng)Button被點(diǎn)擊,OnClickListener執(zhí)行onClick事件订歪。
RxJava 的觀察者模式脖祈,與之類似,Observable (可觀察者刷晋,即被觀察者)盖高、 Observer (觀察者)、 subscribe (訂閱)眼虱、事件喻奥。Observable 和 Observer 通過 subscribe() 方法實(shí)現(xiàn)訂閱關(guān)系,從而 Observable 可以在需要的時(shí)候發(fā)出事件來通知 Observer捏悬∽膊希可以通過下表進(jìn)行理解:
點(diǎn)擊事件 | RxJava |
---|---|
Button | Observable |
OnClickListener | Observer |
setOnClickListener() | subscribe() |
onClick() | onNext() onCompleted() onError() |
簡單實(shí)現(xiàn)
下面舉一個(gè)基本的例子:
Observer<String> observer = new Observer<String>() {
@Override
public void onNext(String s) {
Log.d(tag, "onNext: " + s);
}
@Override
public void onCompleted() {
Log.d(tag, "Completed!");
}
@Override
public void onError(Throwable e) {
Log.d(tag, "Error!");
}
};
Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("aaaa");
subscriber.onNext("bbbb");
subscriber.onNext("cccc");
subscriber.onCompleted();
}
});
observable.subscribe(observer);
just
上面的例子利用just方法還有一種寫法,不用重寫call方法:
Observer<String> observer = new Observer<String>() {
@Override
public void onNext(String s) {
Log.d(tag, " just onNext: " + s);
}
@Override
public void onCompleted() {
Log.d(tag, "just Completed!");
}
@Override
public void onError(Throwable e) {
Log.d(tag, "just Error!");
}
};
Observable observable = Observable.just("aaaa","bbbb","cccc");
observable.subscribe(observer);
我們可以看一下log輸出:
from
當(dāng)傳入的是一個(gè)數(shù)組的時(shí)候过牙,可以使用from方法
String[] words = {"aaaa", "bbbb", "cccc"};
Observable observable = Observable.from(words);
observable.subscribe(observer);
subscribe()
上面提到的都是去subscribe一個(gè)observer甥厦,在observer中也是一個(gè)一個(gè)的任務(wù),如果單獨(dú)執(zhí)行一個(gè)任務(wù)是否可以呢寇钉?答案是:必須的刀疙。
Action1<String> onNextAction = new Action1<String>() {
@Override
public void call(String s) {
Log.d(tag, s);
}
};
Action1<Throwable> onErrorAction = new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
Log.d(tag, "error");
}
};
Action0 onCompletedAction = new Action0() {
@Override
public void call() {
Log.d(tag, "completed");
}
};
String[] words = {"aaaa", "bbbb", "cccc"};
Observable observable = Observable.from(words);
observable.subscribe(onNextAction);
observable.subscribe(onNextAction, onErrorAction);
observable.subscribe(onNextAction, onErrorAction, onCompletedAction);
結(jié)果如下:
線程管理Scheduler
在不指定線程的情況下, RxJava 遵循的是線程不變的原則扫倡,即:在哪個(gè)線程調(diào)用 subscribe()谦秧,就在哪個(gè)線程生產(chǎn)事件;在哪個(gè)線程生產(chǎn)事件,就在哪個(gè)線程消費(fèi)事件油够。如果需要切換線程蚁袭,就需要用到 Scheduler (調(diào)度器)征懈。
Schedulers.immediate(): 直接在當(dāng)前線程運(yùn)行石咬,相當(dāng)于不指定線程。這是默認(rèn)的 Scheduler卖哎。
Schedulers.newThread(): 總是啟用新線程鬼悠,并在新線程執(zhí)行操作。
Schedulers.io(): I/O 操作(讀寫文件亏娜、讀寫數(shù)據(jù)庫焕窝、網(wǎng)絡(luò)信息交互等)所使用的 Scheduler。行為模式和 newThread() 差不多维贺,區(qū)別在于 io() 的內(nèi)部實(shí)現(xiàn)是是用一個(gè)無數(shù)量上限的線程池它掂,可以重用空閑的線程,因此多數(shù)情況下 io() 比 newThread() 更有效率溯泣。不要把計(jì)算工作放在 io() 中虐秋,可以避免創(chuàng)建不必要的線程。
Schedulers.computation(): 計(jì)算所使用的 Scheduler垃沦。這個(gè)計(jì)算指的是 CPU 密集型計(jì)算客给,即不會被 I/O 等操作限制性能的操作,例如圖形的計(jì)算肢簿。這個(gè) Scheduler 使用的固定的線程池靶剑,大小為 CPU 核數(shù)。不要把 I/O 操作放在 computation() 中池充,否則 I/O 操作的等待時(shí)間會浪費(fèi) CPU桩引。
另外, Android 還有一個(gè)專用的AndroidSchedulers.mainThread()收夸,它指定的操作將在 Android 主線程運(yùn)行阐污。
圖片下載
對于Android來說異步線程與主線程交互,是一個(gè)關(guān)鍵點(diǎn)咱圆,這里舉個(gè)簡單的例子:
Observer<Bitmap> observer = new Observer<Bitmap>() {
@Override
public void onNext(Bitmap s) {
imageView.setImageBitmap(s);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
Toast.makeText(ImageActivity.this,"error="+e.getMessage(),Toast.LENGTH_LONG).show();
}
};
Observable observable = Observable.create(new Observable.OnSubscribe<Bitmap>() {
@Override
public void call(Subscriber<? super Bitmap> subscriber) {
Bitmap bitmap = binary2Bitmap(getNetData(imageurl));
subscriber.onNext(bitmap);
subscriber.onCompleted();
}
}).subscribeOn(Schedulers.io()) // 指定 subscribe() 發(fā)生在 IO 線程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回調(diào)發(fā)生在主線程
;
observable.subscribe(observer);
其中 Bitmap bitmap = binary2Bitmap(getNetData(imageurl));是獲取網(wǎng)絡(luò)圖片笛辟,具體實(shí)現(xiàn)方法可以參照我的demo,但是這個(gè)方法由于有網(wǎng)絡(luò)請求序苏,需要放到子線程手幢,所以使用Schedulers.io(),而回調(diào)需要設(shè)置ImageView忱详,需要放到主線程围来,所以使用AndroidSchedulers.mainThread()
map
map是做什么用的呢,我們可以這樣理解,我們希望傳入的類型是String類型监透,而處理的類型是int類型桶错,這時(shí)應(yīng)該怎么辦呢,需要在處理之前胀蛮,根據(jù)一定的邏輯院刁,將string轉(zhuǎn)成int型。
Observable.just("aaaa","bbb","cc") // 輸入類型 String
.map(new Func1<String, Integer>() {
@Override
public Integer call(String s) { // 參數(shù)類型 String
return s.length(); // 返回類型 Bitmap
}
})
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer i) { // 參數(shù)類型 Bitmap
Log.e(tag,"length = "+i);
}
});
輸出:
flatMap
關(guān)于flatMap的使用場景有點(diǎn)抽象粪狼,我先上代碼退腥,然后再介紹:
Observable.just("aaaa","bbb","cc") // 輸入類型 String
.flatMap(new Func1<String, Observable<Integer>>() {
@Override
public Observable<Integer> call(String s) {
Integer[] info = new Integer[3];
info[0] = s.length();
info[1] = s.hashCode();
info[2] = s.getBytes().length;
return Observable.from(info);
}
})
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer i) { // 參數(shù)類型 Bitmap
Log.e(tag,"length = "+i);
}
});
打印截圖:
我們可以這樣理解, flatMap() 中返回的是個(gè) Observable 對象再榄,并且這個(gè) Observable 對象并不是被直接發(fā)送到了 Subscriber 的回調(diào)方法中狡刘。
例如上面的例子,可以看做是困鸥,一個(gè)String返回了一個(gè)Observable嗅蔬,一個(gè)Observable執(zhí)行了三次Action。
filter
顧名思義疾就,filter就是一個(gè)過濾澜术,可以指定過濾條件,例如我指定過濾字符串中含有字符a的字符串:
Observer<String> observer = new Observer<String>() {
@Override
public void onNext(String s) {
Log.d(tag, " filter onNext: " + s);
}
@Override
public void onCompleted() {
Log.d(tag, "filter Completed!");
}
@Override
public void onError(Throwable e) {
Log.d(tag, "filter Error!");
}
};
Observable observable = Observable.just("aaaa","bbbb","cccc")
.filter(new Func1<String, Boolean>() {
@Override
public Boolean call(String s) {
return s.contains("a");
}
});
observable.subscribe(observer);
輸出結(jié)果:
好了基本就講這么多虐译,如果感興趣的用戶可以下載我的demo瘪板,自己修改些內(nèi)容測試一下,理解可能會更深刻漆诽。
我的demo