Java 回顧 ( Revisiting Java )

近在看一些工程代碼,於是看了看設計模式,看設計模式之前發現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,可以保證還原的時候能夠識別,從而正確還原出對象。但要注意有些修改會損害解序列化。

Java 回顧 ( Revisiting Java )


分享到:


相關文章: