從RxJava中深入理解泛型,從學習泛型的過程中深入理解RxJava(一)

RxJava出現在我們的視線已經很久了,我自己也有閱讀過非常多的文章,談不上精通,但是勉強稱得上會一些簡單的使用,近日總是對這種響應式的編程,對RxJava魂牽夢繞,深刻的感覺到自己對泛型的認識,理解不到位,對RxJava的核心,觀察者模式有很多的不理解,導致在編碼或者說思想上達不到自己想要的效果

So,想著既然要學RxJava,自己對泛型的認識又不夠,就決定深入研究一下RxJava的源碼對泛型的使用,在探究源碼的過程中去理解泛型,去使用泛型,在泛型的學習中理解掌握RxJava,算是一種互補吧

再此默認大家已經會簡單使用RxJava,并且對RxJava的操作符(Operation)有一些了解

什么叫做響應式編程,什么叫做觀察者模式,什么又叫做事件,什么叫做消費,我談一下我自己的理解,如有不恰當之處,請大家指正(輕噴),這篇文章我也會長期更新下去,每次都會涉及到RxJava的操作符和自己去編寫這些操作符的實現

響應式編程:

與我們傳統(tǒng)編碼(函數式編程)不一樣,傳統(tǒng)編碼是做完這件事之后做另外一件事,給人的感覺都是單線程的,可能會開新線程去
處理耗時操作,在處理完成之后通過回調去處理之后的事情
而響應式編程提供給我們的是一種不一樣的思想,在響應式編程的世界中一切執(zhí)行流程都是基于事件的,已事件為驅動

觀察者模式:

觀察者模式是這樣子的,我先舉個例子看大家能不能理解
老師在講臺上講課,而所有的學生都會觀察著老師的一舉一動,而老師每產生一個事件(比如說在黑板上寫下一串公式),則對應著所有的學生都觀察到了老師的這一舉動,自己則在自己的筆記本中記錄,大腦中進行思考.而老師卻不關心自己的學生對這一舉動做什么事.
好了,例子就是這樣的,我們來分析一下這個例子跟觀察者模式有個什么關系?
這個例子中,老師可以產生事件,學生觀察著老師,而老師在產生事件之后咳嗽一下,通知所有的學生,我剛才做了什么事,你們應該也需要做點自己的事情
而這就產生了幾個概念,觀察者,被觀察者,事件,事件的處理與消費
被觀察者中存在觀察者的引用,即教師知道自己要通知的學生都有誰
被觀察者在產生事件之后通知觀察者,即教師產生事件之后通知每一位觀察著自己的學生

RxJava是對觀察者模式的一種高級運用,或者說是一種升級,他把觀察者模式具體化,更加明確了各個對象之間的關系

四個基本概念:
Observable (可觀察者柳琢,即被觀察者)累贤、
Observer (觀察者)秩冈、 
subscribe (訂閱)弄屡、事件享完。
Observable 和 Observer 通過 subscribe() 方法實現訂閱關系匣掸,從而 Observable 可以在需要的時候發(fā)出事件來通知 Observer。

談完了響應式的一些東西,我覺得既然要討論學習泛型的使用,我們就把泛型的一些概念也揪出來瞅一下

泛型分為:
    1 : 自定義泛型接口   interface Observer<T>
    2 : 泛型類           class ImplObserver<T> implements Observer<T>
    3 : 泛型方法         <T> Observer<T> call(T t) 
    
說一下泛型的作用域
如果將泛型聲明放在泛型接口,泛型類上,則該泛型在該類中就是確定的了,如果將泛型聲明放在了泛型方法上,則該泛型只在該方法中有效,如果泛型方法上聲明的泛型類型和類或接口中聲明的泛型一致,則會在該方法中隱藏類或接口上的泛型

貼個代碼看一下

將泛型聲明放在接口
public interface Observable<T> {
    public T call();
}
將泛型聲明放在方法
public interface Observable2 {
    <T> T call(T t);
}
泛型聲明在接口或類上,則類或接口中的方法均可使用T類型
public class ImplObservable<T> implements Observable<T>{
    @Override
    public T call() {
        // TODO Auto-generated method stub
        return null;
    }
}
泛型聲明在方法上,則除去該聲明有T泛型的方法之外,其他方法不識別T類型
public class ImplObservable2 implements Observable2{
    @Override
    public <T> T call(T t) {
        // TODO Auto-generated method stub
        return null;
    }
}


public static void main(String[] args) {
    //將泛型聲明在接口上或聲明在類上
    Observable<Student> observer = new ImplObservable<Student>();
    Student student = observer.call();
    //將泛型聲明在方法上
    ImplObserver2 Observable2 = new ImplObservable2();
    Student student2 = observer2.call(new Student());
}


泛型聲明在方法上的錯誤.png

大概了解一下泛型的作用域和泛型的類型之后,我們現在有這么一個需求
我給你一個對象,你能夠觀察著該對象,即一個觀察者中存在著該對象的引用,并且將該觀察者返回給我
我剛開始是這么想的,我們看一下有沒有什么問題

public class ImplObservable<T> implements Observable<T>{
    T t;
    public ImplObservable(T t){
        this.t = t;
    }
}

看代碼的話好像確實也沒什么問題,我把泛型的聲明放在了類上,那我這個類中都是可以識別T類型的,那我在創(chuàng)建對象的時候傳入T好像也沒什么不對,一樣完成了需求,我們回到創(chuàng)建該對象的main方法中去看一看,創(chuàng)建方法變成了這樣

ImplObservable<Student> observer = new ImplObservable<>(new Student());
如果我把<>刪除掉,則編譯器會給我們這樣一個警告
Type safety: The expression of type ImplObservable needs unchecked conversion to conform to ImplObservable<Student>
類型不安全?怎么會不安全?并沒有報錯啊..
事情是這樣的,在ImplObserver中,我們將泛型聲明放在了類上,在該類中都可以識別T類型了,但是,構造方法接受一個T類型,如果你在創(chuàng)建該對象的時候,沒有向該類聲明T類型究竟屬于哪種類型,就直接傳遞了一個實際類型過去,問題就像這樣,教室接受所有類型過來,可能是教師,也可能是學生,但是,你在創(chuàng)建該教室的時候,你對教室接受的類型進行了限制,但是你又沒有通知教室說教室準確的要接受哪種類型的對象,這就會造成泛型不安全

我去翻了翻Rxjava的源碼,他將Observable這個對象的構造函數的訪問權限降低了,不在他包下都不可以創(chuàng)建這個對象,但是他提供了一個create方法去創(chuàng)建,我們也來模仿一下

public class ImplObservable<T> implements Observable<T>{
    T t;
    private ImplObservable(T t){
        this.t = t;
    }
    public static <T> Observable<T> create(T t) {
        return new ImplObservable<T>(t);
    }
}

創(chuàng)建方法變成了這樣

Observable<Student> create = ImplObservable.create(new Student());

這樣我們在使用ImplObserver的時候就沒有對這個類的泛型進行明確說明,而是在create方法中進行了聲明,怎么聲明的? 這里面還有點門道,我們將create方法定義成了靜態(tài)方法,并且在該方法上聲明了T類型,這樣該方法的T類型就會隱藏掉類上的T類型,但是,我們的create方法做了這么一件事,將靜態(tài)方法的泛型,傳遞給了ImplObservable類上的泛型,并且返回創(chuàng)建好的ImplObservable泛型對象,此處的泛型類型為create方法聲明的泛型類型

是不是有點暈了?我當時也是暈的不行,迷糊過來之后也就那樣吧..如果有迷糊的朋友在下方評論吧,指出你的問題,我們一起討論

現在來考慮Rxjava寫代碼舒服的原因,全鏈式,全鏈式啊有木有,一條道走到黑,就是在不停的調用調用調用,不需要我們去考慮返回的對象是什么對象,只需要進行一系列操作就可以了,因為泛型已經幫助我們做了太多太多.

鏈式?哇,鏈式調用好像是很牛逼的,我們也來實現一下.

先說一下需求:

現在我給你一個student對象,你把這個對象給我通過某種規(guī)則給轉換成teacher對象,并且!
你要給我返回的觀察者不在是觀察學生了,而是,你剛才轉換成的teacher對象,并且!
我要求這些都是鏈式操作,起碼我看起來比較舒服,寫起來也比較開心!

說實話我是在學習泛型,研究Rxjava,我為啥非得給自己找不自在,提出的需求比較惡心就算了,還并且,倆并且,完成功能不就行了嗎?追求那么點的鏈式可能會給我的工作,我的業(yè)余時間帶來什么呢?
好了,我們來分析一下需求:

現在給一個student對象,要返回一個觀察著student的觀察者,我們通過上面的代碼可以這樣創(chuàng)建
ImplObservable.create(new Student());
現在要把這個學生通過某種規(guī)則轉換成teacher
做一個接口回調,傳遞學生類型進去,返回老師類型,但是這倆類型不明確,應該用泛型
我們模仿Rxjava的命名,也叫作Func1,
public interface Func1<T,R> {
    R call(T t);
}
接口做好了,我們現在要在Observer中去定義一個方法,將T類型轉換成R類型,為了保持和Rxjava的一致,我們也叫作map
并且該方法要接受一種規(guī)則,一種能夠將T轉成R的規(guī)則
方法聲明也有了
<R> Observer<R> map(Func1<T,R> fun1);
我們要在ImplObserver中去實現該方法了
@Override
public <R> Observer<R> map(Func1<T, R> fun1) {
    // TODO Auto-generated method stub
    Observer<R> ob = ImplObservable.create(fun1.call(t));
    return ob;
}
實現完了是這樣子的...

可能你看這點代碼會比較惡心,甚至會吐..

先喝杯水,起來晃悠一下,放松一會,希望你待會能打起十二分精神來讀接下來的一丁點篇幅
我會認真將自己的理解全部寫出來.

1:
    創(chuàng)建被觀察者即ImplObservable.create(new Student());這時候我們要把Student這個對象存儲起來方便之后使用,但是create是靜態(tài)方法,
有聲明泛型T,但是ImplObservable又是被泛型聲明的泛型類,在create的時候去創(chuàng)建真正的被觀察者,并且將create方法攜帶的泛型類型帶過去,即被觀察者中的泛型來自于create方法的泛型.
而ImplObservable的構造方法要求傳入一個T類型,并且該類中存在一個T t的引用,即保存create方法傳遞過來的實際對象的引用

現在我們搞清楚了一個被觀察者中的實際對象(T對象)究竟存儲在了哪,一個成員變量T t中

2: 
    現在我們要想辦法把一個存儲有t對象的被觀察者轉換成一個存儲有另外一個t對象的被觀察者,我們提供一個map操作,代表類型的轉換操作
    map要怎么實現是我們現在重點思考的問題
    既然ImplObservable中可以存儲t對象,一個ImplObservable對應一個T類型,也就意味著一個ImplObservable存儲的這個t對象的類型已經確定,
    那么我們要怎么把一個T對象轉換成R對象,轉換規(guī)則是怎么樣的
    public interface Func1<T,R> {
        R call(T t);
    }
    定義這么一個接口,接受一個T類型,返回一個R類型,在call方法中編寫轉換規(guī)則.
    那么map方法就必然要接受一個接口了,即轉換規(guī)則
    我們暫且這樣定義map方法
    <R> Observable<R> map(Func1<T,R> fun1);
    既然map方法也有了轉換的規(guī)則
    map的實現就這樣了
    @Override
    public <R> Observable<R> map(Func1<T, R> fun1) {
    Observable<R> ob = ImplObservable.create(fun1.call(t));
    return ob;
    }
    至于為什么這么做?
    現在我們知道ImplObservable.create方法接受一個T類型,并且把T類型存儲到當前對象中去,叫做t,這里是沒毛病的
    我們來回想一下Func1這個接口的泛型聲明,接受T,返回R.
    call方法接受T,返回R
    這就意味著我們的ImplObservable.create方法接受的就是一個R類型!!!
    并且ob對象中存儲的那個T t類型,實際上就應該是R r對象,即Teacher對象
    這時候我們返回了ob,一個存儲有R(teacher)對象的被觀察者
    至此,student轉換為teacher才真正結束.
what's.jpg
    好像是有點暈,好吧,回頭我畫個圖在說一下....

好了放松一下吧...確實比較惡心,也有點繞口,燒腦,但是想通了也就是那么一回事...

現在再來定義一個操作符,我們就結束今天這篇文章了

需求是這樣的

我需要在被觀察者的執(zhí)行過程中改一下被觀察者中存在的對象的屬性
并且不能破壞鏈式
我只是修改屬性,我要的還是該被觀察者

分析一下:

一個接口回調,需要把被觀察者保存的對象給傳遞回來,返回的結果不關心,即(void)

代碼實現:

//聲明下一步做的事
Observable<T> doOnNext(Action<T> action);
//定義泛型接口
public interface Action<T> {
 void callAction(T t);
}

實現doOnNext方法
@Override
public Observable<T> next(Action<T> action) {
   action.callAction(t);
   return this;
}
解釋一下
當前被觀察者中已經存在T對象的引用即t,只需要將t回調過去,在外部類中進行修改,
但是被觀察者是不改變的,直接返回this就可以了.

最后上一下測試代碼

public static void main(String[] args) {
        
    Student student = new Student();
    System.out.println("創(chuàng)建好student : " + student);
    final Teacher teacher = new Teacher();
    System.out.println("創(chuàng)建好teacher : " + teacher);
    
    ImplObservable.create(student)
    .map(new Func1<Student, Teacher>() {

        @Override
        public Teacher call(Student t) {
            // TODO Auto-generated method stub
            System.out.println("student hashcode : " + t);
            System.out.println("teacher hashcode : " + teacher);
            return teacher;
        }
    })
    .doOnNext(new Action<Teacher>() {
        
        @Override
        public void callAction(Teacher t) {
            // TODO Auto-generated method stub
            System.out.println("teacher hashcode2 : " + t);
        }
    });
}


輸出結果

    創(chuàng)建好student : com.lxkj.learn.Student@95cfbe
    創(chuàng)建好teacher : com.lxkj.learn.Teacher@1950198
    student hashcode : com.lxkj.learn.Student@95cfbe
    teacher hashcode : com.lxkj.learn.Teacher@1950198
    teacher hashcode2 : com.lxkj.learn.Teacher@1950198

在RxJava的世界里,你可以把所有的被觀察者想成一條河流,既然是河流,他就可以被過濾,攔截,轉換,變換,修飾,處理,甚至于合并等一系列操作,河流在流動的過程中觀察者是不會處理的,只有在河流抵達了終點,即被觀察者訂閱了觀察者之后,觀察者才會對最終的這股河流進行處理.

RxJava之所以強大,是因為他對河流在流動過程中提供了太多太多的操作符,我們能想到的操作,在RxJava中基本都有某種操作符來處理,而RxJava不太贊成我們自己去定義操作符,因為定義這些操作符的邏輯確實太繞了,就像上面我們自己定義map操作符一樣,真的是非常難受,一不小心可能就會造成一連串的錯誤

這篇文章的重點從RxJava的四個概念開始,到結束,全都是我和同學一點點分析總結出來的,跟源碼比起來搓一萬倍,
只能說跟大家一塊兒感悟一下Rxjava的魅力和他的執(zhí)行流程,對自己理解Rxjava也算是一點幫助吧,
最后想說的是,泛型這玩意,是真厲害...

以后沒事的話就去研究幾個操作符,對自己理解泛型理解Rx都是有很大幫助的.我也非常樂意分享這些心得,也希望大家能批評文章中的錯誤,我會認真吸取經驗反哺大家的..

余生還望大家多多指教~~
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末椒振,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子梧乘,更是在濱河造成了極大的恐慌澎迎,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件选调,死亡現場離奇詭異夹供,居然都是意外死亡,警方通過查閱死者的電腦和手機仁堪,發(fā)現死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門哮洽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弦聂,你說我怎么就攤上這事鸟辅》帐玻” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵匪凉,是天一觀的道長枪眉。 經常有香客問我,道長再层,這世上最難降的妖魔是什么贸铜? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮聂受,結果婚禮上蒿秦,老公的妹妹穿的比我還像新娘。我一直安慰自己蛋济,他們只是感情好棍鳖,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瘫俊,像睡著了一般鹊杖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扛芽,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天骂蓖,我揣著相機與錄音,去河邊找鬼川尖。 笑死登下,一個胖子當著我的面吹牛,可吹牛的內容都是我干的叮喳。 我是一名探鬼主播被芳,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼馍悟!你這毒婦竟也來了畔濒?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤锣咒,失蹤者是張志新(化名)和其女友劉穎侵状,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體毅整,經...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡趣兄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了悼嫉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艇潭。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出蹋凝,到底是詐尸還是另有隱情鲁纠,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布仙粱,位于F島的核電站房交,受9級特大地震影響彻舰,放射性物質發(fā)生泄漏伐割。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一刃唤、第九天 我趴在偏房一處隱蔽的房頂上張望隔心。 院中可真熱鬧,春花似錦尚胞、人聲如沸硬霍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唯卖。三九已至,卻和暖如春躬柬,著一層夾襖步出監(jiān)牢的瞬間拜轨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工允青, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留橄碾,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓颠锉,卻偏偏與公主長得像法牲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子琼掠,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內容