本期的話題是ModernJava
目錄
前言
一、重新認識JAVA
二、 Lambda運算式
2-1 匿名內部類的問題
三、常用函式式介面
四、組合異步編程與新API
五、總結
前言
因有感直播中接收知識點過于密集,單純聽一聽吸收下來的并不多,遂綜合直播回放和文字將其作為學習筆記,以慢慢消化,下文主要內容為老師講解的內容,我做資料查找和補充作業,
本期嘉賓:湘王(下文簡稱XW)、蒼狼、團子
視頻回放:http://s5j.net/bc9mm
推薦結合視頻回放和筆記內容,會有較好的學習效果,
以下是正文部分
一、重新認識JAVA
XW:
Modern Java這個概念是這幾年才提的比較多的一個概念,以及與之相關的函式式編程介面和Lambda運算式,在Java中新出現的組合異步編程和一些新的API,
Java和大資料其實也有自己的「行話」,但并不是像網路安全那樣搞三個人出來,而是通過另一種方式,
比如如果我說juc,稍微資深一點的Java工程師一聽就知道我說的是什么,再比如S0S1,還有Eden、Stop The World等等,Java高玩也肯定不陌生,
👇為筆者注:
1. JUC 簡介(注:可直接點擊,會跳轉至我查找到的解釋原文)
- 在 Java 5.0 提供了
java.util.concurrent(簡稱JUC)包,在此包中增加了在并發編程中很常用的工具類,
用于定義類似于執行緒的自定義子系統,包括執行緒池,異步 IO 和輕量級任務框架;還提供了設計用于多執行緒背景關系中
的 Collection 實作等;2.S0S1
- S0:年輕代中第一個survivor(幸存區)已使用的占當前容量百分比
- S1:年輕代中第二個survivor(幸存區)已使用的占當前容量百分比
3.Eden
- 因為采用復制演算法,所以年輕代分為三部分:1個Eden區和2個Survivor區(分別叫From和To),默認比例為8:1,
4.Stop The World
- 所謂的Stop the World機制,簡稱STW,即在執行垃圾收集演算法時,Java應用程式的其他所有除了垃圾收集收集器執行緒之外的執行緒都被掛起,
在大資料領域,WC、MR、算子,這些只要是稍稍搞過一點大資料的童鞋,應該一聽就懂是什么,
前面這兩個可能淺一點,但是如果我說并行度、流分組、資料傾斜,這個就需要一點功底才知道了,
或者就像搞過電商的童鞋一聽SKU、SPU就知道是啥,
👇為筆者注:
1.WC
- 統計檔案里面有多少單詞,多少行,多少字符,
2.MR
- MR理論:MapTask & ReduceTask
3.算子
- 在數學上可以解釋為一個函式空間到函式空間上的映射O:X->X,其實就是一個處理單元,往往是指一個函式,在使用算子時往往會有輸入和輸出,算子則完成相應資料的轉化,比如:Group、Sort等都是算子,
Java是一個靜態型別的、強型別和解釋型的語言,這些都代表什么意呢?
解釋型就是說想Java源代碼其實是沒有被計算機轉換成可執行檔案的,也就是位元組碼,
像之前學過的C語言,源代碼就必須通過make編譯成目標檔案以后才能執行,但是Java就不用先編譯之后才執行,因為Java是跨平臺的,有JVM,所以可以在運行的時候再去解釋,
👇為筆者注:
JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用于計算設備的規范,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實作的,
強型別比較好理解,例如javascript,比如我用var定義一個變數,雖然之前存的是數字,但是后面還可以存字串或者是其他型別,
所以js就是一個弱型別語言,但是像Java是需要明確定義資料型別的,而且一旦定義之后就不能改成其他資料型別了
舉個例子:這有點像結婚證~沒拿之前,你交男女朋友都沒有約束力,想和誰做朋友都可以(筆者注:可以,但不提倡),這就是弱型別,
但是一旦拿了結婚證辦了酒呢,就不能想換個人就換個人了,這就是強型別
靜態語言是你在寫代碼的時候就能知道資料型別有沒有報錯,就像你用idea寫java代碼的時候,就能檢查出來語法錯誤,但是你用webstorm寫js代碼,默認情況下它是不會檢查你的語法錯誤的,即使是選了JavaScript語法,也不會檢查的,
Modern Java中的Modern又是什么意思呢?
Modern的字面意思就是現代,更深一層的意思就是先進的,Modern Java就是更先進的Java的意思,而且也主要是從Java8之后才開始叫起來的,
那Java8之前就不叫Modern Java了嗎?
因為先進不僅意味著能夠很好地解決當前的問題,還能夠解決未來可能遇到的問題,例如更穩定、更簡單、可擴展,等等等等,
而Java8就達到了這樣的標準,而Java8之所以被稱之為Modern Java,不是因為它僅僅是Java7的升級版,而是它還是Java7的重生版
請注意,是「重生版」!
為什么這么說呢?
因為其實「函式」這個概念可能從編程語言誕生的那天起就有了,很多同學第一次學習編程的時候,例如C語言里面的函式,和Java里面的類/物件方法本質上是一回事,
但是和C語言、JavaScript等語言不同的是,在Java8之前的版本中,函式只是Java的附屬品,根本沒有獨立存在的資格,
如果大資料技術沒有出現,不知道這種狀況還會持續多久
難道Modern和大資料也有關系嗎?
肯定有關系的,Java是一門純面向物件語言,這個在OOD和OOP的時代,OOD呢就是面向物件的設計,OOP就是面向物件編程,也就是在十多年前吧,因為那時候純面向物件的編程語言不多,所以那時候的Java完全是巨星閃耀,一枝獨秀的感覺,
但是時過境遷,云計算和大資料技術出現了,在這種情況下,如果只是要處理大量的資料而業務要求非常簡單的情況下,面向物件不僅沒有任何好處,反而還會造成拖累,
因為大量的代碼都和資料處理這件事情關系不大,但按照Java的語法規定卻又不得不寫,比如Java中大量的資料操作,其實都是通過集合來完成的,比如List、比如Map和Set,這些工具在大資料技術到來之前倒并沒有覺得特別不方便的地方,
但是大資料里面的MR資料處理程序,就把這個完全顛覆了,也不是顛覆吧,就是使用方式感覺就不一樣了,
比如一個簡單的WC,就是wordcount,對集合的使用就和以前的代碼完全不一樣,尤其是像Spark、Flink這種流式及流批一體的大資料計算引擎中對各類算子的使用,例外復雜,用傳統Java的集合處理API完全無法勝任,這是一方面,
另一方面,像Python、Scala等腳本類編程語言在大資料環境下,由于編碼方式簡單,只需要寫點簡單的功能函式,就能馬上看到結果了,簡直是如魚得水,天然就適合做大資料功能的開發,
所以這樣一來二去,兩相對比之下,Java的在這方面的弊端就太明顯了,
所以,開發出Java的工程師們也看到了這些情況,就干脆給Java來了個大手術,把以前類和物件里面的方法,或者叫函式,直接提升成了一等公民,
所以能夠處理大資料的Java就是Modern Java了嗎?
部分原因吧,但是卻是影響最為深遠的決定性的原因,它直接促成了Modern Java函式式編程的誕生,
也就是說,Java8之后的Java,不但依舊可以用OOD、OOP的方式編程,而且還多了一個新的,也非常強大的功能特性,那就是面向函式編程OFP,或者也叫函式式編程,
不過,還有另一個可能是同樣重要而且同樣影響深遠的決定性因素,只是目前還不太明顯而已,
如果就是這樣的要求,確實很難玩出新花樣,畢竟需求太簡單,體現不出水平來,好,那這樣,我問一下各位在線的小伙伴,如果需求復雜一點,比如現在有幾家大廠的員工之間不定期搞一下聯誼聚會活動,解決一下單身問題,但是只有通過公司才能找到員工,然后員工資訊有年齡、是否已婚等內容,如果讓你來做一個未婚員工的篩選,再根據年齡排序,然后全部放到一個串列里面,你該怎么實作呢?再加個變態的要求:用一行代碼實作,
在實際開發中,這種對集合的操作是每個Java工程師每天都要處理的事情,但是大多數工程師,至少我曾經面試過的Java工程師,或者說程式員吧,大部分都沒用過Java8,有的甚至聽都沒聽說過,真的有點讓人震驚,
Java8最主要的變化就是函式式編程,而這其中最為主要和最為核心的部分,可能也是大多數人都覺得比較難理解地方,就是我剛才說的,用一行代碼解決需求的Lambda運算式,
二、 Lambda運算式
Lambda運算式之所以叫Lambda,是因為λ演算在計算機存在之前就已經存在了,它是由數學家阿隆佐·邱奇從數理邏輯中發展演變出來的,并且任何可計算函式都能用這種形式來表達和求值,
而且Lambda演算強調的是變數、謂詞(動詞)、量詞和規則的運用,而非實作它們的具體機器,
👇為筆者注:知識點補充Lambda
Lambda 運算式(lambda expression)是一個匿名函式,Lambda運算式基于數學中的λ演算得名,直接對應于其中的lambda抽象(lambda abstraction),是一個匿名函式,即沒有函式名的函式,Lambda運算式可以表示閉包(注意和數學傳統意義上的不同),
不采用Lambda的老方法:
Runnable runnable1=new Runnable(){
@Override
public void run(){
System.out.println("Running without Lambda");
}
};
采用Lambda的新方法:
Runnable runnable2=()->System.out.println("Running from Lambda");
這種思想和開發出Java8的工程師們不謀而合,而且也為了紀念阿隆佐·邱奇這位數學家的天才發現,不光Java8里面,現在只要某個語言中涉及到函式式編程,統一都叫Lambda運算式,比如,Python里面也有Lambda運算式,
計算機科學里面其實有很多很有意思的趣事,大家可以當作學習之余的一點談論交流話題吧,
IT行業里面有一句話:Talk is cheap,show me the code,翻譯成白話過來說就是,能動手咱就別BB,所以現在團子給大家展示一下,看看Java8之前,工程師們都是怎么開發的,
(演示建議觀看直播回放)
感謝團子的演示,咱們可以簡單看看代碼,這段代碼,不管Modern不Modern,肯定是都能運行的,不過按照Java標準語法來看,這里面有幾行代碼是沒有意義的,也就是不夠優雅~
對,就是優雅,也就是太笨重,而且不靈活,
為什么這么說呢,因為這些代碼是和實際功能沒有很強的關聯關系,這些代碼已經不可能再去優化它了,否則功能都不完整,
比如代碼里面的setLayout、setTtitle等等set方法,還有一些創建jbutton的方法,以及給按鈕增加點擊事件的方法,就是addActionListern()方法,
這其實就是當初Java8的開發者所面臨的問題,該從哪里開始優化代碼呢?
大家可以把自己想像成Java8專案開發組的成員,就是由你來開發Java8這門語言的編譯器,想想如果是你,你該怎么優化Java,讓他變得更好更強大,然后可以對別人說:「我會Python,我會C,(唉~)就是不用,(就是玩~)」,(括號內內容是筆者瞎寫的,略)
既然我們已經知道,業務代碼,就是那些創建環境、給物件賦值、然后調整大小、位置的代碼是不能被優化的,那么很明顯,真相只有一個~
2-1 匿名內部類的問題
做Java時間長一點的同學都知道,在Java中,有一種類叫「匿名內部類」,就是剛才代碼里面給按鈕1和按鈕2增加點擊事件的時候用到的那個ActionListener,還有創建執行緒的時候用到的Runnable,這種匿名內部類的特點就是沒有賦值給一個明確的變數,比如創建按鈕時就通過代碼new JButton("button1")明確地把物件的參考賦值給了jb1和jb2,而像匿名內部類這種語法的設計初衷,就是為了方便Java程式員將代碼作為資料傳遞進去,其實就是想達到「代碼即資料」的目的,

說的直白一點,就是把new ActionListener() {}這段代碼塊作為資料,傳給jb1或者jb2物件,
如果是我,我會覺得最外面那個new ActionListener() {}有點多余,原因有幾點:
1、因為ActionListener是以匿名類的方式實作的,既然它都已經不想讓人知道自己干了什么,退出江湖了,又何必再露臉呢,對吧,
2、因為button的addActionListener里面已經要求了ActionListener作為引數,所以其實只要是個稍微聰明點的編譯器,它自己就應該能夠推斷出來需要啥玩意,不用我再來告訴它一次,有點多此一舉了,
3、第三點就是,其實咱們寫代碼,最終是只需要兩個東西的,也就是資料和結果,而且這個類本身也是匿名的,所以它完全可以去掉,去掉以后應該是這樣的,
public是多余的,因為只是作為引數了,有沒有public沒啥區別,所以去掉;
然后void也是多余的,編譯器可以推斷出來有沒有回傳值,以及它的回傳值是啥;
然后方法名actionPerformed也是多余的,因為這段代碼已經被賦值給「代碼塊」了,所以它不需要,也不配擁有自己的名字了,

刪光不是目的,目的是要達到咱們優化的目的,
進行到這一步,我想Java8開發組的工程師們已經知道自己在干嘛了,
剛開始只是嘗試著找到優化的方向,另外說一句,咱們可能用了不到幾分鐘就做到了這一步,那些工程師們可是嘔心瀝血了幾年才達到了這一步的,所以在這里,我們需要向那些優秀的工程師們致敬,感謝他們為我們這些程式員所做出的付出,為整個Java開發社區和生態所做出的貢獻,
然后,和回傳型別一樣,需要什么型別的資料編譯器也應該是能夠自己推斷出來的,所以括號里那個ActionEvent也可以省略了,
有些像我這樣追求完美的工程師會覺得這還不行,太丑了,這種形式看起來真的特別別扭,不知道其他人會不會這么覺得,反正我是這么覺得,如果是我的話,怎么能寫出這么丑陋的代碼來呢,我會繼續把它打扮漂亮一點,

大功告成,大家可以看團子的桌面,對比一下之前的代碼和現在精簡的代碼,這就是Java8中標準的Lambda運算式,也許之前的那些工程師經歷了和我們一樣的程序,
Lmabda運算式改造完了,但是咱們改造的目的不就是為了運用嗎,對吧,所以,咱們應該馬上趁熱打鐵,讓團子把第二個按鈕再按第一遍的形式改一下,
這段代碼其實還有點小尾巴,就是多執行緒那一塊,留著給大家用Lambda的方式去優化的哈,還是老規矩,如果看完咱們今天的直播以后,有小伙伴需要原始碼的,就在微信群里打0.618,如果統計出來有超過50位小伙伴,就讓團子把原始碼發到群里,
大家可以看到哈,從去掉new ActionListener() {}開始,到去掉public,去掉void,去掉方法名actionPerformed(),去掉引數ActionEvent,最后把為了美化代碼,增加了「->」符號,最終,咱們實作了將代碼作為資料傳遞的目的,而且是以一種非常優雅的方式完成的,所以總結一下,Lambda運算式的特點:
一是匿名,沒有名字,只有代碼塊;
二是它基于介面函式執行,而不是類;三是它傳遞的是行為和資料,而不是代碼,這幾點,只有在理解的基礎上才能記住,
這種帶個箭頭的語法是不是可以到處用?或者說我想在哪加都行呢?
這其實就是函式式編程中比較重要的另一塊了,Lambda可不是你想寫,想寫就能寫的,剛才咱們之所以能夠省掉那么多的代碼,其實是開發Java8的工程師們給咱們提前就準備好的語法規范,
這個規范就叫函式式介面,說白了,就是專門用來寫lambda運算式的介面,這種介面里面只有一個方法,記住,只能有一個,
雖然Java8之后的版本里面加入了默認方法,但是函式式介面里面只應該有一個待實作的非默認方法,這一點很關鍵,各位同學可以記下來,這是一條函式式編程的語法約束,

大家可以看到,比如有的函式式介面只有一個引數,那么就是arg1,如果有兩個引數就是arg2,依此類推,如果有回傳值的話,也要根據情況來區分,

這就是按照上面的「形式」串列歸納的幾個常見的函式式介面,而且有些介面其實在Java8之前就已經存在了,這樣也極大地平緩了程式員學習函式式編程的曲線,所以有時候真的是不得不佩服和感謝那些天才的工程師們,沒有他們,我們不可能像今天這么方便,
剛才咱們的代碼里面就有和第一個Runnable相關的函式式介面吧,就是「() -> void」,而且之前給大家留的一個小任務,多執行緒那塊改成lambda運算式的,也是這個,至于后面兩個,其實大家看看JDK的原始碼就知道了,
三、常用函式式介面
雖然可以自己定義函式式介面,但是開發Java8的工程師們已經預先替咱們程式員考慮到了使用Lambda運算式的若干種場景,所以,其實大部分情況下,都不用自己再去定義函式式介面了,

大家看著這張表格,這些都是Java8里面已經事先定義好的函式式介面,這些介面都在Java8的juf包里面,
方法名就是函式中的那唯一一個介面方法,然后描述符就是Lambda運算式的抽象描述,比如對于咱們自定義的MyFirstFunctionInterface函式式介面,它的Lambda運算式是(content) -> System.out.println(content);
那么它的抽象描述就是(T) -> void,就是這個意思,然后按照Lambda的類別來分的話,它屬于消費型,因為它接收了一個引數卻沒有回傳任何資料;相對應的,第一行的() -> T就屬于供給型的,因為它不需要任何引數,但卻回傳了一個泛型T,依此類推,
四、組合異步編程與新API
接下來我們來說說組合異步編程與新API,
關于咱們這次的Modern Java函式式編程的技術分享中,還有最后一個部分,那就是Java8中新出來的一些特性,其中比較重要的就是兩個部分:
一是組合異步編程,這個在Java8中是新的CompletableFuture API;
二是新的時間日期API,
其實組合異步編程的知識更多適合在更高級的部分來講解,至于什么是更高階的部分,這里先暫時保密,今天只把組合異步編程的基礎部分說一下,
在開發的時候也會遇到這種場景,比如當有用戶請求時,服務器如果一直保持連接,等用戶輸入完資訊并且發送以后才去干別的事情,那么當有其他用戶請求時,肯定是無法及時處理的,那么這個新用戶的體驗就會非常差,這個就是傳統的阻塞式I/O干的事情,然后為了改善用戶體驗,服務器端只要知道有用戶請求就會立即回應請求新的請求,不管現在的用戶是不是正在輸入資料還是在干別的,
等用戶輸入完要發送了,再通知服務器說我輸完了,你發吧,這時候服務器再來發,就不用干等了,這樣一來,回應速度和處理效率就會大大提升,這就是非阻塞I/O,在Java中也叫NIO,這是一種典型的并行處理方式,就是客戶端輸入資料和服務端處理資料其實是并行著的,

所謂并發,就是指一瞬間,資源被大量請求占用,比如CPU內核和記憶體或者磁盤上的資料,這些請求雖然在宏觀上感覺是同時發生的,但在微觀上仍然是串行順序執行的,
然后并行就完全是同時進行的了,不存在微觀上順序的問題,不管宏觀微觀,都是同時發生,就比如當用戶在輸入資料時,服務器在處理其他任務,所以如果再看到有資料或其他人說什么提高并發訪問量,或者說提高并行度,您就應該理解這后面是什么意思了吧,
并發可以通過多執行緒去控制它,而并行可以通過回呼進行控制,這兩點單獨講都是很大的主題,內容都特別多,所以在這里不可能把它們講的特別清楚,只能點到為止,也算是給大家做個科普,
其實作在計算機硬體成本已經很低了,像我現在用的蘋果筆記本macbook pro就是i9八核、64G的記憶體,幾乎已經感覺不到什么并行和并發的區別了,
這是一方面,另一方面,其實并發是需要盡量控制或避免執行緒阻塞對資源造成的浪費,從這個角度來說,程式也是需要某種方法來控制回應的,比如,Java從JDK1.5開始就提供了Future類,
只不過這里面有些問題,舉個例子來說,在物聯網開發領域,如果我們要采集大樓內所有煙感設備的感應資料,那么就需要持續不斷地采集,如果采集一次就不能再次采集了,肯定是不能滿足要求的,如果用Future實作這個功能,會有嚴重的缺陷,因為Future是一個「一次性」的物件,
這里的一次性并不是說這個物件只能用一次,然后要銷毀重新生成,而是說這個物件里面的獲取結果的方法,只能呼叫一次,之后再去呼叫就拿不到任何資料了,

很明顯哈,我用Future第一查詢資料的時候,是能夠得到資料的,然后第二次問,就得不到資料了,但是用CompletableFuture就不一樣,它可以反復得到資料,大家可以細品其中的區別,
其實CompletableFuture對Future來說,不止是加了個Completable這么簡單,而是一些根本性的變革,至于為什么,咱們再下一期繼續來說,這里先賣個關子,
然后Java8里面也增加了一些新的時間日期API,主要是由于一些歷史遺留原因,導致Java中的Date和Calendar類對時間日期的處理,包括像一些時區、格式化、轉換、執行緒安全性考慮以及日期計算方面都比較麻煩,所以從Java8開始,JDK提供了一些新的工具來解決這些問題,例如像什么LocalDate、LocalTime、LocalDateTime、Instant、Duration以及Period等等,
除了時間日期,還有一個比較獨特的新API,就是Optional這個全新的類,這個類其實就是為了解決大量的防御性代碼問題的,什么是防御性代碼呢?
比如有個介面,用戶會輸入手機號、密碼、驗證碼等等資訊,OK,咱們為了保證這個介面不報錯,那肯定要先保證資料不會出問題對吧,行,那就每個資料都來個檢查好了,
比如手機號,要判斷有沒有輸入對吧,輸入了,是不是一個正確的手機號,長度、正則啊什么的,都要檢查一遍,加幾個判斷條件就行了,也就是if...else把,好,然后密碼什么的也這樣搞一遍,其實我們可以看到,真正實作業務功能的代碼可能就一兩行,但是這種防御性代碼,像什么if...else可能會有一大堆,完全把業務部分都淹沒了,這就是防御性代碼,
Optional雖然不解決業務資料是否合法,但是卻可以從技術上解決NPE問題,什么是NPE?搞Java的同學不可能不遇到NPE,不過有些童鞋剛開始可能會不習慣,沒關系,像Lambda一樣,慢慢適應就好了,
👇為筆者注
NPE是指編程語言中的空指標例外
五、總結
當然也可以不總結的,但整個直播講的對我來說新知識量大于舊知識量,即使把這個筆記梳理后,也沒有完全消化,但同時確確實實對一些java的歷史和概念性問題,如,Java是一個靜態型別的、強型別和解釋型的語言、Modern Java的含義和由來以及指向的問題,有了一些認識,再如Lambda,當初自學Python的時候就已經遇到這個概念了,但當時并沒有搞懂,一晃而過了,現在算是再次理解這個概念了,不過仍需要在代碼中多實踐幾次,去體會理解,并發和并行的概念,老師上課時曾經講過,這里做重溫,也算有一番新的體會,以及比較晚了,大概就說這樣多,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/279628.html
標籤:其他
