前言:最近看了點Java 函數語言程式設計, 寫了個小故事和大家分享下心得,不妥之處,歡迎拍磚
1. 遊行示威
Java 帝國成立20多年了, 20多年來帝國給每個程式設計師都提供了趁手的工具箱, 裡邊裝著物件導向, 自動記憶體管理,泛型 , 型別安全等強大的工具,
大家用這些工具開發了無數的框架和系統,都誇Java大法好。
(微信公眾號“碼農翻身”注: 請參考文章《Java 一個帝國的誕生》 《Java帝國之Java Bean(上)》 《Java帝國之Java Bean (下)》)
可以最近出現了一點新情況, 一群函數語言程式設計的狂熱份子不停的到總部來遊行, 要求帝國向LISP學習, 支援函數語言程式設計, 他們的理由也很充分:
“很多語言都支援函數語言程式設計了,比如 Ruby , Python…
“人家微軟的C#很早就支援了…”
“同樣是執行在Java 虛擬機器上的語言,那些雜牌軍Scala , Clojure 都有函數語言程式設計 , 我們的精銳,中央軍Java 怎麼就不支援呢?”
這樣的聲音雖然不是主流,但越來越多也讓我不勝其煩, 我把我最得力的助手小碼哥叫來商量對策:
“小碼, 外邊那一群狂熱分子叫囂的函數語言程式設計到底是什麼東西?”
“唉, 老大, 其實我也不知道函數語言程式設計的確切定義, 我只知道LISP是函數語言程式設計的鼻祖,並且像LISP這樣的語言學習起來很難,和我們的物件導向完全是兩個世界, 但奇怪的是很多掌握了LISP的人都無可救藥的愛上了她。”
“那它肯定有什麼過人之處 !” 我說
“像C,C++,Java, 用他們程式設計是一種命令式的風格, 告訴計算機如何如何做, 而用LISP程式設計是一種宣告式的風格,就像SQL一樣, 只是描述我要什麼, 不去具體的描述怎麼去做”
“聽起來很不錯啊 ” 我覺得一門語言能用宣告式的方式來程式設計, 對程式設計師絕對是福音啊。
“此外, 物件導向程式設計更多的是對資料進行抽象, 而函數語言程式設計是對行為進行抽象, 在LISP世界裡,函式是一等公民, 函式可以當做引數傳遞給另外一個函式, 函式也可以作為另外一個函式的返回值。”
“只用函式進行程式設計, 聽起來不可思議啊!”
“是啊, 所以如果你學過物件導向的語言,像我們們Java, 再去學LISP, 會感到一片迷茫, 無所適從, 真氣渾身亂竄, 好久才能平息下來。 ”
2. 強型別
“那我們能不能學一下Ruby , Python 在我們們Java 中新增一點函數語言程式設計的特性, 至少讓這些刁民, 奧不, 狂熱分子安靜下來? ”
小碼想了一會說: “有點難度, 因為Java 有一個特點,就是強型別的。”
“強型別? ,這是什麼意思? “我問他
“是啊, 強型別, 簡單的說無論任何變數都得有個型別, 並且不能變。”
小碼哥沒料到我還不知道這個概念, 開始興致勃勃的給我普及。
”比如你可以宣告一個變數
Person p = new Person();
可是如果你想讓p指向另外一個物件 :
p = new House();
我們偉大的java編譯器立刻就能告訴你錯誤: ‘型別不匹配, 不能把House轉化成 Person’
強型別的好處是顯而易見的, 我們在編譯期間就可以消滅大部分的錯誤,並且還可以在你用IDE程式設計的時候做出很‘智慧’的提示, 幫你自動補全程式碼。”
“與之相對的就是弱型別,例如在Ruby 中一個變數根本不需要宣告型別:
p = Person.new
p = House.new
這是沒有任何問題的, 變數p 可以指向任何物件, 但是也喪失了提前發現錯誤的可能性, 很多錯誤只能在執行時暴露出來。
def do_something(p)
p.walk(); // 假設walk只是Person獨有的方法。
end
系統檢查不出來你到底是傳遞進去一個什麼型別的物件, 如果p 指向一個House, House是不能walk的, 那在執行時就會報錯: undefined method `walk’ for #<House:0x00000002f40590>”
“但是弱型別也有好處, 例如
p = Animal.new
這個Animal 也有一個walk()方法。 那在呼叫do-something方法的時候完全沒有問題! ”
我有點懂了: “是不是說由於Java 是強型別的, 如果想支援函數語言程式設計, 我們也得給這些函式找個型別 ?”
小碼哥說: “是啊,不過這有點難度”
”我們們能不能重新的設計下Java 的型別系統 ?“ 我有點不甘心
“肯定不行, 我們Java 帝國為了吸引程式設計師,並且為了維穩, 曾經對外鄭重的承諾過: 帝國要保持向後相容性,你就是用Jdk 1.1編譯的程式碼, 在我們jdk 8中也能執行, 要是為了函數語言程式設計,改動太大,估計很多程式設計師就要拋棄我們了。”
“不管如何, 你得想想辦法,在Java 中加上函數語言程式設計, 帝國已經這麼大了, 再加一點東西也沒什麼大不了的。”
我給了小碼哥三天時間。
3. Lambda 表示式 和 型別推斷
過了兩天,小碼哥就興沖沖的來找我了: “老大,你看看,我設計出了java 中的Lambda表示式”
我接過紙一看, 上面寫著:
1 2 3 4 5 |
() -> System.out.println("Hello Lambda"); s-> s.toUpperCase(); (x,y) -> x +y ; |
“這看起來像是個函式啊? 還起個這麼時髦的名字Lambda !”
“對,Lambda可以說就是匿名函式, 我定義的語法是這樣的, 箭頭(->) 左邊是函式的引數列表, 右邊是方法體”
我看了看, 確實是, 第一個表示式 沒有引數, 函式體是列印”Hello Lambda”
第二個函式有一個引數 , 函式體似乎是把這個引數變成大寫字元。
第三個函式有兩個引數, 函式體是把兩個引數相加
“小碼,既然它們是函式, 按照函數語言程式設計風格, 就能把這些函式當做引數傳遞給另外一些函式了? ”
“那是自然, 我給你舉個例子“ 小碼哥說
例如我們有這麼個介面:
1 2 3 4 5 |
public interface StringFuction{ public String apply(String s); } |
還有這麼個函式:
1 2 3 4 5 |
public String run (StringFuction f){ return f.apply("Hello World"); } |
現在就可以把“匿名函式”當做引數傳遞給另外一個函式了
run (s -> s.toUpperCase()) ;
返回值就是大寫字串: HELLO WORLD
如果我們傳遞進去一個別的Lambda 表示式:
run (s -> s.toLowerCase()) ;
返回值就是小寫字串: hello world
”我有個問題, 上次你說過我們們Java 都是強型別的, 所有的東西都得有型別, 那這個Lambda表示式s -> s.toUpperCase() 的型別是什麼? ”
小碼哥說: “唉, 為了這個問題, 我可是費勁了, 我搞了個‘型別推斷’, 由編譯器智慧的推斷出s -> s.toUpperCase()的型別, 其實在上面的例子中, 這個Lambda表示式的型別就是StringFunction!”
“注意”小碼哥接著說, “這個StringFunction 的apply方法接收一個字串引數, 然後返回另外一個字串的值。
所以當我們用s -> s.toUpperCase() 去呼叫的時候, 就會知道 s 就是apply的引數, s.toUpperCase()其實就是這個方法的實現了, 這個方法的返回值也是String ”
“奧,我明白了, 其實就是我們之前的匿名類嘛,我們完全可以這麼寫“
1 2 3 4 5 6 7 8 9 |
run(new StringFuction(){ public String apply(String s) { return s.toUpperCase(); } }); |
“對的,本質就是這樣, 對於Lambda表示式,我們只是會做更多的型別推斷,如果你這麼呼叫 : “
run(s -> s.length());
“那編譯就不通過了, 因為這個匿名函式的返回值是int , 它不符合介面(StringFunction)的方法(apply)的語義, 接受一個字串, 然後返回一個字串。 ”
“那個函式介面的方法名稱(apply)重要嗎?” 我接著問
“不重要, 關鍵是它的輸入型別和返回值, 至於名稱,儘可能的好一點吧”
“唉,小碼,我有點懂了, 為了維護Java 的強型別, 還得定義一個函式介面, 然後編譯器會把這些匿名函式(Lambda表示式) 和這個介面做匹配, 但是你讓程式設計師們定義函式介面, 太麻煩了吧? ”
小碼哥不慌不忙 : “這個問題我考慮過了,我已經對我們的JDK做了增強, 特別引入了一個叫java.util.function 的包, 裡邊現成的函式介面足夠大部分人用了”
說著, 小碼哥給我展示了一個文件:
(1) Function函式介面: 傳入一個型別為T的引數, 返回一個型別為R 的引數
1 2 3 4 5 6 7 |
public interface Function<T,R>{ R apply(T t); ...... } |
(2) Predicate<T> 函式介面 :傳入一個型別為T 的引數, 返回boolean
1 2 3 4 5 6 7 |
public interface Predicate<T> { boolean test(T t); ...... } |
(3) Consumer<T>函式介面 : 傳入一個型別為T的引數,沒有返回值, 執行你自定義的操作
1 2 3 4 5 6 7 |
public interface Consumer<T> { void accept(T t); ...... } |
例如
s -> s.length() 就可以匹配 (1)
x -> x>5 就可以匹配 (2)
s -> System.out.println(s) 就可以匹配 (3)
我不僅暗暗讚歎:還是抽象大法好。
“小碼,我明白你的苦心, 這些確實都是高度的抽象啊”
“老大, 這是沒辦法的事啊, 想想我們java 帝國那麼多工具,框架, 我們必須得保持向後相容性啊。 所以一定得堅持強型別, 即使是匿名函式, 也得給它找個型別,即函式介面 !“
“但是有了這麼東西還是不夠, 似乎沒有體現出像SQL那樣‘宣告式’程式設計的好處啊? ”
“確實不夠, 我們得把類庫好好改改, 真正體現函數語言程式設計的特性, 我打算引入一個叫做Stream的概念。”
(未完待續)
宣告:原創文章,未經授權,禁止轉載
你看到的只是冰山一角, 更多精彩文章,盡在“碼農翻身” 微信公共號
“碼農翻身”公眾號由工作15年的前IBM架構師建立,分享程式設計和職場的經驗教訓。
- 我是一個執行緒我是一個Java class
- Javascript: 一個屌絲的逆襲
- Java : 一個帝國的誕生
- Basic : 一個老兵的自述
- 小王的架構師之路
- 程式設計師在工作中必備的能力
- 碼農需要知道的潛規則
- TCP/IP 之 大明王朝的郵差
- CPU 阿甘
- IE為什麼把Chrome和火狐打傷了
- Node.js :我只需要一個店小二
- 假如我是計算機系老師
- 假如時光倒流,我會這麼學Java
- 學會程式設計,而不是學會Java
- 15年程式設計生涯,資深架構師總結的7條經驗
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式