函數語言程式設計
在開篇我需要介紹一下什麼叫函數語言程式設計,我先引用網上的一個概念:
函式程式語言最重要的基礎是 λ 演算(lambda calculus)。而且λ演算的函式可以接受函式當作輸入(引數)和輸出(返回值)。
好吧,這樣說,太過於抽象了,我們先舉個簡單的例子,區分一下程式導向,物件導向,和函式式變成。
程式導向
以下是一個求階乘的實現:
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);
}複製程式碼
物件導向
還是上面提到過的求階乘的概念:
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));
}複製程式碼
函數語言程式設計
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
}複製程式碼
總結
例子有些簡單,不知道是否恰當,容易理解。相對於物件導向的程式設計,函數語言程式設計無副作用,內部不存在狀態,易於併發。
再通俗點說,函數語言程式設計會將函式也被當作一種資料物件。下面來看一下Android中RXJava的實現。
RXJava
RxJava我們可以理解為是一個觀察者模式的擴充套件,什麼是觀察者模式?
觀察者模式
舉個簡單例子,我們都會給Button設定一個Click事件對吧,對設定 OnClickListener 來說, Button 是被觀察者, OnClickListener 是觀察者,二者通過 setOnClickListener() 方法產生關係。
OnClickListener一直觀察著Button,當Button被點選,OnClickListener執行onClick事件。
RxJava 的觀察者模式,與之類似,Observable (可觀察者,即被觀察者)、 Observer (觀察者)、 subscribe (訂閱)、事件。Observable 和 Observer 通過 subscribe() 方法實現訂閱關係,從而 Observable 可以在需要的時候發出事件來通知 Observer。可以通過下表進行理解:
點選事件 | RxJava |
---|---|
Button | Observable |
OnClickListener | Observer |
setOnClickListener() | subscribe() |
onClick() | onNext() onCompleted() onError() |
##簡單實現
下面舉一個基本的例子:
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
當傳入的是一個陣列的時候,可以使用from方法
String[] words = {"aaaa", "bbbb", "cccc"};
Observable observable = Observable.from(words);
observable.subscribe(observer);複製程式碼
#subscribe()
上面提到的都是去subscribe一個observer,在observer中也是一個一個的任務,如果單獨執行一個任務是否可以呢?答案是:必須的。
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);複製程式碼
結果如下:
執行緒管理Scheduler
在不指定執行緒的情況下, RxJava 遵循的是執行緒不變的原則,即:在哪個執行緒呼叫 subscribe(),就在哪個執行緒生產事件;在哪個執行緒生產事件,就在哪個執行緒消費事件。如果需要切換執行緒,就需要用到 Scheduler (排程器)。
Schedulers.immediate(): 直接在當前執行緒執行,相當於不指定執行緒。這是預設的 Scheduler。
Schedulers.newThread(): 總是啟用新執行緒,並在新執行緒執行操作。
Schedulers.io(): I/O 操作(讀寫檔案、讀寫資料庫、網路資訊互動等)所使用的 Scheduler。行為模式和 newThread() 差不多,區別在於 io() 的內部實現是是用一個無數量上限的執行緒池,可以重用空閒的執行緒,因此多數情況下 io() 比 newThread() 更有效率。不要把計算工作放在 io() 中,可以避免建立不必要的執行緒。
Schedulers.computation(): 計算所使用的 Scheduler。這個計算指的是 CPU 密集型計算,即不會被 I/O 等操作限制效能的操作,例如圖形的計算。這個 Scheduler 使用的固定的執行緒池,大小為 CPU 核數。不要把 I/O 操作放在 computation() 中,否則 I/O 操作的等待時間會浪費 CPU。
另外, Android 還有一個專用的AndroidSchedulers.mainThread(),它指定的操作將在 Android 主執行緒執行。
圖片下載
對於Android來說非同步執行緒與主執行緒互動,是一個關鍵點,這裡舉個簡單的例子:
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() 發生在 IO 執行緒
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回撥發生在主執行緒
;
observable.subscribe(observer);複製程式碼
其中 Bitmap bitmap = binary2Bitmap(getNetData(imageurl));是獲取網路圖片,具體實現方法可以參照我的demo,但是這個方法由於有網路請求,需要放到子執行緒,所以使用Schedulers.io(),而回撥需要設定ImageView,需要放到主執行緒,所以使用AndroidSchedulers.mainThread()
map
map是做什麼用的呢,我們可以這樣理解,我們希望傳入的型別是String型別,而處理的型別是int型別,這時應該怎麼辦呢,需要在處理之前,根據一定的邏輯,將string轉成int型。
Observable.just("aaaa","bbb","cc") // 輸入型別 String
.map(new Func1<String, Integer>() {
@Override
public Integer call(String s) { // 引數型別 String
return s.length(); // 返回型別 Bitmap
}
})
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer i) { // 引數型別 Bitmap
Log.e(tag,"length = "+i);
}
});複製程式碼
輸出:
flatMap
關於flatMap的使用場景有點抽象,我先上程式碼,然後再介紹:
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) { // 引數型別 Bitmap
Log.e(tag,"length = "+i);
}
});複製程式碼
列印截圖:
我們可以這樣理解, flatMap() 中返回的是個 Observable 物件,並且這個 Observable 物件並不是被直接傳送到了 Subscriber 的回撥方法中。
例如上面的例子,可以看做是,一個String返回了一個Observable,一個Observable執行了三次Action。
filter
顧名思義,filter就是一個過濾,可以指定過濾條件,例如我指定過濾字串中含有字元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);複製程式碼
輸出結果:
好了基本就講這麼多,如果感興趣的使用者可以下載我的demo,自己修改些內容測試一下,理解可能會更深刻。
我的demo
更多的開發知識,可以關注我的公眾號: