1. 抽象類
1.1 抽象類概念
在面向對象的概念中,所有的對象都是通過類來描繪的,但是反過來,并不是所有的類都是用來描繪對象的,如果 一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。
以上代碼中,這個方法是否可以不用實現? 這個寫了跟沒寫是一樣的
所以我們就可以不去實現他,類似c語言中的函數聲明
但是呢,這里顯示報錯了
1,一個方法可以被修飾為abstract,此時代表這個方法可以不進行實現,被稱為抽象方法!
2,如果一個類當中包含抽象方法,此時這個類必須時抽象類。若沒有抽象方法,這個類也可以被寫為抽象類。
問題:什么情況下一個類可以被定義為一個抽象類?
答:當不能具體描述一個對象的時候
3,抽象類中定義成員和普通類沒有區別
4,抽象類不能具體的描述一個對象,在main方法中就不能被實例化
????????
問題:抽象類不可以被實例化,那么存在的意義是什么?
答:為了被繼承
5,當一個普通類繼承這個抽象類之后,必須重寫抽象類當中的抽象方法!
6,抽象類也可以發生向上轉型,動態綁定,多態!
1.2 抽象類語法
在Java中,一個類如果被 abstract 修飾稱為抽象類,抽象類中被 abstract 修飾的方法稱為抽象方法,抽象方法不用給出具體的實現體。
注意:抽象類也是類,內部可以包含普通方法和屬性,甚至構造方法
1.3 抽象類特性
1. 抽象類不能直接實例化對象(上面以講過)
2,抽象方法也需要滿足重寫的特點。被static,private,final修飾都不能發生重寫的
所以abstract和final是對立的,不可能同時存在
3. 抽象類必須被繼承,并且繼承后子類要重寫父類中的抽象方法,否則子類也是抽象類,必須要使用 abstract 修飾
當一個普通類A繼承了一個抽象類,不想重寫這個抽象方法,此時,可以把這個普通類A改為抽象類。
但是,如果A這個抽象類再次被普通類B繼承,此時B這個類,需要重新所以未被重寫的抽象方法!
4. 抽象類中不一定包含抽象方法,但是有抽象方法的類一定是抽象類
5. 抽象類中可以有構造方法,供子類創建對象時,初始化父類的成員變量
1.4 抽象類的作用
抽象類本身不能被實例化, 要想使用, 只能創建該抽象類的子類. 然后讓子類重寫抽象類中的抽象方法.
使用抽象類相當于多了一重編譯器的校驗.
使用抽象類的場景就如上面的代碼, 實際工作不應該由父類完成, 而應由子類完成. 那么此時如果不小心誤用成父類了, 使用普通類編譯器是不會報錯的. 但是父類是抽象類就會在實例化的時候提示錯誤, 讓我們盡早發現問題
2. 接口
2.1 接口的概念
在現實生活中,接口的例子比比皆是,比如:筆記本上的USB口,電源插座等。
電腦的USB口上,可以插:U盤、鼠標、鍵盤...所有符合USB協議的設備
電源插座插孔上,可以插:電腦、電視機、電飯煲...所有符合規范的設備
通過上述例子可以看出:接口就是公共的行為規范標準,大家在實現時,只要符合規范標準,就可以通用。 在Java中,接口可以看成是:多個類的公共規范,是一種引用數據類型。
2.2 語法規則
接口的定義格式與定義類的格式基本相同,將class關鍵字換成 interface 關鍵字,就定義了一個接口。
創建接口:
定義接口:interface + 接口名? ? ?會生成一個字節碼文件
1,接口的定義可以使用interface定義
2,接口當中可以有方法和屬性
但是這里報錯了。接口當中的成員變量,默認為public static final修飾的,定義的時候必須初始化
以后定義的時候,可以省略掉public static final
3,接口當中方法默認是public abstract修飾的,你不寫的時候也是抽象方法,不能有具體的實現。
4,接口當中使用default修飾的方法和static修飾的方法時可以有具體實現的 {}。
?
5,接口不可以被實例化
6,接口需要被實現,此時使用關鍵詞implements來實現。
7,當一個類實現了一個接口,那么此時這個類就要重寫這個方法。
8,接口也可以發生向上轉型,也可以發生動態綁定,也可以發生多態!
提示:
1. 創建接口時, 接口的命名一般以大寫字母 I 開頭.
2. 接口的命名一般使用 "形容詞" 詞性的單詞.
3. 阿里編碼規范中約定, 接口中的方法和屬性不要加任何修飾符號, 保持代碼的簡潔性.
2.3 接口使用
接口不能直接使用,必須要有一個"實現類"來"實現"該接口,實現接口中的所有抽象方法。
1.USB接口
2,鼠標類,實現USB接口
3,鍵盤類,實現USB接口
4,電腦類:使用USB設備及測試類
5,輸出
2.4 接口特性
1. 接口類型是一種引用類型,但是不能直接new接口的對象
2. 接口中每一個方法都是public的抽象方法, 即接口中的方法會被隱式的指定為 public abstract(只能是 public abstract,其他修飾符都會報錯)
3. 接口中的方法是不能在接口中實現的,只能由實現接口的類來實現
4. 重寫接口中方法時,不能使用默認的訪問權限
5. 接口中可以含有變量,但是接口中的變量會被隱式的指定為 public static final 變量
6. 接口中不能有靜態代碼塊和構造方法
7. 接口雖然不是類,但是接口編譯完成后字節碼文件的后綴格式也是.class
8. 如果類沒有實現接口中的所有的抽象方法,則類必須設置為抽象類
9. jdk8中:接口中還可以包含default方法。
因為test3是一個默認方法,所以可以使用testDemo調用!
test2是一個靜態方法,使用接口名去調用
static,private,final修飾的方法不能被重寫
2.5 實現多個接口
在Java中,類和類之間是單繼承的,一個類只能有一個父類,即Java中不支持多繼承,但是一個類可以實現多個接口。
這個類同時具備了IA接口的行為和IB接口的行為
Java的接口解決了Java不能多繼承的問題!
注意:一個類實現多個接口時,每個接口中的抽象方法都要實現,否則類必須設置為抽象類。
繼承表達的含義是 is - a 語義, 而接口表達的含義是具有 xxx 特性
例如:魚能游泳,狗能跑,鴨子能跑能游泳還能飛
上面的代碼展示了 Java 面向對象編程中最常見的用法: 一個類繼承一個父類, 同時實現多種接口
這樣設計有什么好處呢? 時刻牢記多態的好處, 讓程序猿忘記類型. 有了接口之后, 類的使用者就不必關注具體類型, 而只關注某個類是否具備某種能力
2.6 接口間的繼承
在Java中,類和類之間是單繼承的,一個類可以實現多個接口,接口與接口之間可以多繼承。即:用接口可以達到多繼承的目的。
IC這個接口,不僅具備了自己的testC這個功能,還擴展了IA和IB的功能
接口間的繼承相當于把多個接口合并在一起.
2.7 接口使用實例
例如:比較兩個對象誰大誰小
對于引用數據類型來說,只能通過特定的方式來比較。此時就需要使用一個接口,這個接口當中的方法可以幫助我們完成任務!
我們就有一個Comparable接口來實現
此時,可以看到源碼里面有一個compareTo方法
這個方法并沒有實現,所以就要重寫這個方法!
需要我們自定義:? ? ? ? ?------>? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 優化:
此時就可以實現兩個對象的比較
對于compareTo來說不管根據什么比較,一定是指定了一種比較方式
字符串的比較也可根據compareTo來比較
再寫一個例子:給對象數組排序
現在就是一個普通的代碼
我們發現在運行是報錯了。
在這里他并不知道要對誰進行排序,是name還是age?重大到小還是從小到大也沒說
這里說類型轉換異常,這是為什么呢?
在第320行,進入320行的源碼
發現,將a數組元素下標runHi的值取出來強轉為Comparable,所以這個類起碼要實現這個接口,才能使他強轉,問題就是現在并沒有實現這個接口,
從大到小排序:
也可以自己實現一個方法:用冒泡排序
但是呢以上的代碼都不太靈活,都已經寫死了,寫了name就只能比較name,寫了age就只能比較age。??
所以,我們可以讓他靈活一點,我想根據年齡比較就年齡比較,想根據姓名比較就姓名比較
我們把Comparator稱為比較器,在這呢是一個年齡比較器
這類的實現并沒有寫在學生類上,他很靈活,可以再寫一個name比較器
還可以加上之前默認的
通過數組傳Comparator
所以在這是可以傳一個比較器的
2.8 Cloneable 接口
Java 中內置了一些很有用的接口, Clonable 就是其中之一.
Object 類中存在一個 clone 方法, 調用這個方法可以創建一個對象的 "拷貝". 但是要想合法調用 clone 方法, 必須要先實現 Clonable 接口, 否則就會拋出 CloneNotSupportedException 異常.
前提:任何一個對象默認都是繼承于Object類的,他是所有類的父類,雙擊shift可搜索Object
例如:
進入Object類
可以發現,Object有clone這個方法,為什么在 . 的時候找不到這個方法??
這里Object是由protected修飾的,只能是不同包子類才能訪問,而且還需要super訪問,Student在Demo3這個路徑底下,Object在Java.lang這個路徑底下
所以就要重寫這里的clone,說是重寫,其實自己的clone方法里面調用了一個super????????
現在就可以調用了
但是呢為什么還是會報錯?
因為是把父類給到子類,向下轉型,需要強轉為Student? ? 那為啥還報錯?
這是異常的原因,后續會講到
現在就可以運行了? ?又為什么運行時報錯了?
要想克隆成功,必須實現一個Cloneable接口
但是呢,我們實現這個接口為什么沒有重寫方法,可進入這個接口源碼
發現,這個接口并沒有一個抽象方法,此時這個接口叫做空接口或者叫做標記接口。只要實現了這個克隆接口,才證明當前類才能被克隆!!!
淺拷貝和深拷貝:
淺拷貝:
在上面代碼中增加一代點代碼
這里只是克隆了student1所指向的對象(Student),并沒有克隆m的值,實際上student2還是指向的是student1? m的值
所以這里的輸出結果,改變了student2的值,student1的值也跟著改變了,這就叫淺拷貝!
深拷貝:
按照以上代碼,我們就需要把m的值跟著克隆一份,就不會修改student1的值了
通過上述代碼發現,是通過super.clone來克隆student1對象,定義一個tmp來接受
但是tmp里面的m并沒有克隆,所以我們需要把m也跟著克隆,那怎么樣才能去克隆m所指向的這個Money呢?? 所以Money這個類也要支持克隆
目前這個m還是student1的m,通過this去調用代表當前對象的m
2.9 抽象類和接口的區別
抽象類和接口都是 Java 中多態的常見使用方式. 都需要重點掌握. 同時又要認清兩者的區別(重要!!! 常見面試題)
核心區別: 抽象類中可以包含普通方法和普通字段, 這樣的普通方法和字段可以被子類直接使用(不必重寫), 而接口中不能包含普通方法, 子類必須重寫所有的抽象方法
如之前寫的 Animal 例子. 此處的 Animal 中包含一個 name 這樣的屬性, 這個屬性在任何子類中都是存在的. 因此此 處的 Animal 只能作為一個抽象類, 而不應該成為一個接口
3. Object類
Object是Java默認提供的一個類。Java里面除了Object類,所有的類都是存在繼承關系的。默認會繼承Object父類。即所有類的對象都可以使用Object的引用進行接收。
例如:
所以在開發之中,Object類是參數的最高統一類型。但是Object類也存在有定義好的一些方法。如下:雙擊shift,搜索Object
3.1?對象比較equals方法
此時,當前類并沒有equals這個方法,那為什么能調用,因為繼承Object的
那為什么是false(equals判斷是否一樣),這里他們比較了兩個對象的地址,所以返回false
進入equals的源碼
可以發現這兩種寫法是一樣的
發現他提供equals沒有實現,我們自己實現一個方法
所以,equals的功能是比較相不相同,在某些情況下,我們需要重寫這個方法!
比如String就重寫了!? Object原生的是比較地址的!!!
結論:比較對象中內容是否相同的時候,一定要重寫equals方法。
3.2 hashcode方法
toString方法的源碼:
我們看到了hashCode()這個方法,他幫我算了一個具體的對象位置,這里面涉及數據結構,但是我們還沒學數據結構,沒法講述,所以我們只能說它是個內存地址。
然后調用Integer.toHexString()方法,將這個地址以16進制輸出。
我們來到hashcode的源碼:
這個方法是由native修飾的,說明底層代碼是由c/c++寫的,會返回一個整數
觀察下面代碼:從代碼上看確實是兩個不同的對象,但是從業務邏輯上來說,認為是同一個人
所以,我們期望這兩個對象放到同一位置,意味著返回的整數值應該是一樣的!!!
但是這里的值并不是一樣的!
所以,我們就要重寫hashcode值
生成hashcode值就是是根據age,name生成,equals就是根據name,age同時一樣來判斷
總結:只要是自定義類型,一定要重寫hashcode方法和equals方法!!!