(點選上方公眾號,可快速關註)
來源:whatbeg ,
whatbeg.com/2017/10/23/revisitingjava.html
最近在看一些工程程式碼,於是看了看設計樣式,看設計樣式之前發現Java是先修知識,又重新補了一遍Java,溫故知新,獲得一些新的體會。
本文不打算作為“Java知識點詳細梳理”,“10分鐘學會Java”之類的文章,僅作為博主自己的一個回顧,涉及的內容也無定法。
Java應該是目前用的最多的程式語言,以前覺得Java老要點點點(呼叫方法),變數名也很長,C++/Python很少程式碼寫完的東西Java可能要寫很多行……
覺得挺麻煩的,不過Java風靡自有其風靡的理由,在面向物件語言中她是一個標桿,雖然繁瑣,但比較清晰,比較簡單。
拿變數型別來說,Java只有兩種變數型別,primitive主資料型別和取用資料型別。
Java中最關鍵的概念是面向物件,面向物件最關鍵的東西就是類和物件,所有的Java程式都定義在類中,你不能像python那樣,開啟.py檔案就開始寫東西,就可以執行了,也不像C++,定義一個main函式即可執行。在Java中即使main函式也要包括在類中。
為什麼面向物件是核心內容?它的好處在哪呢?可以說,OO(面向物件)無處不在,OO使得我們很方便的擴充套件功能,而不需要重覆寫很多程式碼!另外,OO的設計思想其實是抽象思維的一種體現,它改變了我們設計程式的方式,我們不再是根據程式需要什麼功能就開始從頭到尾實現什麼功能,我們更多考慮的是類和物件,程式包含幾種型別的物體?有什麼共同點?可以進行怎樣的抽象?用繼承還是介面?……
說說類和物件,類是物件的模板,類定義好“像我這樣的人應該有什麼狀態,特徵,能夠做到那些事”,而物件具體化了類,真正獲得了具體的狀態,具體的特徵,以及做某些事的方法。
我們說到,Java只有兩種變數,primitive主資料型別和取用資料型別。主資料型別包括我們所指的int,double,float等等,這些不是物件。而取用變數是一個到物件的取用,相當於一個遙控器,指向堆上的某個物件,透過此取用可以獲得物件,重新賦值此取用並不改變物件,只是取用指到了另一個物件上而已。沒有物件變數,只有指向物件的取用變數。
==: 比較primitive主資料型別是否相同,或兩個取用是否指向同一物件
話題回到面向物件,提到面向物件,不得不提其三大特性,這也是面試中經常會問到的,即封裝,繼承和多型。
-
封裝(encapsulation),即隱藏物件的屬性和實現細節,僅對外公開介面,控制在程式中屬性的讀和修改的訪問級別;
-
多型(polymorphism),一句話,“介面的多種不同的實現方式即為多型”,但是這個不太好理解,甚至我覺得它不夠準確,因為光說介面是不是有點不夠?換一種說法,多型即允許將子類物件的取用賦值給父類物件的取用,賦值之後,父物件就可以根據當前賦值給它的子物件的特性以不同的方式運作。因為:編譯器根據取用型別來判斷可以呼叫哪些方法,而不是根據確實的型別。
-
繼承(inheritance) 是指一個物件直接使用另一物件的屬性和方法,很簡單,父類是球,子類是足球,那麼足球可以直接使用“滾動”這個方法,如果需要特殊的“滾”,那子類自己實現就好了。
之所以繼承放在最後講,是因為我們關於繼承有更多要說的。
【繼承方法呼叫時的最近原則】呼叫物件取用的方法時,會呼叫到與該物件型別最接近的方法,就是說如果子類實現了某繼承的方法,那就呼叫子類的,如果沒有實現,那就往上找最近的實現的類的方法。
繼承的IS-A測試,即“足球”IS-A“球”,總得滿足這樣的關係才好說繼承,就像你不太好意思繼承隔壁王叔叔財產。
繼承的意義何在?這是顯然的,首先避免了大量重覆的程式程式碼,其次可以定義出一組共同的協議,所有繼承者都需要滿足這個協議,你知道,在很多時候大家遵守一些共同的規則是很重要的。
繼承的一些使用建議:
1) 當某個類會比其父類更具有特定意義時使用繼承
2)行為程式需要被多個相同基本型別的類共享時,考慮使用繼承
3)整合並不一定是達成重用行為程式的最佳方式,具體可參見設計樣式
4)繼承結構並不匹配兩者的關係,不要用繼承
5)不能透過IS-A測試一定不要用繼承
如果最高的父類不能抽象出一些對所有族類都使用的方法,或者不太好初始化,比如你不好新建一個“球”物件,它是啥球呢?地球還是足球?這樣一些情況我們可以定義抽象類,它不能被初始化,只能被繼承。。抽象類中可以定義抽象方法,抽象方法只存在於抽象類中,一個類只要有一個抽象方法,那他必是抽象類。
有時候,你會想要繼承多個父類,以便使用更多的已有程式碼,但是不幸的是Java並不支援多重繼承,要多重繼承請關閉本文,搜尋”C++”關鍵詞謝謝。
為啥不支援多重繼承呢?因為存在多重繼承(繼承多個類)的“致命方塊”問題,即如果兩個父類繼承自同一個祖父類,都實現了某個方法,那麼子類(如果沒有實現該方法)該呼叫那個版本?
解決“致命方塊”問題?介面!
介面是100%純抽象類,每個方法都是抽象的,必須被實現。
如果想要定義出類可以扮演的角色,使用介面。
接下來從生物學的角度談談物件?什麼是生物學角度??即生老病死~
物件生存在堆上(可以理解為垃圾堆,隨時可能有人來回收…),取用變數或區域性變數生存在棧上。
一旦一個物件,它的取用沒有了或者離棄了它,那麼他就可以等待被回收了。Java有一套垃圾回收機制(GC)保證物件的回收來騰出堆空間,有時候,GC又常常被人詬病,在大資料應用中常常面臨這大量的shuffle,大量的物件,有時候需要花費大量的時間來做GC,體驗不佳。
總的來說,物件的出生靠呼叫建構式,生存在堆上,一旦沒了取用,就向生命的終點走去,直到GC(黑白無常)帶走了它。。
新建物件時,父類的建構式先於子類被呼叫,以此類推,Object的建構式先被執行,然後往下推,直到標的物件型別
(先有父母才有你)
只有當完全沒寫建構式時,Java才會自動幫你寫一個無參建構式。
super()呼叫父類的建構式,this是對物件本身的取用
談談實體變數,實體變數即物件的成員變數。
JAVA的實體變數具有如下特點:
1)實體變數宣告在一個類中,但在方法、構造方法和陳述句塊之外;
2)當一個物件被實體化之後,每個實體變數的值就跟著確定;
3)實體變數在物件建立的時候建立,在物件被銷毀的時候銷毀;
4)實體變數的值應該至少被一個方法、構造方法或者陳述句塊取用,使得外部能夠透過這些方式獲取實體變數資訊;
5)實體變數可以宣告在使用前或者使用後;
6)訪問修飾符可以修飾實體變數;
7)實體變數對於類中的方法、構造方法或者陳述句塊是可見的。一般情況下應該把實體變數設為私有。透過使用訪問修飾符可以使實體變數對子類可見;
8)實體變數具有預設值。數值型變數的預設值是0,布林型變數的預設值是false,取用型別變數的預設值是null。變數的值可以在宣告時指定,也可以在構造方法中指定;實體變數可以直接透過變數名訪問。但在靜態方法以及其他類中,就應該使用完全限定名:ObejectReference.VariableName。
你可能想問,如果Java中只有物件和primitive主資料型別,那麼我想定義全域性變數或者常量怎麼辦?比如PI=3.141592653589..(後面忘了)
這時候,靜態變數可以幫你。靜態變數定義在類中,它屬於類,不屬於任何物件,但物件可以獲得它。
類的靜態變數由(該類的)所有物件所共享。
靜態方法透過類名呼叫,靜態變數透過類名存取 。
如果類只有靜態方法,則可以將建構式標記為private的,以免被初始化
Java常量 = final static 的變數
final意味著不能被改變,static意味著是靜態變數。
插一句字串的格式化:
String.format(格式化說明)
格式化說明包括5部分,%和type是必要的
%[argument number] [flags] [width] [.precision] type
如: %,6.1f 為6位逗號分隔,1位小數的浮點數
談談異常吧,誰能保證自己的程式不出問題呢?與其系統執行的時候報一大堆亂七八糟的錯誤trace,早早地預見並處理一下,以自己的方式處理或者列印它,總要漂亮些吧?甚至可以在抓到異常後,給出“沒關係,一個小錯誤,已經報告給開發者~”這樣溫和的陳述句,是不是顯得b格很高?……
異常中要註意的點有:
-
可能會丟擲異常的方法必須宣告成throws Exception
-
catch捕獲多個異常時,要從小排到大,因為大異常後面的小異常根本沒有被catch的機會
-
在方法後加上throws xxException,沒有try/catch塊,表示可能會丟擲異常,自己並不處理,需要呼叫方自己處理異常
-
所以>>>要麼處理,要麼宣告(異常)
序列化物件:有時候需要儲存一下物件,以便於恢復,被呼叫,而不用重新生成,因為生成過程可能很麻煩。
要序列化的話,物件必須可序列化,且物件中實體變數所取用的物件甚至物件取用的物件…都必須可以序列化,簡而言之,整個物件版圖都必須可以序列化
如果某實體變數不需要或者不能被序列化,那可以把它標記為transient(瞬時)的。
解序列化時,transient變數會恢覆成null物件取用或者0,false等primitive預設值
靜態變數不會被序列化,物件被還原時,靜態變數會維持類中原本的樣子。因為所有物件共用一份靜態變數。
讀取物件的順序必須與寫入的順序相同。
序列化物件:
FileOutputStream fileStream = new FileOutputStream(“MySer.ser”)
ObjectOutputStream os = new ObjectOutputStream(fileStream)
os.writeObject(obj)
os.close()
或者不序列化,而是將資訊寫入文字檔案:
BufferedWriter writer = new BufferedWriter(new FileWriter(file)) // file is a File object
writer.write(…)
可以把File想象成檔案的路徑,代表磁碟上的某個檔案,但並不是檔案內容
BufferedWriter writer = new BufferedWriter(new FileWriter(file)) // file is a File object
這句程式碼形成如下連結:
字串 –> BufferedWriter –> FileWriter –> File
物件序列化以後,類繼續演進,這時會出現無法還原的情況。透過將serialVersionUID放在class中,讓類在演化過程中維持同樣的ID,可以保證還原的時候能夠識別,從而正確還原出物件。但要註意有些修改會損害解序列化。
Reference
*《Head First Java》
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,提升Java技能