原文連接:http://pfmiles.github.io/blog/java-groovy-mixed/javascript
—— 這是我所理解的「工業化開發編程語言」的概念java
很顯然, java就是種典型的「工業語言」, 很是流行,不少企業靠它賺錢,很實際;
但java也是常年被人黑,光是對其開發效率的詬病就已經足夠多,不過java始終屹立不倒;node
這樣的局面其實無所謂高興仍是擔心,理性的程序員有不少種,其中一種是向「錢」看的 —— 我寫java代碼,就是由於工做須要而已,能幫助個人組織搞定業務,作出項目,這很好;
當有人說java語言很差的時候,理性的程序員不會陷入宗教式的語言戰爭之中,他會思考這些人說的是否有道理;若是真的發現整個java平臺大勢已去,他會絕不猶豫地扭頭就走,不過直到目前爲止,尚未這種跡象出現;python
那麼,從這些無數次的口水之爭中,咱們可否從別人的「戰場」上發現一些有用的東西, 來改進咱們的開發方式,從而使得java這種已經成爲一個「平臺」的東西走得更遠,賺更多的錢呢?
答案是「有的」,感謝那些參與口水戰爭的、各類陣營的年輕程序員們,有了大家,java speaker們纔有了更多的思考;git
我就只談一個最實際的問題:程序員
也就是說,究竟是哪些主要特性直接致使了某些其它語言在語法上相對於java的優越感?github
在groovy中定義map和list的慣用方式:算法
def list = [a, 2 ,3] def map = [a:0, b:1]
而java呢?只能先new一個list或map,再一個個add或put進去; 上面這種literal(字面量)形式的寫法便捷得多;編程
而javascript在這方面作得更絕, 咱們都用過json,而json其實就是literal形式的objectjson
極端狀況下,一門編程語言裏的全部數據類型,包括」內建」的和用戶自定義的,通通能夠寫成literal形式;
在這種情形下,其實這種語言連額外的對象序列化、反序列化機制都不須要了 —— 數據的序列化形式就是代碼自己, 「代碼」和「數據」在形式上被統一了
java對這方面幾乎沒有任何支持,對於提升編碼效率來說,這是值得學習的一點, 起碼「內建」數據結構須要literal寫法支持
不管是js, 仍是python/ruby,或是groovy,均可以將函數做爲另外一個函數的參數傳入,以便後者根據執行狀況判斷是否要調用前者
或者可以將一個函數做爲另外一個函數的返回值返回,以便後續再對其進行調用
這種高階函數特性,就不要再說java的匿名內部類「可以」實現了, 若是認爲匿名內部類已經」夠用」了的話,其實就已經與如今的話題「開發效率」相悖了
高階函數顯然是一種值得借鑑的特性,它會讓你少寫不少不少無聊的「包裝」代碼;
還有就是匿名函數(lambda)了
我不喜歡lambda、lambda地稱呼這個東西,我更喜歡把它叫作「匿名函數」或者「函數字面量(literal)」, 由於它跟數學上的lambda演算仍是有本質區別,叫」lambda」有誤導的危險
函數字面量的意思就是說,你能夠在任何地方,甚至另外一個函數體的調用實參或內部,隨時隨地地定義另外一個新的函數
這種定義函數的形式,除了「這個函數我只想在這裏用一次,因此不必給它起個名字」這種理由以外,還有一個更重要的理由就是「閉包」了
所謂閉包,其實也是一個函數,可是在這個函數被定義時,其內部所出現的全部」自由變量(即未出如今該函數的參數列表中的變量)」已被當前外層上下文給肯定下來了(lexical), 這時候,這個函數擁有的東西不只僅是一套代碼邏輯,還帶有被肯定下來的、包含那些「自由變量」的一個上下文, 這樣這個函數就成爲了一個閉包
那麼閉包這種東西有什麼好呢?其實若是懶散而鑽牛角尖地想,閉包的全部能力,是嚴格地小於等於一個普通的java對象的,也就是說,凡是能夠用一個閉包實現的功能,就必定能夠經過傳入一個對象來實現,但反過來卻不行 —— 由於閉包只有一套函數邏輯,而對象能夠有不少套,其次不少語言實現的閉包其內部上下文不可變但對象內部屬性可變
既然這樣,java還要閉包這種東西來幹嗎?其實這就又陷入了」匿名內部類能夠實現高階函數」的困境裏了 —— 若是我在須要一個閉包的時候,均可以經過定義一個接口再傳入一個對象來實現的話,這根本就跟今天的話題「開發效率」背道而馳了
顯然,java是須要閉包的
這和開發效率有關麼?
編程語言不是越「動態」,開發效率越高麼?還須要強大而複雜的靜態類型系統麼?
試想一下這種api定義:
def eat(foo) { ... }
這裏面你認識的東西可能只有’吃’了, 你知道foo是什麼麼?你知道它想吃什麼麼?吃完後要不要產出點什麼東西? —— 你什麼都不知道
這種api極易調用出錯,這就比如我去買飯,問你想吃什麼你說「隨便」,但買回肯德基你卻說你實際想吃的是麥當勞同樣
可能你還會反駁說,不是還有文檔麼?你把文檔寫好點不就好了麼? —— 不要逼我再提「匿名內部類」的例子,若是給每一個函數寫上覆雜詳盡的文檔是個好辦法,那就顯然 —— again, 與「開發效率」背道而馳了
那麼,靜態類型系統,這裏顯然就該用上了
靜態類型系統在多人協做開發、甚至團隊、組織間協做開發是很是有意義的;
擁有靜態類型系統的編程語言一般都有強大的、帶語法提示功能的IDE,這很正常,由於靜態類型語言的語法提示功能好作;
只要把別人的庫拿過來,導入IDE,各類函數簽名只需掃一眼 —— 不少狀況下根本不須要仔細看文檔 —— 就已經知道這個函數是幹嗎用的了, 合做效率成倍提高;
並且,做爲」api」,做爲「模塊邊界」,做爲與其它程序員合做的「門面」, 函數簽名上能將參數和返回值類型「卡」得越緊越好 —— 這樣別人不用猜你這個函數須要傳入什麼類型,甚至他在IDE裏一「點」,這裏就給自動填上了 :)
要作到「卡得緊」,光有靜態類型系統還不夠,這個系統還需強大, 試想一下這個例子:
/** * 我只吃香蕉和豬肉,請勿投食其它物品 */ public void eat(List<Object> list) { for(Object o: list) { if(o instanceof Banana){ ... // eating banana } else if(o instanceof Pork) { ... // eating pork } else { throw new RuntimeException("System err."); } } }
這段純java代碼已是「定義精確」的靜態類型了
但若是沒有上面那行註釋,你極可能會被System err.無數次
而這行註釋之因此是必需的,徹底是由於我找不到一個比List<Object>
更好的表達「香蕉或豬肉」的形式, 這種情形足以讓人開始想念haskell的either monad
在「強大而複雜的類型系統」這一點上,jvm平臺上使人矚目的當屬scala了,惋惜java沒有,這是值得借鑑的
不過這一點的「借鑑」還需java的compiler team發力,我等也只是說說(按照java保守的改進速度,估計HM類型系統是期望不上了)
剛說完靜態類型,如今又來講動態類型系統合適麼?
然而這與節操無關,我想表達的是,只要是有助於「開發效率」的,都可以借鑑,這是一個理性的java speaker的基本素質
咱們在開發項目的時候,大量的編碼發生在「函數」或「方法」的內部 —— 這就比如你在屋子裏、在家裏宅着同樣, 是否是應該少一些拘束,多一些直截了當?
在這種情形下,動態類型系統要不要太爽? ——
Void visitAssert(AssertTree node, Void arg1) { def ahooks = this.hooks[VisitAssertHook.class] ahooks.each {it.beforeVisitCondition(node, errMsgs, this.ctx, resolveRowAndCol, setError)} scan((Tree)node.getCondition(), arg1); ahooks.each {it.afterVisitConditionAndBeforeDetail(node, errMsgs, this.ctx, resolveRowAndCol, setError)} scan((Tree)node.getDetail(), arg1); ahooks.each {it.afterVisitDetail(node, errMsgs, this.ctx, resolveRowAndCol, setError)} return null; }
你知道ahooks是什麼類型麼?你不知道但我(我是編碼的人)知道
你知道ahooks身上有些什麼方法能夠調麼?你一樣不知道但我知道
你不知道不要緊,只要我知道就好了,由於如今是我在寫這段代碼;
這段代碼寫完之後,我只會把Void visitAssert(AssertTree node, Void arg1)這個類型明確的方法簽名提供給你調用,我並不會給你看函數體裏面的那坨東西,所以你知不知道上面這些真的不要緊
方法內部盡是def, 不用書寫繁複的List<Map<String, List<Map<Banana, Foo>>>>
這種反人類反社會標語, 每一個對象我知道它們身上能「點」出些什麼來,我只管「點」,跑起來以後invokedynamic會爲我搞定一切
動態類型系統 —— 這就是方法內部實現應該有的樣子
哪怕你的方法內部實現就是一坨shi,你也但願這坨shi能儘量小隻一點,這樣看起來更清爽是吧?
不要說我太分裂,我要笑你看不穿 —— 靜態類型和動態類型既然都有好處,那麼他們能放在一塊兒麼?
能的,這裏就須要點明這篇文章的政治目的了: 「java與groovy混編」
並且,目前來看,jvm平臺上,只有它兩者的結合,才能完成動態靜態混編的任務
曾經我發出過這樣一段感嘆:
公共api、對外接口聲明、應用程序邊界…這些對外的「臉面」部分代碼,若是擁有scala般強大的類型系統…就行了;而私有代碼、內部實現、各類內部算法、邏輯,若是擁有groovy般的動態、簡單的類型系統…就行了;綜上,若是有門語言,在接口和實現層面分別持有上述特性,就行了
這種「理想」中的語言或許某天我有空了會考慮實現一個
而如今,雖然說不是scala,但我終於想要在java和groovy身上來試驗一把這種開發方式了
這裏我坦白一下爲何沒用scala,緣由很簡單,我在技術選型方面是勢利的,scala還不被大多數平均水平的java開發人員(參見」工業化開發編程語言」定義第一條)接受,這直接致使項目的推動會遇到困難
而相對來說,我暫且相信大多數java開發人員都還算願意跨出groovy這一小步,固然這還須要時間證實
好了,下面還剩下一點點無關痛癢的牢騷 ——
macro, eval, 編譯過程切入, 甚至method missing機制,這些都算「元編程」
元編程能力的強弱直接決定了使用這種語言創做「內部DSL」的能力
java在元編程方面的能力,幾乎爲0
這是值得借鑑的
與groovy的混編,順便也能把groovy的元編程也帶進來
語法糖,關起門來吃最美味,這也是一種使得「方法內部實現更敏捷」的附加手段
網上隨便下載一份groovy的cheat sheet, 都會列舉groovy的那些寫代碼方面的奇技淫巧
這些奇技淫巧,在各類腳本語言之間其實都大同小異, 由於他們原本就是抄來抄去的
結合方法內部的動態類型環境,這必定會進一步縮小方法內部實現代碼的體積
我不去討論什麼語言纔是The True Heir of Java, 那會使這篇文章變成一封戰書,我只關心如何更好地利用現有開發資源完成項目,高效地幫組織實現利益
因此說java和groovy的混編是一種最「勢利」的折衷,我不想強迫平均水平的開發人員去學習一種徹底不一樣的語言,短時間內不會對項目有任何好處,真正想去學的人他本身會找時間去學
而groovy,說它是java++也不爲過,由於java代碼直接就能夠被groovy編譯, groovy徹底兼容java語法, 對通常java開發人員來講,這真是太親切了
這裏我要提一下我對「java和groovy混編」的一個我的性質的小嚐試 —— kan-java項目
kan-java這個小工具,凡是用戶在編碼使用過程當中能「碰」到的類和接口,所有都由java定義, 這確保用戶拿到的東西都有精確的類型定義
凡是對上述接口的實現,都以groovy代碼的形式存在
這貫徹了」接口靜態類型,內部實現動態類型」的宗旨, 或者說「凡是要提供給另一我的看、調用的地方(接口或接口類),使用java,不然就用groovy」
固然了,單元測試也徹底由groovy代碼實現
將kan-java的jar包引入到項目中使用時,就跟使用其它任何純java實現的jar包同樣 —— 接口清晰,參數類型明確,返回類型明確, 你不會也沒有必要知道開發人員在具體實現的時候,使用動態語言爽過一把
對於java和groovy的混編,項目的pom.xml如何配置,除了能夠參考kan-java的配置外,還能夠參考這個gist: https://gist.github.com/pfmiles/2f2ab77f06d48384f113, 裏面舉例了兩種配置方式,各有特點
具體的效果,還須要真正地去實際項目中體會 另外,kan-java也是一個有趣的工具,這個工具所實現的功能我也是從未見到java世界內有其它地方討論過的,它能夠輔助java作「內部DSL」,有場景的能夠一試