明翰Java教学系列之进阶面向对象篇
生活随笔
收集整理的這篇文章主要介紹了
明翰Java教学系列之进阶面向对象篇
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
復習
1.Java有多少種數據類型,數據類型的分類?數據類型的大小排序?默認類型?默認值?
2.Java的工作機制?
3.Java中有多少種初始化數組的方式?
4.什么是變量,如何定義變量?Java中有多少種變量?分別是什么以及區別?
5.循環體內return、continue、break的區別?
6.什么時候會出數組下標越界?
7.for循環的三個表達式的作用?
8.String s[][] = new String[2][2];與String s[][] = new String[2][];的含義
9.方法有幾種?分別是什么?區別是什么?
10.JDK&JRE&JVM分別是什么以及他們的關系?
前言 記得小時候語文老師教過我們記敘文六要素:時間、地點、人物,事件的起因、經過、結果,對吧? 那么Java的三要素是:繼承 、封裝 、多態。 我們圍繞著Java三要素,來展開一些其他的重要概念,例如接口,抽象類,方法的重寫&重載等等。 本節課的概念與類&對象的概念為承上啟下的關系,是非常重要的概念。
繼承(Inheritance,也稱為泛化) 繼承是面向對象編程實現軟件復用的重要手段,當子類繼承父類后,子類作為一種特殊的父類, 將直接自動獲得父類的屬性和方法(拿來就用,不用自己寫)。 同時子類也可以增加自己的屬性和方法以及重新定義父類的屬性、重寫父類的方法可以獲得與父類不同的功能。 在類層次結構中,對父類的改動自動反映在它所有的子類,子類的子類中, 不需要修改或重新編譯任何低層次的類,他們通過繼承而接收新信息, 僅僅需要在層次結構中定義行為和屬性一次,以后將自動由所有子類所繼承。 (父類也叫超類、基類,子類也叫派生類) (爸爸抽中華,兒子天生就抽中華。爸爸改成抽雪茄,兒子自動抽雪茄。但爸爸不會喝酒,兒子可以會喝酒。) 當兩個類具有相同的特征(屬性)和行為(方法)時,可以將相同的部分抽取出來放到一個類中作為父類,其它兩個類繼承這個父類。 在定義一個類時,可以在一個已經存在的類的基礎之上來進行,把這個已經存在的類所定義的內容作為自己的內容, 并加入自己若干新的內容。繼承有利于軟件的復用,避免重復代碼的出現,可以提高開發效率。 生活中的例子: 還能舉出其他的例子嗎? 繼承是類之間的一種關系,一般類與特殊類之間的關系,繼承關系的語義:“is a”。 父類更通用,子類更具體。 老虎類(子類)是一種食肉動物類(父類) tiger is a carnivore. 食肉動物類(子類) 是一種動物類(父類) carnivore?is an animal. 雖然食草動物和食肉動物都是屬于動物,但是兩者的屬性和行為上有差別, 所以子類會具有父類的一般特性也會具有自身的特性。 語法: [修飾符] class 子類名 extends 父類名{ ? ... } 注意: ~子類無法繼承父類的構造方法。 ~Java只支持類的單繼承,每個類只能有一個父類,兒子只能有一個爸爸,不允許有多個爸爸,爸爸只能有一個爺爺, 一個爸爸可以有多個兒子。 ~子類擁有父類非private的屬性與方法。(注意private的屬性和方法無法被繼承) ~子類可以擁有自己的屬性和方法,子類可以對父類進行擴展,子類可以用自己的方式實現父類的方法。 ~繼承提高了類之間的耦合性(繼承的缺點,耦合度高就會造成代碼之間的聯系)。 ~所有的類都是繼承于java.lang.Object,不用顯式的寫extends。 class Animal{ ? String color; ? int age; ? public void eat(){ ? ? System.out.println("覓食"); ? } } class Tiger extends Animal{ ? public void attack(){ ? ? System.out.println("攻擊"); ? } } class Main{ ? public static void main(String args[]){ ? ? Tiger t1 = new Tiger(); ? ? t1.attack(); ? ? t1.eat();//兒子自動繼承爸爸的方法 ? ? t1.age = 5;//兒子自動繼承爸爸的屬性 ? } } 實例化子類對象時總會在執行子類構造方法之前去調用父類構造方法, (先實例化父類,再實例化子類,繼承的類很多的話會影響性能) 注意: 如果父類有無參構造,則默認先調用父類的無參構造 (子類不用顯式的調用super(),系統會自動調用父類的無參構造方法。 )。 如果父類沒有無參構造,則需要程序員顯式的在子類構造中通過super()來調用父類構造,且必須在第一行調用。 如果父類只有有參構造,而子類沒有顯式的去調用則會報錯。 如果父類只有有參構造,而子類顯式的去調用父類的無參構造則會報錯。 應用場景: 例如有兩個類,這兩個類中有一部分相同的方法與屬性(相同部分屬于重復代碼)。 如果沒有父類的話,如果相同部分需要修改就要改2個地方(維護性低)。 因此我們把這兩個類中相同的部分抽出來,形成一個父類。 之后讓這兩個類繼承父類,這樣這兩個子類就不會純在重復代碼,維護性提高,代碼也簡潔了。
this與super關鍵字 this()與super()必須定在在構造方法中,并且必須在第一行。 super() 作用:調用父類的構造方法,只能出現在子類的構造器中,且必須是第一行 ~super()中的參數,決定了調用父類哪個構造方法 ~如果子類構造方法中沒有出現super,那么編譯器會默認加上super(),即調用父類的無參構造方法, 如果父類沒有無參構造,編譯器提示錯誤。 class Father{ ? public Father(){ ? ? System.out.println("這里是爸爸喲"); ? } } class Son extends Father{ ? public Son(){ ? ? super();//顯式調用父類構造方法 ? ? System.out.println("這里是兒子喲"); ? } } class Test{ ? public static void main(String[] args){ ? ? Son s = new Son(); ? } } this() 作用:調用本類的構造方法。 public Son() { ??? super("jack"); ??? System.out.println("兒子被構造啦"); ? } ? public Son(String name) { ??? this();//顯式調用無參構造方法 ??? System.out.println(name + "娃哈哈"); ? } 注意: ~this()&super()不能出現在static的方法與static塊中。 ~this()調用和super()調用不會同時出現。 super.指向父類的引用,我們可以通過super關鍵字來實現對父類成員的訪問。 this.指向本類的引用,我們可以通過this關鍵字來實現對本類成員的訪問。 class Father{ ? int age = 50; } class Son extends Father{ ? int age = 20; ? public void say(){ ? ? System.out.println(super.age);//輸出50 ? ? System.out.println(this.age);//輸出20 ? } } class Main{ ? public static void main(String[] args){ ? ? Son s = new Son(); ? ? s.say(); ? } }
final關鍵字(經典初級Java面試題) final可以修飾的元素: 類:不能被繼承,類被定義成final,那么類中的所有方法全部為final。 方法:不能在子類中被覆蓋(除private方法外),即不能修改。 變量:不能被重新賦值,就是之前教過大家的常量。
包(package) 包主要用來對類和接口進行分類。當開發Java程序時,可能編寫成百上千的類,因此很有必要對類和接口進行分類。 包的概念跟操作系統里的文件夾類似,我們把寫完的Java類文件放到不同的文件夾(包)中。 包與文件夾一樣,也會有層次結構,一個包中可以包含若干個子包,子包再還可以包含若干個子包。 不同的包下,文件命名可以重名。 與包相關的語句只有2個,一個是打包語句package,一個是引包語句import。
封裝(Encapsulation) 將對象的實現細節(屬性&方法)隱藏起來,然后通過一些公用方法來暴露該對象的功能。 將對象的狀態信息隱藏在對象內部,不允許外部程序直接訪問對象的內部信息, 而是通過該類所提供的方法來實現對內部信息的操作與訪問。 封裝可以被認為是一個保護屏障,防止該類的代碼和數據被外部類定義的代碼隨機訪問。 要訪問該類的代碼和數據,必須通過嚴格的入口控制。 適當的封裝可以讓代碼更容易理解與維護,也加強了代碼的安全性。 例如: ~電視機的內部是什么電路你也不知道,你只知道用遙控器能操作電視就可以了。 ~女生的年齡是比較隱私的,需要隱藏起來,不要讓外人知道。 ~吃一粒膠囊的時候,你不用care膠囊里有多少種化學藥品,只管吃了能治病就行。 那么為什么要隱藏呢? 開發者讓使用者只能通過事先定制好的方法來訪問數據,可以方便地加入控制邏輯,限制對屬性的不合理操作。 把例子寫一下,有墻有門。。。。!!! 那么怎么隱藏呢? Java的訪問控制符(體現了Java的封裝性) 可修飾在類名、方法名、成員變量名上等等。 級別從小到大:private->default->protected->public
(可以結合上面的表自己做幾個小例子,加深印象。)
注意:
~類的控制級別只有public與default,而成員變量與方法則4個都有。
~如果類的構造方法為private,則其他類均不能生成該類的實例(自己類內部則可以)。
~Java類中一般會使用private來修飾成員變量,來防止外部調用。(其實還有一個好處,生成getter/setter方法用于多態時的覆蓋屬性值)
class Test{ ? private String name; ? private int age; ? public Test(){ ? ? this.name = "張三"; ? ? this.age = 18; ? } ? public void speak(){ ? ? System.out.println("大聲說出我的名字是"+name); ? ? System.out.println("大聲說出我的年齡是"+age); ? } } class Main{ ? public static void main(String[] args){ ? ? Test t1 = new Test(); ? ? t1.name = "張三";//編譯報錯,name屬性已經被封裝,無法調用。 ? ? t1.speak();//程序作者只允許調用speak方法。 ? } }
這段代碼中,將 name 和 age 屬性設置為私有的,只能本類才能訪問,其他類都訪問不了,如此就對信息進行了隱藏。
模塊設計追求高內聚、低耦合。
高內聚:盡可能把模塊的內部數據、功能實現細節隱藏在模塊的內部獨立完成,不允許外部直接干涉。
低耦合:僅暴露少量的方法給外部使用。
(有興趣的同學可以看一下單例模式。。。)
方法的覆蓋
多態(Polymorphism) 多態是建立在繼承的基礎上的,是指子類類型的對象可以賦值給父類類型的引用變量,但運行時仍表現子類的行為特征。也就是說,同一種類型的對象執行同一個方法時可以表現出不同的行為特征。 java引用變量有兩個類型:一個是編譯時類型,一個是運行時類型。編譯時類型由聲明該變量時使用的類型決定, 運行時類型由實際賦給該變量的對象決定,如果編譯時類型與運行時類型不一致,就可能會出現所謂的多態。 多態的三個必要條件: ~要有類繼承或實現接口(接口在下面會講解) ~要有方法重寫 ~父類引用指向子類對象(或接口引用指向實現類對象)。 一旦滿足以上3個條件,當調用父類中被重寫的方法后,運行時創建的是哪個子類的對象, 就調用該子類中重寫的那個方法。 生活中的例子: 按鍵盤F1鍵 如果當前在 Word 下彈出的就是 Word 幫助; 在 Windows 下彈出的就是 Windows 幫助和支持。 在XX游戲中就是人物介紹。 同一個事件發生在不同的對象上會產生不同的結果。 多態性是對象多種表現形式的體現。 class Father{ ? public void somke(){ ? ? System.out.println("抽中華"); ? } } class Son extends Father{ ? public void somke(){ ? ?System.out.println("抽中南海"); ? } } class Main{ ? public static void main(String[] argus){ ? ? Father f = new Son(); //子類對象可以直接賦給父類變量 ? ? f.somke(); //運行時表現出子類的行為特征 ? ? //這意味著同一個類型的對象在執行同一個方法時,可能表現出多種行為特征。 ? } } 為什么要使用多態? 可以增強程序的可擴展性及可維護性,使代碼更加簡潔。 作用:在方法傳遞引用數據類型的時候,使方法內部的代碼更具有可維護性,當有新增子類時, 方法內部的代碼不用去修改,進一步的使用抽象的概念。 //例如父類是Father,其子類分別是Son1、Son2、Son3 public static void doSomething(Father f){ ? //三個兒子都可以當參數傳進來,三次調用的work方法表現出來的行為都不一樣。 ? f.work(); } public void main(String[] args){ doSomething(new Son1()); doSomething(new Son2()); doSomething(new Son3());? ? //再增加Son4的時候,doSomething方法也不用修改。 } 多態與繼承、方法重寫密切相關,我們在方法中接收父類類型作為參數,在方法實現中調用父類類型的各種方法。 當把子類作為參數傳遞給這個方法時,java虛擬機會根據實際創建的對象類型, 調用子類中相應的方法(存在方法重寫時)。 大白話: 在父子類繼承/接口環境中,去實例化對象時,數據類型使用的是父類/接口,實例化的是子類/接口實現類的對象。 多態就是一個數據類型的對象會具有多種狀態/形態,具有多種實現方式。 注意事項(面試題): 在多態情況下,父類的成員變量無法被子類覆蓋,因此需要使用setter/getter方法。 在繼承情況下,父類的成員變量可以被子類覆蓋。 在多態情況下,子類有重寫父類方法的情況,則調用子類方法。 class Father { ? String name = "爸爸"; } class Son extends Father { ? String name = "兒子"; } public class Main { ? public static void main(String[] args) { ? ? Son s = new Son(); ? ? Father f = new Son(); ? ? System.out.println(s.name); //輸出兒子 ? ? System.out.println(f.name); //輸出爸爸 ? } } 多態情況下,對象無法調用子類獨有的方法與屬性,只能調用編譯類型的成員。 class Father { ? String name = "爸爸"; } class Son extends Father { ? String name = "兒子"; ? int age = 18; //子類特有的屬性 ? public void eat(){ ? ? System.out.println("兒子在吃飯"); //子類特有的方法 ? } } public class Main { ? public static void main(String[] args) { ? ? Father f = new Son(); ? ? System.out.println(f.name);//正常輸出 ? ? System.out.println(f.age); //編譯出錯 ? ? System.out.println(f.eat()); //編譯出錯 ? } }
抽象類(abstract class)與抽象方法(abstract method)
接口(interface) 接口是抽象類的變體,在接口中,所有方法都是抽象的,多繼承性可通過實現這樣的接口而獲得。 接口就是標準(一些類需要遵守的規范),是用來隔離具體實現的(或者說是和具體實現解耦)。 舉個生活中的例子: 各種電腦、移動硬盤等設備上的USB接口就是標準,大家各自制造自己的具體產品。 產品使用者和提供者都遵守這個標準, 那么使用者就不必擔心自己電腦上的USB接口是否只能插移動硬盤而不能插手機。 再打個比方,網絡上的各種協議,比如HTTP協議,只要客戶端和服務端都遵守這個協議, 那么無論是用火狐貍瀏覽器還是用IE,也或者是360瀏覽器,都可以訪問,不用擔心服務端發過來的信息, 瀏覽器解析不了。 回到主題,程序接口的使用就將調用者和提供者之間進行了解耦,只要實現者遵守這個接口來做實現就好, 實現細節不用管。 語法: [修飾符] interface 接口名{ ? ... } 注意事項 ~接口不是類,不能被實例化也沒有構造方法,但抽象類有構造方法。 ~接口內所有的屬性均默認為public static final,只能在定義時指定默認值。 ~接口內所有的方法均默認為public abstract,不能用static&final。 ~接口內不允許有正常方法(帶方法體的方法)。 ~接口可以同時繼承多個父接口,但接口只能繼承接口,不能繼承類。 ~方法提供者與方法調用者有可能是一個人也可能是不同的人。 ~一個java源文件中最多只能有一個public接口,并且文件名與接口名相同。 類實現接口(implements) 類一旦實現接口,就必須實現其所有方法,否則這個類必須聲明為抽象類。 一個類可以只能繼承一個類,但可以實現多個接口。 多個無關的類可以實現一個接口,一個類可以實現多個無關的接口。 一個類可以在繼承一個父類的同時,實現一個或多個接口。 public class A extend B implements C,D{ ? ... } 接口與抽象類的區別(經典初級Java面試題) 接口不能含有任何非抽象方法,而抽象類可以。(JDK8以及以上除外) 類可以實現多個接口,但只能繼承一個父類。 接口不是類分級結構的一部分,沒有聯系的類可以實現相同的接口。 標記接口 最常用的繼承接口是沒有包含任何方法的接口。 標識接口是沒有任何方法和屬性的接口.它僅僅表明它的類屬于一個特定的類型,供其他代碼來測試允許做一些事情。 標識接口作用:簡單形象的說就是給某個對象打個標(蓋個戳),使對象擁有某個或某些特權。 例如:java.awt.event 包中的 MouseListener 接口繼承的 java.util.EventListener 接口定義如下: package java.util; public interface EventListener{} 沒有任何方法的接口被稱為標記接口。標記接口主要用于以下兩種目的: 建立一個公共的父接口: 正如EventListener接口,這是由幾十個其他接口擴展的Java API,你可以使用一個標記接口來建立一組接口的父接口。 例如:當一個接口繼承了EventListener接口,Java虛擬機(JVM)就知道該接口將要被用于一個事件的代理方案。 向一個類添加數據類型: 這種情況是標記接口最初的目的,實現標記接口的類不需要定義任何接口方法(因為標記接口根本就沒有方法), 但是該類通過多態性變成一個接口類型。
接口的好處(接口的應用場景) 很多小伙伴不知道接口的好處是什么,包括我自己剛開始也不是很清楚,接口可以配合多態性彰顯巨大威力。 在定義方法的參數類型與返回類型時,以及定義成員變量的類型時都可以使用接口, 這樣你傳進任何這個接口的實現類的對象都可以,返回、成員變量都同理。 如果方法的參數只寫死一個類,那么我們只能傳這個固定類的對象,這種寫法就非常的死!!! 限制住了調用者傳遞的對象必須只能是這個固定的類,無法擴展自己的自定義類對象傳到方法中。 public void eat(Person person){ ? ... } eat(new Person()); 下面升級一下,我們使用抽象類&繼承。 這樣貌似解決了上面只能傳一個類的對象的尷尬局面,看~這次我可以傳多個類,只要是Father的子類就好了。 但不要忘了,Java只支持類的單繼承,很多場景,要傳的類的對象已經有父類,那你就瞎了。 如果這個方法只能傳Father的子類,也有些不夠靈活。 public void eat(Father f){ ? ... } eat(new Son()); eat(new Son2()); eat(new Son3()); 再升級,我們使用接口,這次所有實現該接口的類的對象都可以通過參數傳過來,并且別忘了, 實現接口的類可以是毫無關聯的,類與類之間沒有關系,并且每個類允許實現多個接口。 這樣是不是就更靈活更隨便了呢? 還有一點,當傳過來的參數的對象變化多端,只要你實現了接口,我就放你進來。 那么這個被調用的方法是不用修改的。 public void eat(Tag tag){ ? ... } eat(new Person()); eat(new Father()); eat(new Tiger()); 方法的編寫者需要調用參數對象的一個方法,但是他不管這個方法的實現,實現由調用者負責。 所謂面向接口編程,讓程序依賴于一個比較寬泛的(或者說更抽象的)類型,這個類型下面應該有很多具體的子類;也就是說,這個類型處在一個比較大的類型樹的頂端。 “請求”和“實現”分離開來,這就是接口的解耦! 使用接口,可以避免接口調用方直接參與業務邏輯實現,所以能避免接口調用與業務邏輯實現緊密關聯,即解耦 一個現實的例子(新手PASS): Collection接口定義了iterator()方法,該方法返回Iterator接口的實現類對象, 下面這一段代碼可以遍歷所有直接或間接實現Collection接口的集合類: public void traverse(Collection<?> c) { Iterator<?> i = c.iterator(); while(i.hasNext()) { Object o = i.next(); //do something with o... } } 也就是說,這個方法無論傳入ArrayList、LinkedList、HashSet、TreeSet、ArrayBlockingQueue還是其他的什么Collection實現,都可以遍歷其中的內容。 如果沒有接口的話,那么就需要寫一堆traverse方法了,traverse(ArrayList<?> list)、traverse(LinkedList<?> list) …… 一個還不錯的例子: 定義一個接口 磁盤 interface Disk(){ ??void save(File file); } U盤和硬盤都是磁盤,都實現這個接口 class UDisk implement Disk{ void save(File file){ ???System.out.println("u盤在存儲"); ?} } class HardDisk implement Disk{ void save(File file){ ??System.out.println("硬盤在存儲"); } } 一個需要用磁盤來存儲的下載工具 class Download{ ??Disk disk;//用接口聲明,我們不知道,也不用知道,我們未來會存到什么樣的磁盤,我們不依賴于任何類型的磁盤,我們只依賴于這個接口 ??void download(File file){ ???????disk.save(file); ??} ??void setDisk(Disk disk){ ??this.disk=disk; ??} ??public static void main(String[] args){ ??Download download = new Download(); ??設置存儲目標為U盤 ??download.setDisk(new UDisk()); ??文件被存到了U盤 ??download.download(file); ??設置存儲目標為硬盤 ??download.setDisk(new HardDisk()); ??文件被存到了硬盤 ??download.download(file); ? } } 某天我們想把下載的文件保存到CD里邊,我們只需要定義CDDisk類,實現Disk接口就可以不對download本身做任何修改,就可以方便的將文件下載到CD或其他介質里。我們的Download類不依賴于任何具體的類,這樣就接觸了與任何具體存儲設備的耦合! 這樣就接解除了與任何具體存儲設備的耦合! 如果是依賴接口,則可以隨心所欲的更換實現類。 就像SpringJDBC框架設計的dataSource注入 (新手PASS) 不同的數據庫廠商只要把自己的代碼都實現dataSource接口就可以了。 這樣無論你要切換哪個廠商的代碼,至少底層框架中的dataSource是不會改變的,框架的代碼是不會變的, 只是改變注入就可以。 這樣豈不是很爽?
引用數據類型的轉換
內部類(新手PASS) 內部類(嵌套類) 內部類就是定義在另一個類內部的類。 內部類對于同一包中的其它類來說,內部類能夠隱藏起來。 注意: 內部類可以訪問其外部類中所有的屬性和方法 無需創建外部類的對象,即可從內部類訪問外部類的變量和方法。 必須創建內部類的對象,否則無法從外部類訪問內部類的變量和方法。 如果內部類中有和外部類同名的變量或方法,則內部類的變量和方法將獲得比外部類的變量和方法更高的優先級。 不能定義static變量 public class Outer { ? private int varOuter=100; ? class Inner { ? ? int varInner=200; ? ? public void showOuter() { ? ? ? System.out.println(varOuter); //是否能夠輸出? ? ? } ? } ? public void showInner() { ? ? ? Inner i=new Inner(); ? ? ? ? System.out.println(i.varInner); ? } } 普通類的訪問權限修飾符default public 內部類的訪問權限修飾符default public protected private 內部類的訪問 在Outer內訪問Inner,只需如下: Inner in = new Inner() ; 在Outer外訪問Inner,必須如下: Outer o = new Outer(); //實例化外部類 Outer.Inner oi = o.new Inner(); //實例化內部類 靜態內部類 用static標識的內部類為靜態內部類。 靜態內部類作為外部類的靜態成員,不能訪問外部類非靜態成員。 非靜態內部類只能定義非靜態成員,而靜態內部類可以定義靜態成員和非靜態成員。 使用Outer.Inner inn=new Outer.Inner()方式實例化靜態內部類。 非靜態內部類不可以使用上面的方式實例化 局部內部類 在一個類的方法體中或程序塊內定義的內部類 類中定義的內部類 class? A{ ? int a;? ? public void method(){ ? } ?? ? class B{ ? } } 局部內部類 class A{ ? int a; ? public void method(int c){ ? ? int b=0; ? ? class B{ ? ?? ? ? } ? } } 在方法定義的內部類中只能訪問方法中的final類型的局部變量 public class Outer2 { ? public int a = 1; ? private int b = 2; ? public void method(final int c) { ? ? int d = 3; ? ? final int e = 2; ? ? class Inner { ? ? ? private void iMethod(int e) { ? ? ? ? //System.out.println(e); ? ? ? } ? ? } ? } }
總結 今天我們主要學習了java面向對象的進階部分,包括傳說中的封裝、繼承、多態等等知識,尤其是關于接口的運用, 一些小伙伴都不是很理解為什么要使用接口,概念在于理解,而不是死記硬背,這樣才能融會貫通。
前言 記得小時候語文老師教過我們記敘文六要素:時間、地點、人物,事件的起因、經過、結果,對吧? 那么Java的三要素是:繼承 、封裝 、多態。 我們圍繞著Java三要素,來展開一些其他的重要概念,例如接口,抽象類,方法的重寫&重載等等。 本節課的概念與類&對象的概念為承上啟下的關系,是非常重要的概念。
繼承(Inheritance,也稱為泛化) 繼承是面向對象編程實現軟件復用的重要手段,當子類繼承父類后,子類作為一種特殊的父類, 將直接自動獲得父類的屬性和方法(拿來就用,不用自己寫)。 同時子類也可以增加自己的屬性和方法以及重新定義父類的屬性、重寫父類的方法可以獲得與父類不同的功能。 在類層次結構中,對父類的改動自動反映在它所有的子類,子類的子類中, 不需要修改或重新編譯任何低層次的類,他們通過繼承而接收新信息, 僅僅需要在層次結構中定義行為和屬性一次,以后將自動由所有子類所繼承。 (父類也叫超類、基類,子類也叫派生類) (爸爸抽中華,兒子天生就抽中華。爸爸改成抽雪茄,兒子自動抽雪茄。但爸爸不會喝酒,兒子可以會喝酒。) 當兩個類具有相同的特征(屬性)和行為(方法)時,可以將相同的部分抽取出來放到一個類中作為父類,其它兩個類繼承這個父類。 在定義一個類時,可以在一個已經存在的類的基礎之上來進行,把這個已經存在的類所定義的內容作為自己的內容, 并加入自己若干新的內容。繼承有利于軟件的復用,避免重復代碼的出現,可以提高開發效率。 生活中的例子: 還能舉出其他的例子嗎? 繼承是類之間的一種關系,一般類與特殊類之間的關系,繼承關系的語義:“is a”。 父類更通用,子類更具體。 老虎類(子類)是一種食肉動物類(父類) tiger is a carnivore. 食肉動物類(子類) 是一種動物類(父類) carnivore?is an animal. 雖然食草動物和食肉動物都是屬于動物,但是兩者的屬性和行為上有差別, 所以子類會具有父類的一般特性也會具有自身的特性。 語法: [修飾符] class 子類名 extends 父類名{ ? ... } 注意: ~子類無法繼承父類的構造方法。 ~Java只支持類的單繼承,每個類只能有一個父類,兒子只能有一個爸爸,不允許有多個爸爸,爸爸只能有一個爺爺, 一個爸爸可以有多個兒子。 ~子類擁有父類非private的屬性與方法。(注意private的屬性和方法無法被繼承) ~子類可以擁有自己的屬性和方法,子類可以對父類進行擴展,子類可以用自己的方式實現父類的方法。 ~繼承提高了類之間的耦合性(繼承的缺點,耦合度高就會造成代碼之間的聯系)。 ~所有的類都是繼承于java.lang.Object,不用顯式的寫extends。 class Animal{ ? String color; ? int age; ? public void eat(){ ? ? System.out.println("覓食"); ? } } class Tiger extends Animal{ ? public void attack(){ ? ? System.out.println("攻擊"); ? } } class Main{ ? public static void main(String args[]){ ? ? Tiger t1 = new Tiger(); ? ? t1.attack(); ? ? t1.eat();//兒子自動繼承爸爸的方法 ? ? t1.age = 5;//兒子自動繼承爸爸的屬性 ? } } 實例化子類對象時總會在執行子類構造方法之前去調用父類構造方法, (先實例化父類,再實例化子類,繼承的類很多的話會影響性能) 注意: 如果父類有無參構造,則默認先調用父類的無參構造 (子類不用顯式的調用super(),系統會自動調用父類的無參構造方法。 )。 如果父類沒有無參構造,則需要程序員顯式的在子類構造中通過super()來調用父類構造,且必須在第一行調用。 如果父類只有有參構造,而子類沒有顯式的去調用則會報錯。 如果父類只有有參構造,而子類顯式的去調用父類的無參構造則會報錯。 應用場景: 例如有兩個類,這兩個類中有一部分相同的方法與屬性(相同部分屬于重復代碼)。 如果沒有父類的話,如果相同部分需要修改就要改2個地方(維護性低)。 因此我們把這兩個類中相同的部分抽出來,形成一個父類。 之后讓這兩個類繼承父類,這樣這兩個子類就不會純在重復代碼,維護性提高,代碼也簡潔了。
this與super關鍵字 this()與super()必須定在在構造方法中,并且必須在第一行。 super() 作用:調用父類的構造方法,只能出現在子類的構造器中,且必須是第一行 ~super()中的參數,決定了調用父類哪個構造方法 ~如果子類構造方法中沒有出現super,那么編譯器會默認加上super(),即調用父類的無參構造方法, 如果父類沒有無參構造,編譯器提示錯誤。 class Father{ ? public Father(){ ? ? System.out.println("這里是爸爸喲"); ? } } class Son extends Father{ ? public Son(){ ? ? super();//顯式調用父類構造方法 ? ? System.out.println("這里是兒子喲"); ? } } class Test{ ? public static void main(String[] args){ ? ? Son s = new Son(); ? } } this() 作用:調用本類的構造方法。 public Son() { ??? super("jack"); ??? System.out.println("兒子被構造啦"); ? } ? public Son(String name) { ??? this();//顯式調用無參構造方法 ??? System.out.println(name + "娃哈哈"); ? } 注意: ~this()&super()不能出現在static的方法與static塊中。 ~this()調用和super()調用不會同時出現。 super.指向父類的引用,我們可以通過super關鍵字來實現對父類成員的訪問。 this.指向本類的引用,我們可以通過this關鍵字來實現對本類成員的訪問。 class Father{ ? int age = 50; } class Son extends Father{ ? int age = 20; ? public void say(){ ? ? System.out.println(super.age);//輸出50 ? ? System.out.println(this.age);//輸出20 ? } } class Main{ ? public static void main(String[] args){ ? ? Son s = new Son(); ? ? s.say(); ? } }
final關鍵字(經典初級Java面試題) final可以修飾的元素: 類:不能被繼承,類被定義成final,那么類中的所有方法全部為final。 方法:不能在子類中被覆蓋(除private方法外),即不能修改。 變量:不能被重新賦值,就是之前教過大家的常量。
包(package) 包主要用來對類和接口進行分類。當開發Java程序時,可能編寫成百上千的類,因此很有必要對類和接口進行分類。 包的概念跟操作系統里的文件夾類似,我們把寫完的Java類文件放到不同的文件夾(包)中。 包與文件夾一樣,也會有層次結構,一個包中可以包含若干個子包,子包再還可以包含若干個子包。 不同的包下,文件命名可以重名。 與包相關的語句只有2個,一個是打包語句package,一個是引包語句import。
- package語句
- import語句
- java常用包
封裝(Encapsulation) 將對象的實現細節(屬性&方法)隱藏起來,然后通過一些公用方法來暴露該對象的功能。 將對象的狀態信息隱藏在對象內部,不允許外部程序直接訪問對象的內部信息, 而是通過該類所提供的方法來實現對內部信息的操作與訪問。 封裝可以被認為是一個保護屏障,防止該類的代碼和數據被外部類定義的代碼隨機訪問。 要訪問該類的代碼和數據,必須通過嚴格的入口控制。 適當的封裝可以讓代碼更容易理解與維護,也加強了代碼的安全性。 例如: ~電視機的內部是什么電路你也不知道,你只知道用遙控器能操作電視就可以了。 ~女生的年齡是比較隱私的,需要隱藏起來,不要讓外人知道。 ~吃一粒膠囊的時候,你不用care膠囊里有多少種化學藥品,只管吃了能治病就行。 那么為什么要隱藏呢? 開發者讓使用者只能通過事先定制好的方法來訪問數據,可以方便地加入控制邏輯,限制對屬性的不合理操作。 把例子寫一下,有墻有門。。。。!!! 那么怎么隱藏呢? Java的訪問控制符(體現了Java的封裝性) 可修飾在類名、方法名、成員變量名上等等。 級別從小到大:private->default->protected->public
| private | default(啥也不寫) | protected | public | |
| 同一類中 | yes | yes | yes | yes |
| 同一包中 | yes | yes | yes | |
| 子類中 | yes | yes | ||
| 全局范圍(同一項目) | yes |
方法的覆蓋
- 重寫(overriding)
- 重載(overloading)
多態(Polymorphism) 多態是建立在繼承的基礎上的,是指子類類型的對象可以賦值給父類類型的引用變量,但運行時仍表現子類的行為特征。也就是說,同一種類型的對象執行同一個方法時可以表現出不同的行為特征。 java引用變量有兩個類型:一個是編譯時類型,一個是運行時類型。編譯時類型由聲明該變量時使用的類型決定, 運行時類型由實際賦給該變量的對象決定,如果編譯時類型與運行時類型不一致,就可能會出現所謂的多態。 多態的三個必要條件: ~要有類繼承或實現接口(接口在下面會講解) ~要有方法重寫 ~父類引用指向子類對象(或接口引用指向實現類對象)。 一旦滿足以上3個條件,當調用父類中被重寫的方法后,運行時創建的是哪個子類的對象, 就調用該子類中重寫的那個方法。 生活中的例子: 按鍵盤F1鍵 如果當前在 Word 下彈出的就是 Word 幫助; 在 Windows 下彈出的就是 Windows 幫助和支持。 在XX游戲中就是人物介紹。 同一個事件發生在不同的對象上會產生不同的結果。 多態性是對象多種表現形式的體現。 class Father{ ? public void somke(){ ? ? System.out.println("抽中華"); ? } } class Son extends Father{ ? public void somke(){ ? ?System.out.println("抽中南海"); ? } } class Main{ ? public static void main(String[] argus){ ? ? Father f = new Son(); //子類對象可以直接賦給父類變量 ? ? f.somke(); //運行時表現出子類的行為特征 ? ? //這意味著同一個類型的對象在執行同一個方法時,可能表現出多種行為特征。 ? } } 為什么要使用多態? 可以增強程序的可擴展性及可維護性,使代碼更加簡潔。 作用:在方法傳遞引用數據類型的時候,使方法內部的代碼更具有可維護性,當有新增子類時, 方法內部的代碼不用去修改,進一步的使用抽象的概念。 //例如父類是Father,其子類分別是Son1、Son2、Son3 public static void doSomething(Father f){ ? //三個兒子都可以當參數傳進來,三次調用的work方法表現出來的行為都不一樣。 ? f.work(); } public void main(String[] args){ doSomething(new Son1()); doSomething(new Son2()); doSomething(new Son3());? ? //再增加Son4的時候,doSomething方法也不用修改。 } 多態與繼承、方法重寫密切相關,我們在方法中接收父類類型作為參數,在方法實現中調用父類類型的各種方法。 當把子類作為參數傳遞給這個方法時,java虛擬機會根據實際創建的對象類型, 調用子類中相應的方法(存在方法重寫時)。 大白話: 在父子類繼承/接口環境中,去實例化對象時,數據類型使用的是父類/接口,實例化的是子類/接口實現類的對象。 多態就是一個數據類型的對象會具有多種狀態/形態,具有多種實現方式。 注意事項(面試題): 在多態情況下,父類的成員變量無法被子類覆蓋,因此需要使用setter/getter方法。 在繼承情況下,父類的成員變量可以被子類覆蓋。 在多態情況下,子類有重寫父類方法的情況,則調用子類方法。 class Father { ? String name = "爸爸"; } class Son extends Father { ? String name = "兒子"; } public class Main { ? public static void main(String[] args) { ? ? Son s = new Son(); ? ? Father f = new Son(); ? ? System.out.println(s.name); //輸出兒子 ? ? System.out.println(f.name); //輸出爸爸 ? } } 多態情況下,對象無法調用子類獨有的方法與屬性,只能調用編譯類型的成員。 class Father { ? String name = "爸爸"; } class Son extends Father { ? String name = "兒子"; ? int age = 18; //子類特有的屬性 ? public void eat(){ ? ? System.out.println("兒子在吃飯"); //子類特有的方法 ? } } public class Main { ? public static void main(String[] args) { ? ? Father f = new Son(); ? ? System.out.println(f.name);//正常輸出 ? ? System.out.println(f.age); //編譯出錯 ? ? System.out.println(f.eat()); //編譯出錯 ? } }
抽象類(abstract class)與抽象方法(abstract method)
- 抽象方法
- 抽象類
接口(interface) 接口是抽象類的變體,在接口中,所有方法都是抽象的,多繼承性可通過實現這樣的接口而獲得。 接口就是標準(一些類需要遵守的規范),是用來隔離具體實現的(或者說是和具體實現解耦)。 舉個生活中的例子: 各種電腦、移動硬盤等設備上的USB接口就是標準,大家各自制造自己的具體產品。 產品使用者和提供者都遵守這個標準, 那么使用者就不必擔心自己電腦上的USB接口是否只能插移動硬盤而不能插手機。 再打個比方,網絡上的各種協議,比如HTTP協議,只要客戶端和服務端都遵守這個協議, 那么無論是用火狐貍瀏覽器還是用IE,也或者是360瀏覽器,都可以訪問,不用擔心服務端發過來的信息, 瀏覽器解析不了。 回到主題,程序接口的使用就將調用者和提供者之間進行了解耦,只要實現者遵守這個接口來做實現就好, 實現細節不用管。 語法: [修飾符] interface 接口名{ ? ... } 注意事項 ~接口不是類,不能被實例化也沒有構造方法,但抽象類有構造方法。 ~接口內所有的屬性均默認為public static final,只能在定義時指定默認值。 ~接口內所有的方法均默認為public abstract,不能用static&final。 ~接口內不允許有正常方法(帶方法體的方法)。 ~接口可以同時繼承多個父接口,但接口只能繼承接口,不能繼承類。 ~方法提供者與方法調用者有可能是一個人也可能是不同的人。 ~一個java源文件中最多只能有一個public接口,并且文件名與接口名相同。 類實現接口(implements) 類一旦實現接口,就必須實現其所有方法,否則這個類必須聲明為抽象類。 一個類可以只能繼承一個類,但可以實現多個接口。 多個無關的類可以實現一個接口,一個類可以實現多個無關的接口。 一個類可以在繼承一個父類的同時,實現一個或多個接口。 public class A extend B implements C,D{ ? ... } 接口與抽象類的區別(經典初級Java面試題) 接口不能含有任何非抽象方法,而抽象類可以。(JDK8以及以上除外) 類可以實現多個接口,但只能繼承一個父類。 接口不是類分級結構的一部分,沒有聯系的類可以實現相同的接口。 標記接口 最常用的繼承接口是沒有包含任何方法的接口。 標識接口是沒有任何方法和屬性的接口.它僅僅表明它的類屬于一個特定的類型,供其他代碼來測試允許做一些事情。 標識接口作用:簡單形象的說就是給某個對象打個標(蓋個戳),使對象擁有某個或某些特權。 例如:java.awt.event 包中的 MouseListener 接口繼承的 java.util.EventListener 接口定義如下: package java.util; public interface EventListener{} 沒有任何方法的接口被稱為標記接口。標記接口主要用于以下兩種目的: 建立一個公共的父接口: 正如EventListener接口,這是由幾十個其他接口擴展的Java API,你可以使用一個標記接口來建立一組接口的父接口。 例如:當一個接口繼承了EventListener接口,Java虛擬機(JVM)就知道該接口將要被用于一個事件的代理方案。 向一個類添加數據類型: 這種情況是標記接口最初的目的,實現標記接口的類不需要定義任何接口方法(因為標記接口根本就沒有方法), 但是該類通過多態性變成一個接口類型。
接口的好處(接口的應用場景) 很多小伙伴不知道接口的好處是什么,包括我自己剛開始也不是很清楚,接口可以配合多態性彰顯巨大威力。 在定義方法的參數類型與返回類型時,以及定義成員變量的類型時都可以使用接口, 這樣你傳進任何這個接口的實現類的對象都可以,返回、成員變量都同理。 如果方法的參數只寫死一個類,那么我們只能傳這個固定類的對象,這種寫法就非常的死!!! 限制住了調用者傳遞的對象必須只能是這個固定的類,無法擴展自己的自定義類對象傳到方法中。 public void eat(Person person){ ? ... } eat(new Person()); 下面升級一下,我們使用抽象類&繼承。 這樣貌似解決了上面只能傳一個類的對象的尷尬局面,看~這次我可以傳多個類,只要是Father的子類就好了。 但不要忘了,Java只支持類的單繼承,很多場景,要傳的類的對象已經有父類,那你就瞎了。 如果這個方法只能傳Father的子類,也有些不夠靈活。 public void eat(Father f){ ? ... } eat(new Son()); eat(new Son2()); eat(new Son3()); 再升級,我們使用接口,這次所有實現該接口的類的對象都可以通過參數傳過來,并且別忘了, 實現接口的類可以是毫無關聯的,類與類之間沒有關系,并且每個類允許實現多個接口。 這樣是不是就更靈活更隨便了呢? 還有一點,當傳過來的參數的對象變化多端,只要你實現了接口,我就放你進來。 那么這個被調用的方法是不用修改的。 public void eat(Tag tag){ ? ... } eat(new Person()); eat(new Father()); eat(new Tiger()); 方法的編寫者需要調用參數對象的一個方法,但是他不管這個方法的實現,實現由調用者負責。 所謂面向接口編程,讓程序依賴于一個比較寬泛的(或者說更抽象的)類型,這個類型下面應該有很多具體的子類;也就是說,這個類型處在一個比較大的類型樹的頂端。 “請求”和“實現”分離開來,這就是接口的解耦! 使用接口,可以避免接口調用方直接參與業務邏輯實現,所以能避免接口調用與業務邏輯實現緊密關聯,即解耦 一個現實的例子(新手PASS): Collection接口定義了iterator()方法,該方法返回Iterator接口的實現類對象, 下面這一段代碼可以遍歷所有直接或間接實現Collection接口的集合類: public void traverse(Collection<?> c) { Iterator<?> i = c.iterator(); while(i.hasNext()) { Object o = i.next(); //do something with o... } } 也就是說,這個方法無論傳入ArrayList、LinkedList、HashSet、TreeSet、ArrayBlockingQueue還是其他的什么Collection實現,都可以遍歷其中的內容。 如果沒有接口的話,那么就需要寫一堆traverse方法了,traverse(ArrayList<?> list)、traverse(LinkedList<?> list) …… 一個還不錯的例子: 定義一個接口 磁盤 interface Disk(){ ??void save(File file); } U盤和硬盤都是磁盤,都實現這個接口 class UDisk implement Disk{ void save(File file){ ???System.out.println("u盤在存儲"); ?} } class HardDisk implement Disk{ void save(File file){ ??System.out.println("硬盤在存儲"); } } 一個需要用磁盤來存儲的下載工具 class Download{ ??Disk disk;//用接口聲明,我們不知道,也不用知道,我們未來會存到什么樣的磁盤,我們不依賴于任何類型的磁盤,我們只依賴于這個接口 ??void download(File file){ ???????disk.save(file); ??} ??void setDisk(Disk disk){ ??this.disk=disk; ??} ??public static void main(String[] args){ ??Download download = new Download(); ??設置存儲目標為U盤 ??download.setDisk(new UDisk()); ??文件被存到了U盤 ??download.download(file); ??設置存儲目標為硬盤 ??download.setDisk(new HardDisk()); ??文件被存到了硬盤 ??download.download(file); ? } } 某天我們想把下載的文件保存到CD里邊,我們只需要定義CDDisk類,實現Disk接口就可以不對download本身做任何修改,就可以方便的將文件下載到CD或其他介質里。我們的Download類不依賴于任何具體的類,這樣就接觸了與任何具體存儲設備的耦合! 這樣就接解除了與任何具體存儲設備的耦合! 如果是依賴接口,則可以隨心所欲的更換實現類。 就像SpringJDBC框架設計的dataSource注入 (新手PASS) 不同的數據庫廠商只要把自己的代碼都實現dataSource接口就可以了。 這樣無論你要切換哪個廠商的代碼,至少底層框架中的dataSource是不會改變的,框架的代碼是不會變的, 只是改變注入就可以。 這樣豈不是很爽?
引用數據類型的轉換
- 向上轉型
- 向下轉型
- instanceof運算符
內部類(新手PASS) 內部類(嵌套類) 內部類就是定義在另一個類內部的類。 內部類對于同一包中的其它類來說,內部類能夠隱藏起來。 注意: 內部類可以訪問其外部類中所有的屬性和方法 無需創建外部類的對象,即可從內部類訪問外部類的變量和方法。 必須創建內部類的對象,否則無法從外部類訪問內部類的變量和方法。 如果內部類中有和外部類同名的變量或方法,則內部類的變量和方法將獲得比外部類的變量和方法更高的優先級。 不能定義static變量 public class Outer { ? private int varOuter=100; ? class Inner { ? ? int varInner=200; ? ? public void showOuter() { ? ? ? System.out.println(varOuter); //是否能夠輸出? ? ? } ? } ? public void showInner() { ? ? ? Inner i=new Inner(); ? ? ? ? System.out.println(i.varInner); ? } } 普通類的訪問權限修飾符default public 內部類的訪問權限修飾符default public protected private 內部類的訪問 在Outer內訪問Inner,只需如下: Inner in = new Inner() ; 在Outer外訪問Inner,必須如下: Outer o = new Outer(); //實例化外部類 Outer.Inner oi = o.new Inner(); //實例化內部類 靜態內部類 用static標識的內部類為靜態內部類。 靜態內部類作為外部類的靜態成員,不能訪問外部類非靜態成員。 非靜態內部類只能定義非靜態成員,而靜態內部類可以定義靜態成員和非靜態成員。 使用Outer.Inner inn=new Outer.Inner()方式實例化靜態內部類。 非靜態內部類不可以使用上面的方式實例化 局部內部類 在一個類的方法體中或程序塊內定義的內部類 類中定義的內部類 class? A{ ? int a;? ? public void method(){ ? } ?? ? class B{ ? } } 局部內部類 class A{ ? int a; ? public void method(int c){ ? ? int b=0; ? ? class B{ ? ?? ? ? } ? } } 在方法定義的內部類中只能訪問方法中的final類型的局部變量 public class Outer2 { ? public int a = 1; ? private int b = 2; ? public void method(final int c) { ? ? int d = 3; ? ? final int e = 2; ? ? class Inner { ? ? ? private void iMethod(int e) { ? ? ? ? //System.out.println(e); ? ? ? } ? ? } ? } }
總結 今天我們主要學習了java面向對象的進階部分,包括傳說中的封裝、繼承、多態等等知識,尤其是關于接口的運用, 一些小伙伴都不是很理解為什么要使用接口,概念在于理解,而不是死記硬背,這樣才能融會貫通。
總結
以上是生活随笔為你收集整理的明翰Java教学系列之进阶面向对象篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 哔哩哔哩视频云画质与窄带高清AI落地实践
- 下一篇: 荐书 | 《大脑的奥秘:人人要懂的脑科学