夯實Java基礎系列1:Java物件導向三大特性(基礎篇)

Java技術江湖發表於2019-10-29

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫裡檢視

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點下Star、Fork、Watch三連哈,感謝你的支援。

文章首發於我的個人部落格:

www.how2playlife.com

本文是微信公眾號【Java技術江湖】的《夯實Java基礎系列博文》其中一篇,本文部分內容來源於網路,為了把本文主題講得清晰透徹,也整合了很多我認為不錯的技術部落格內容,引用其中了一些比較好的部落格文章,如有侵權,請聯絡作者。

該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接著瞭解每個Java知識點背後的實現原理,更完整地瞭解整個Java技術體系,形成自己的知識框架。為了更好地總結和檢驗你的學習成果,本系列文章也會提供每個知識點對應的面試題以及參考答案。

如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關注公眾號【Java技術江湖】聯絡作者,歡迎你參與本系列博文的創作和修訂。

@[toc]

Java物件導向三大特性(基礎篇)

物件導向簡稱 OO(Object Oriented),20 世紀 80 年代以後,有了物件導向分析(OOA)、 物件導向設計(OOD)、物件導向程式設計(OOP)等新的系統開發方式模型的研究。

對語言來說,一切皆是物件。把現實世界中的物件抽象地體現在程式設計世界中,一個物件代表了某個具體的操作。一個個物件最終組成了完整的程式設計,這些物件可以是獨立存在的,也可以是從別的物件繼承過來的。物件之間通過相互作用傳遞資訊,實現程式開發。

物件的概念

Java 是物件導向的程式語言,物件就是物件導向程式設計的核心。所謂物件就是真實世界中的實體,物件與實體是一一對應的,也就是說現實世界中每一個實體都是一個物件,它是一種具體的概念。物件有以下特點:

  • 物件具有屬性和行為。
  • 物件具有變化的狀態。
  • 物件具有唯一性。
  • 物件都是某個類別的例項。
  • 一切皆為物件,真實世界中的所有事物都可以視為物件。

物件導向和麵向過程的區別

  • 程式導向:
    一種較早的程式設計思想,顧名思義就是該思想是站著過程的角度思考問題,強調的就是功能行為,功能的執行過程,即先後順序,而每一個功能我們都使用函式(類似於方法)把這些步驟一步一步實現。使用的時候依次呼叫函式就可以了。

  • 程式導向的設計:
    最小的程式單元是函式,每個函式負責完成某一個功能,用於接受輸入資料,函式對輸入資料進行處理,然後輸出結果資料,整個軟體系統由一個個的函式組成,其中作為程式入口的函式稱之為主函式,主函式依次呼叫其他函式,普通函式之間可以相互呼叫,從而實現整個系統功能。
      程式導向最大的問題在於隨著系統的膨脹,程式導向將無法應付,最終導致系統的崩潰。為了解決這一種軟體危機,我們提出物件導向思想。

  • 程式導向的缺陷:
    是採用指定而下的設計模式,在設計階段就需要考慮每一個模組應該分解成哪些子模組,每一個子模組又細分為更小的子模組,如此類推,直到將模組細化為一個個函式。

  • 存在的問題

    ​ 設計不夠直觀,與人類的思維習慣不一致
    系統軟體適應新差,可擴充性差,維護性低

  • 物件導向:

    ​ 一種基於程式導向的新程式設計思想,顧名思義就是該思想是站在物件的角度思考問題,我們把多個功能合理放到不同物件裡,強調的是具備某些功能的物件。

      具備某種功能的實體,稱為物件。物件導向最小的程式單元是:類。物件導向更加符合常規的思維方式,穩定性好,可重用性強,易於開發大型軟體產品,有良好的可維護性。

  在軟體工程上,物件導向可以使工程更加模組化,實現更低的耦合和更高的內聚。

物件導向的三大核心特性簡介

物件導向開發模式更有利於人們開拓思維,在具體的開發過程中便於程式的劃分,方便程式設計師分工合作,提高開發效率。

該開發模式之所以使程式設計更加完善和強大,主要是因為物件導向具有繼承、封裝和多型 3 個核心特性。

1、繼承的概念

繼承是java物件導向程式設計技術的一塊基石,因為它允許建立分等級層次的類。

繼承就是子類繼承父類的特徵和行為,使得子類物件(例項)具有父類的例項域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為。

[外鏈圖片轉存失敗(img-LkGM7vxm-1569146238577)( https://www.runoob.com/wp-content/uploads/2013/12/14B0951E-FC75-47A3-B611-4E1883887339.jpg)]

兔子和羊屬於食草動物類,獅子和豹屬於食肉動物類。

食草動物和食肉動物又是屬於動物類。

所以繼承需要符合的關係是:is-a,父類更通用,子類更具體。

雖然食草動物和食肉動物都是屬於動物,但是兩者的屬性和行為上有差別,所以子類會具有父類的一般特性也會具有自身的特性。

2、Java 多型


多型是同一個行為具有多個不同表現形式或形態的能力。

多型就是同一個介面,使用不同的例項而執行不同操作,如圖所示:

[外鏈圖片轉存失敗(img-zA9F3Rja-1569146238578)( https://www.runoob.com/wp-content/uploads/2013/12/dt-java.png)]

多型性是物件多種表現形式的體現。

現實中,比如我們按下 F1 鍵這個動作:

  • 如果當前在 Flash 介面下彈出的就是 AS 3 的幫助文件;
  • 如果當前在 Word 下彈出的就是 Word 幫助;
  • 在 Windows 下彈出的就是 Windows 幫助和支援。

同一個事件發生在不同的物件上會產生不同的結果。

3、Java 封裝


在物件導向程式設計方法中,封裝(英語:Encapsulation)是指一種將抽象性函式介面的實現細節部份包裝、隱藏起來的方法。

封裝可以被認為是一個保護屏障,防止該類的程式碼和資料被外部類定義的程式碼隨機訪問。

要訪問該類的程式碼和資料,必須通過嚴格的介面控制。

封裝最主要的功能在於我們能修改自己的實現程式碼,而不用修改那些呼叫我們程式碼的程式片段。

適當的封裝可以讓程式碼更容易理解與維護,也加強了程式碼的安全性。

物件導向程式設計三大特性詳解

物件導向程式設計是利用 類和物件程式設計的一種思想。萬物可歸類,類是對於世界事物的高度抽象 ,不同的事物之間有不同的關係 ,一個類自身與外界的封裝關係,一個父類和子類的繼承關係, 一個類和多個類的多型關係。萬物皆物件,物件是具體的世界事物,物件導向的三大特徵封裝,繼承,多型,封裝,封裝說明一個類行為和屬性與其他類的關係,低耦合,高內聚;繼承是父類和子類的關係,多型說的是類與類的關係。

一、繼承

1、繼承的概念

如同生活中的子女繼承父母擁有的所有財產,程式中的繼承性是指子類擁有父類 資料結構的方法和機制,這是類之間的一種關係;繼承只能是單繼承。

例如定義一個語文老師類和數學老師類,如果不採用繼承方式,那麼兩個類中需要定義的屬性和方法如圖 1 所示。

[外鏈圖片轉存失敗(img-1wmYKDrO-1569146238578)( http://c.biancheng.net/uploads/allimg/181017/3-1Q01G40613629.jpg)]
圖1 語文老師類和數學老師類中的屬性和方法

從圖 1 能夠看出,語文老師類和數學老師類中的許多屬性和方法相同,這些相同的屬性和方法可以提取出來放在一個父類中,這個父類用於被語文老師類和數學老師類繼承。當然父類還可以繼承別的類,如圖 2 所示。

[外鏈圖片轉存失敗(img-gJ5pmYDN-1569146238578)( http://c.biancheng.net/uploads/allimg/181017/3-1Q01G40AR23.jpg)]
圖2 父類繼承示例圖

總結圖 2 的繼承關係,可以用概括的樹形關係來表示,如圖 3 所示。

[外鏈圖片轉存失敗(img-JsPYr5ao-1569146238579)( http://c.biancheng.net/uploads/allimg/181017/3-1Q01G40RT47.jpg)]
圖3 類繼承示例圖

從圖 3 中可以看出,學校主要人員是一個大的類別,老師和學生是學校主要人員的兩個子類,而老師又可以分為語文老師和數學老師兩個子類,學生也可以分為班長和組長兩個子類。

使用這種層次形的分類方式,是為了將多個類的通用屬性和方法提取出來,放在它們的父類中,然後只需要在子類中各自定義自己獨有的屬性和方法,並以繼承的形式在父類中獲取它們的通用屬性和方法即可。

 繼承是類與類的一種關係,是一種“is a”的關係。比如“狗”繼承“動物”,這裡動物類是狗類的父類或者基類,狗類是動物類的子類或者派生類。如下圖所示:

[外鏈圖片轉存失敗(img-TlRQMo8J-1569146238579)( https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701123011243-2128400556.png)]

注:java中的繼承是 單繼承,即 一個類只有一個父類。

補充:Java中的繼承只能單繼承,但是可以通過內部類繼承其他類來實現多繼承。

public class Son extends Father{
public void go () {
System.out.println("son go");
}
public void eat () {
System.out.println("son eat");
}
public void sleep() {
System.out.println("zzzzzz");
}
public void cook() {
//匿名內部類實現的多繼承
new Mother().cook();
//內部類繼承第二個父類來實現多繼承
Mom mom = new Mom();
mom.cook();
}
private class Mom extends Mother {
@Override
public void cook() {
System.out.println("mom cook");
}
}
}

2、繼承的好處

 子類擁有父類的所有屬性和方法(除了private修飾的屬性不能擁有)從而實現了實現程式碼的複用; 

3、語法規則

[外鏈圖片轉存失敗(img-RgZ9dRoy-1569146238579)( https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701123421961-647167245.png)]


A、方法的重寫

 子類如果對繼承的父類的方法不滿意(不適合),可以自己編寫繼承的方法,這種方式就稱為 方法的重寫。當呼叫方法時會優先呼叫子類的方法。

  重寫要注意:

  a、返回值型別

  b、方法名

  c、引數型別及個數

 都要與父類繼承的方法相同,才叫方法的重寫。

  過載和重寫的區別:

  方法過載:在同一個類中處理不同資料的多個相同方法名的多型手段。

  方法重寫:相對繼承而言,子類中對父類已經存在的方法進行區別化的修改。


B、繼承的初始化順序

  1、初始化父類再初始化子類

  2、先執行初始化物件中屬性,再執行構造方法中的初始化。

 基於上面兩點,我們就知道例項化一個子類,java程式的執行順序是:

  父類物件屬性初始化——>父類物件構造方法——>子類物件屬性初始化—->子類物件構造方法   

 下面有個形象的圖:

[外鏈圖片轉存失敗(img-i03JI2xe-1569146238580)( https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701144019071-1063399032.png)]


C、final關鍵字

 使用final關鍵字做標識有“最終的”含義。

  1. final 修飾類,則該類 不允許被繼承。
  2. final 修飾方法,則該方法不允許被 覆蓋(重寫)
  3. final 修飾屬性,則該類的該屬性不會進行隱式的初始化,所以 該final 屬性的 初始化屬性必須有值,或在 構造方法中賦值(但只能選其一,且必須選其一,因為沒有預設值!),且初始化之後就不能改了, 只能賦值一次
  4. final 修飾變數,則該變數的值只能賦一次值,在宣告變數的時候才能賦值,即變為 常量

D、super關鍵字

 在物件的內部使用,可以代表父類物件。

  1、訪問父類的屬性:super.age

   2、訪問父類的方法:super.eat()

 super的應用:

 首先我們知道子類的構造的過程當中必須呼叫父類的構造方法。其實這個過程已經隱式地使用了我們的super關鍵字。

 這是因為如果子類的構造方法中沒有顯示呼叫父類的構造方法,則系統預設呼叫父類無參的構造方法。

 那麼如果自己用super關鍵字在子類裡呼叫父類的構造方法,則必須在子類的構造方法中的 第一行

  要注意的是:如果子類構造方法中既沒有顯示呼叫父類的構造方法,而父類沒有無參的構造方法,則編譯出錯。

(補充說明,雖然沒有顯示宣告父類的無參的構造方法,系統會自動預設生成一個無參構造方法,但是,如果你宣告瞭一個有參的構造方法,而沒有宣告無參的構造方法,這時系統不會動預設生成一個無參構造方法,此時稱為父類有沒有無參的構造方法。)


二、封裝

1、封裝的概念

封裝是將程式碼及其處理的資料繫結在一起的一種程式設計機制,該機制保證了程式和資料都不受外部干擾且不被誤用。封裝的目的在於保護資訊,使用它的主要優點如下。

  • 保護類中的資訊,它可以阻止在外部定義的程式碼隨意訪問內部程式碼和資料。
  • 隱藏細節資訊,一些不需要程式設計師修改和使用的資訊,比如取款機中的鍵盤,使用者只需要知道按哪個鍵實現什麼操作就可以,至於它內部是如何執行的,使用者不需要知道。
  • 有助於建立各個系統之間的鬆耦合關係,提高系統的獨立性。當一個系統的實現方式發生變化時,只要它的介面不變,就不會影響其他系統的使用。例如 U 盤,不管裡面的儲存方式怎麼改變,只要 U 盤上的 USB 介面不變,就不會影響使用者的正常操作。
  • 提高軟體的複用率,降低成本。每個系統都是一個相對獨立的整體,可以在不同的環境中得到使用。例如,一個 U 盤可以在多臺電腦上使用。

Java 語言的基本封裝單位是類。由於類的用途是封裝複雜性,所以類的內部有隱藏實現複雜性的機制。Java 提供了私有和公有的訪問模式,類的公有介面代表外部的使用者應該知道或可以知道的每件東西,私有的方法資料只能通過該類的成員程式碼來訪問,這就可以確保不會發生不希望的事情。

2、封裝的優點

在物件導向程式設計方法中,封裝(英語:Encapsulation)是指一種將抽象性函式介面的實現細節部份包裝、隱藏起來的方法。

封裝可以被認為是一個保護屏障,防止該類的程式碼和資料被外部類定義的程式碼隨機訪問。

要訪問該類的程式碼和資料,必須通過嚴格的介面控制。

封裝最主要的功能在於我們能修改自己的實現程式碼,而不用修改那些呼叫我們程式碼的程式片段。

適當的封裝可以讓程式碼更容易理解與維護,也加強了程式碼的安全性。

封裝的優點

  1. 良好的封裝能夠減少耦合。
  2. 類內部的結構可以自由修改。
  3. 可以對成員變數進行更精確的控制。
  4. 隱藏資訊,實現細節。

Java 封裝,說白了就是將一大坨公共通用的實現邏輯玩意,裝到一個盒子裡(class),出入口都在這個盒子上。你要用就將這個盒子拿來用,連線出入口,就能用了,不用就可以直接扔,對你程式碼沒什麼影響。

對程式設計師來說,使用封裝的目的:

  1. 偷懶,辛苦一次,後面都能少敲很多程式碼,增強了程式碼得複用性
  2. 簡化程式碼,看起來更容易懂
  3. 隱藏核心實現邏輯程式碼,簡化外部邏輯,並且不讓其他人修改,jar 都這麼幹
  4. 一對一,一個功能就只為這個功能服務;避免頭髮繩子一塊用,導致最後一團糟

3、封裝的實現步驟

[外鏈圖片轉存失敗(img-kED45iQa-1569146238580)( https://images2015.cnblogs.com/blog/1189312/201706/1189312-20170630170717493-357592353.png)]

    需要注意:對封裝的屬性不一定要通過get/set方法,其他方法也可以對封裝的屬性進行操作。當然最好使用get/set方法,比較標準。


A、訪問修飾符

[外鏈圖片轉存失敗(img-mjPpp7qr-1569146238580)( https://images2015.cnblogs.com/blog/1189312/201706/1189312-20170630174919274-1857293801.png)]

    從表格可以看出 從上到下封裝性越來越差

B、this關鍵字

 1.this關鍵字 代表當前物件

  this.屬性 操作當前物件的屬性

  this.方法 呼叫當前物件的方法。

 2.封裝物件的屬性的時候,經常會使用this關鍵字。

 3.當getter和setter函式引數名和成員函式名重合的時候, 可以使用this**區別。如:**

[外鏈圖片轉存失敗(img-812wFgw3-1569146238581)( https://images2015.cnblogs.com/blog/1189312/201706/1189312-20170630180217524-833886832.png)]

C、Java 中的內部類

 內部類( Inner Class )就是定義在另外一個類 裡面的類。與之對應,包含內部類的類被稱為外部類。

 那麼問題來了:那為什麼要將一個類定義在另一個類裡面呢?清清爽爽的獨立的一個類多好啊!!

 答:內部類的主要作用如下:

  1. 內部類提供了 更好的封裝,可以把內部類 隱藏在外部類之內, 不允許同一個包中的其他類訪問該類。
  2. 內部類的方法可以 直接訪問外部類的所有資料,包括 私有的資料
  3. 內部類所實現的功能使用外部類同樣可以實現,只是有時使用內部類更方便。

  內部類可分為以下幾種:

  • 成員內部類
  • 靜態內部類
  • 方法內部類
  • 匿名內部類  

三、多型

1、多型的概念

物件導向的多型性,即“一個介面,多個方法”。多型性體現在父類中定義的屬性和方法被子類繼承後,可以具有不同的屬性或表現方式。多型性允許一個介面被多個同類使用,彌補了單繼承的不足。多型概念可以用樹形關係來表示,如圖 4 所示。

[外鏈圖片轉存失敗(img-2rZe5SBZ-1569146238582)( http://c.biancheng.net/uploads/allimg/181017/3-1Q01G4095bW.jpg)]

圖4 多型示例圖

從圖 4 中可以看出,老師類中的許多屬性和方法可以被語文老師類和數學老師類同時使用,這樣也不易出錯。

2、多型的好處

可替換性(substitutability)。多型對已存在程式碼具有可替換性。例如,多型對圓Circle類工作,對其他任何圓形幾何體,如圓環,也同樣工作。

可擴充性(extensibility)。多型對程式碼具有可擴充性。增加新的子類不影響已存在類的多型性、繼承性,以及其他特性的執行和操作。實際上新加子類更容易獲得多型功能。例如,在實現了圓錐、半圓錐以及半球體的多型基礎上,很容易增添球體類的多型性。

介面性(interface-ability)。多型是超類通過方法簽名,向子類提供了一個共同介面,由子類來完善或者覆蓋它而實現的。

靈活性(flexibility)。它在應用中體現了靈活多樣的操作,提高了使用效率。

簡化性(simplicity)。多型簡化對應用軟體的程式碼編寫和修改過程,尤其在處理大量物件的運算和操作時,這個特點尤為突出和重要。

子代父類例項化,然後就相當於一個父親有很多兒子,送快遞的給這個父親的兒子送東西,他只需要送到父親的家就行了,至於具體是那個兒子的,父親還會分不清自己的兒子麼,所以你就不用操心了。

使用多型是一種好習慣
多型方式宣告是一種好的習慣。當我們建立的類,使用時,只用到它的超類或介面定義的方法時,我們可以將其索引宣告為它的超類或介面型別。

它的好處是,如果某天我們對這個介面方法的實現方式變了,對這個介面又有一個新的實現類,我們的程式也需要使用最新的實現方式,此時只要將物件實現修改一下,索引無需變化。

比如Map< String,String> map = new HashMap < String,String>();

想換成HashTable實現,可以Map< String,String> map = new HashTable < String,String>();

比如寫一個方法,引數要求傳遞List型別,你就可以用List list = new ArrayList()中的list傳遞,但是你寫成ArrayList list = new ArrayList()是傳遞不進去的。儘管方法處理時都一樣。另外,方法還可以根據你傳遞的不同list(ArrayList或者LinkList)進行不同處理。

3、Java中的多型

java裡的多型主要表現在兩個方面:

A、引用多型  

  父類的引用可以指向本類的物件;

  父類的引用可以指向子類的物件;

  這兩句話是什麼意思呢,讓我們用程式碼來體驗一下,首先我們建立一個父類Animal和一個子類Dog,在主函式裡如下所示:

[外鏈圖片轉存失敗(img-YUHxvSiU-1569146238582)( https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701155536086-1897896282.png)]

  注意:我們不能使用一個子類的引用來指向父類的物件,如:[外鏈圖片轉存失敗(img-ohyxfag1-1569146238583)( https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701155839586-923083573.png)]。

  這裡我們必須深刻理解引用多型的意義,才能更好記憶這種多型的特性。為什麼子類的引用不能用來指向父類的物件呢?我在這裡通俗給大家講解一下:就以上面的例子來說,我們能說“狗是一種動物”,但是不能說“動物是一種狗”,狗和動物是父類和子類的繼承關係,它們的從屬是不能顛倒的。當父類的引用指向子類的物件時,該物件將只是看成一種特殊的父類(裡面有重寫的方法和屬性),反之,一個子類的引用來指向父類的物件是不可行的!!

B、方法多型

  根據上述建立的兩個物件:本類物件和子類物件,同樣都是父類的引用,當我們指向不同的物件時,它們呼叫的方法也是多型的。

  建立本類物件時,呼叫的方法為本類方法;

  建立子類物件時,呼叫的方法為子類重寫的方法或者繼承的方法;

  使用多型的時候要注意: 如果我們在子類中編寫一個獨有的方法(沒有繼承父類的方法),此時就不能通過父類的引用建立的子類物件來呼叫該方法!!!

   注意: 繼承是多型的基礎。


C、引用型別轉換

 瞭解了多型的含義後,我們在日常使用多型的特性時經常需要進行引用型別轉換。

 引用型別轉換:

  1.向上型別轉換(隱式/自動型別轉換),是小型別轉換到大型別

  就以上述的父類Animal和一個子類Dog來說明,當父類的引用可以指向子類的物件時,就是 向上型別轉換。如:

[外鏈圖片轉存失敗(img-cA31LZrI-1569146238584)( https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701162630508-961507659.png)]

  2. 向下型別轉換(強制型別轉換),是大型別轉換到小型別(有風險,可能出現資料溢位)。

  將上述程式碼再加上一行,我們再次將父類轉換為子類引用,那麼會出現錯誤,編譯器不允許我們直接這麼做 雖然我們知道這個父類引用指向的就是子類物件,但是編譯器認為這種轉換是存在風險的 如:

[外鏈圖片轉存失敗(img-DPI8b2WP-1569146238585)( https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701162926477-3857975.png)]

  那麼我們該怎麼解決這個問題呢,我們可以在animal前加上(Dog)來強制型別轉換。如:[外鏈圖片轉存失敗(img-e9ds8jO5-1569146238585)( https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701163408383-2003626729.png)]

  但是如果父類引用沒有指向 該子類的物件,則不能向下型別轉換,雖然編譯器不會報錯,但是執行的時候程式會出錯,如:[外鏈圖片轉存失敗(img-3k7k0NZg-1569146238586)( https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701164229899-1055190774.png)]

  其實這就是上面所說的子類的引用指向父類的物件,而強制轉換型別也不能轉換!!

  還有一種情況是父類的引用指向 其他子類的物件,則不能通過強制轉為 該子類的物件。如:

    [外鏈圖片轉存失敗(img-tl2WhRgR-1569146238586)( https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701165133289-717439360.png)]

  這是因為我們在編譯的時候進行了強制型別轉換,編譯時的型別是我們強制轉換的型別,所以編譯器不會報錯,而當我們執行的時候,程式給animal開闢的是Dog型別的記憶體空間,這與Cat型別記憶體空間不匹配,所以無法正常轉換。這兩種情況出錯的本質是一樣的,所以我們在使用強制型別轉換的時候要特別注意這兩種錯誤!!下面有個更安全的方式來實現向下型別轉換。。。。

  3. instanceof運算子,來解決引用物件的型別,避免型別轉換的安全性問題。

   instanceof是Java的一個二元操作符,和==,>,<是同一類東東。由於它是由字母組成的,所以也是Java的保留關鍵字。 它的作用是測試它左邊的物件是否是它右邊的類的例項,返回boolean型別的資料。

  我們來使用instanceof運算子來規避上面的錯誤,程式碼修改如下:

  [外鏈圖片轉存失敗(img-QxQpVWZj-1569146238586)( https://images2015.cnblogs.com/blog/1189312/201707/1189312-20170701165626571-501228254.png)]

  利用if語句和instanceof運算子來判斷兩個物件的型別是否一致。

   補充說明:在比較一個物件是否和另一個物件屬於同一個類例項的時候,我們通常可以採用instanceof和getClass兩種方法通過兩者是否相等來判斷,但是兩者在判斷上面是有差別的。Instanceof進行型別檢查規則是: 你屬於該類嗎?或者你屬於該類的派生類嗎?而通過getClass獲得型別資訊採用==來進行檢查是否相等的操作是 嚴格的判斷, 不會存在繼承方面的考慮

   總結:在寫程式的時候,如果要進行型別轉換,我們最好使用instanceof運算子來判斷它左邊的物件是否是它右邊的類的例項,再進行強制轉換。


D、重寫和過載 

多型一般可以分為兩種,一個是重寫override,一個是過載overload。

重寫是由於繼承關係中的子類有一個和父類同名同引數的方法,會覆蓋掉父類的方法。過載是因為一個同名方法可以傳入多個引數組合。
注意,同名方法如果引數相同,即使返回值不同也是不能同時存在的,編譯會出錯。
從jvm實現的角度來看,重寫又叫執行時多型,編譯時看不出子類呼叫的是哪個方法,但是執行時運算元棧會先根據子類的引用去子類的類資訊中查詢方法,找不到的話再到父類的類資訊中查詢方法。
而過載則是編譯時多型,因為編譯期就可以確定傳入的引數組合,決定呼叫的具體方法是哪一個了。

1. 向上轉型和向下轉型

public static void main(String[] args) {
    Son son = new Son();
    //首先先明確一點,轉型指的是左側引用的改變。
    //father引用型別是Father,指向Son例項,就是向上轉型,既可以使用子類的方法,也可以使用父類的方法。
    //向上轉型,此時執行father的方法
    Father father = son;
    father.smoke();
    //不能使用子類獨有的方法。
    // father.play();編譯會報錯
    father.drive();
    //Son型別的引用指向Father的例項,所以是向下轉型,不能使用子類非重寫的方法,可以使用父類的方法。
    //向下轉型,此時執行了son的方法
    Son son1 = (Son) father;
    //轉型後就是一個正常的Son例項
    son1.play();
    son1.drive();
    son1.smoke();
    //因為向下轉型之前必須先經歷向上轉型。
    //在向下轉型過程中,分為兩種情況:
    //情況一:如果父類引用的物件如果引用的是指向的子類物件,
    //那麼在向下轉型的過程中是安全的。也就是編譯是不會出錯誤的。
    //因為執行期Son例項確實有這些方法
    Father f1 = new Son();
    Son s1 = (Son) f1;
    s1.smoke();
    s1.drive();
    s1.play();
    //情況二:如果父類引用的物件是父類本身,那麼在向下轉型的過程中是不安全的,編譯不會出錯,
    //但是執行時會出現java.lang.ClassCastException錯誤。它可以使用instanceof來避免出錯此類錯誤。
    //因為執行期Father例項並沒有這些方法。
        Father f2 = new Father();
        Son s2 = (Son) f2;
        s2.drive();
        s2.smoke();
        s2.play();
    //向下轉型和向上轉型的應用,有些人覺得這個操作沒意義,何必先向上轉型再向下轉型呢,不是多此一舉麼。其實可以用於方法引數中的型別聚合,然後具體操作再進行分解。
    //比如add方法用List引用型別作為引數傳入,傳入具體類時經歷了向下轉型
    add(new LinkedList());
    add(new ArrayList());
    //總結
    //向上轉型和向下轉型都是針對引用的轉型,是編譯期進行的轉型,根據引用型別來判斷使用哪個方法
    //並且在傳入方法時會自動進行轉型(有需要的話)。執行期將引用指向例項,如果是不安全的轉型則會報錯。
    //若安全則繼續執行方法。
}
public static void add(List list) {
    System.out.println(list);
    //在操作具體集合時又經歷了向上轉型
//        ArrayList arr = (ArrayList) list;
//        LinkedList link = (LinkedList) list;
}

總結:
向上轉型和向下轉型都是針對引用的轉型,是編譯期進行的轉型,根據引用型別來判斷使用哪個方法。並且在傳入方法時會自動進行轉型(有需要的話)。執行期將引用指向例項,如果是不安全的轉型則會報錯,若安全則繼續執行方法。

2. 編譯期的靜態分派

其實就是根據引用型別來呼叫對應方法。

public static void main(String[] args) {
    Father father  = new Son();
    靜態分派 a= new 靜態分派();
    //編譯期確定引用型別為Father。
    //所以呼叫的是第一個方法。
    a.play(father);
    //向下轉型後,引用型別為Son,此時呼叫第二個方法。
    //所以,編譯期只確定了引用,執行期再進行例項化。
    a.play((Son)father);
    //當沒有Son引用型別的方法時,會自動向上轉型呼叫第一個方法。
    a.smoke(father);
    //
}
public void smoke(Father father) {
    System.out.println("father smoke");
}
public void play (Father father) {
    System.out.println("father");
    //father.drive();
}
public void play (Son son) {
    System.out.println("son");
    //son.drive();
}

3. 方法過載優先順序匹配

public static void main(String[] args) {
    方法過載優先順序匹配 a = new 方法過載優先順序匹配();
    //普通的過載一般就是同名方法不同引數。
    //這裡我們來討論當同名方法只有一個引數時的情況。
    //此時會呼叫char引數的方法。
    //當沒有char引數的方法。會呼叫int型別的方法,如果沒有int就呼叫long
    //即存在一個呼叫順序char -> int -> long ->double -> ..。
    //當沒有基本型別對應的方法時,先自動裝箱,呼叫包裝類方法。
    //如果沒有包裝類方法,則呼叫包裝類實現的介面的方法。
    //最後再呼叫持有多個引數的char...方法。
    a.eat('a');
    a.eat('a','c','b');
}
public void eat(short i) {
    System.out.println("short");
}
public void eat(int i) {
    System.out.println("int");
}
public void eat(double i) {
    System.out.println("double");
}
public void eat(long i) {
    System.out.println("long");
}
public void eat(Character c) {
    System.out.println("Character");
}
public void eat(Comparable c) {
    System.out.println("Comparable");
}
public void eat(char ... c) {
    System.out.println(Arrays.toString(c));
    System.out.println("...");
}
//    public void eat(char i) {
//        System.out.println("char");
//    }

參考文章

https://segmentfault.com/a/1190000009707894

https://www.cnblogs.com/hysum/p/7100874.html

http://c.biancheng.net/view/939.html

https://www.runoob.com/

https://blog.csdn.net/android_hl/article/details/53228348

微信公眾號

Java技術江湖

如果大家想要實時關注我更新的文章以及分享的乾貨的話,可以關注我的公眾號【Java技術江湖】一位阿里 Java 工程師的技術小站,作者黃小斜,專注 Java 相關技術:SSM、SpringBoot、MySQL、分散式、中介軟體、叢集、Linux、網路、多執行緒,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!

Java工程師必備學習資源: 一些Java工程師常用學習資源,關注公眾號後,後臺回覆關鍵字 “Java” 即可免費無套路獲取。

我的公眾號

個人公眾號:黃小斜

作者是 985 碩士,螞蟻金服 JAVA 工程師,專注於 JAVA 後端技術棧:SpringBoot、MySQL、分散式、中介軟體、微服務,同時也懂點投資理財,偶爾講點演算法和計算機理論基礎,堅持學習和寫作,相信終身學習的力量!

程式設計師3T技術學習資源: 一些程式設計師學習技術的資源大禮包,關注公眾號後,後臺回覆關鍵字 “資料” 即可免費無套路獲取。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69906029/viewspace-2658129/,如需轉載,請註明出處,否則將追究法律責任。

相關文章