歡迎光臨
每天分享高質量文章

Java 回顧 ( Revisiting Java )

(點擊上方公眾號,可快速關註)


來源: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技能

赞(0)

分享創造快樂