內(nèi)容預(yù)覽
- 接口
- 三大特性——多態(tài)
- 引用類型轉(zhuǎn)換
接口
1.1 概述
接口守谓,是Java語言中一種引用類型穿铆,是方法的集合您单,如果說類的內(nèi)部封裝了成員變量斋荞、構(gòu)造方法和成員方法,那么 接口的內(nèi)部主要就是封裝了方法虐秦,包含抽象方法(JDK 7及以前)平酿,默認方法和靜態(tài)方法(JDK 8),私有方法 (JDK 9)悦陋。
接口的定義蜈彼,它與定義類方式相似,但是使用interface
關(guān)鍵字俺驶。它也會被編譯成.class文件幸逆,但一定要明確它并 不是類,而是另外一種引用數(shù)據(jù)類型暮现。
引用數(shù)據(jù)類型:數(shù)組还绘,類,接口栖袋。
接口的使用拍顷,它不能創(chuàng)建對象,但是可以被實現(xiàn)(implements
塘幅,類似于被繼承)昔案。一個實現(xiàn)接口的類(可以看做 是接口的子類),需要實現(xiàn)接口中所有的抽象方法电媳,創(chuàng)建該類對象踏揣,就可以調(diào)用方法了,否則它必須是一個抽象 類匾乓。
1.2 定義格式
public interface 接口名稱 {
// 抽象方法
// 默認方法
// 靜態(tài)方法
// 私有方法
}
含有抽象方法
抽象方法:使用abstract
關(guān)鍵字修飾捞稿,可以省略,沒有方法體。該方法供子類實現(xiàn)使用括享。
代碼如下:
public interface InterFaceName {
public abstract void method();
}
含有默認方法和靜態(tài)方法
默認方法:使用default
修飾搂根,不可省略,供子類調(diào)用或者子類重寫铃辖。 靜態(tài)方法:使用static
修飾剩愧,供接口直接調(diào)用。
代碼如下:
public interface InterFaceName {
public default void method() {
// 執(zhí)行語句
}
public static void method2() {
// 執(zhí)行語句
}
}
含有私有方法和私有靜態(tài)方法
私有方法:使用private
修飾娇斩,供接口中的默認方法或者靜態(tài)方法調(diào)用仁卷。
代碼如下:
public interface InterFaceName {
private void method() {
// 執(zhí)行語句
}
}
1.3 基本的實現(xiàn)
實現(xiàn)的概述
類與接口的關(guān)系為實現(xiàn)關(guān)系,即類實現(xiàn)接口犬第,該類可以稱為接口的實現(xiàn)類锦积,也可以稱為接口的子類。實現(xiàn)的動作類 似繼承歉嗓,格式相仿丰介,只是關(guān)鍵字不同,實現(xiàn)使用implements
關(guān)鍵字鉴分。
非抽象子類實現(xiàn)接口:
- 必須重寫接口中所有抽象方法哮幢。
- 繼承了接口的默認方法,即可以直接調(diào)用志珍,也可以重寫暮刃。
實現(xiàn)格式:
class 類名 implements 接口名 {
// 重寫接口中抽象方法【必須】
// 重寫接口中默認方法【可選】
}
抽象方法的使用
必須全部實現(xiàn)怖亭,代碼如下:
定義接口:
public interface LiveAble {
// 定義抽象方法
public abstract void eat();
public abstract void sleep();
}
定義實現(xiàn)類:
public class Animal implements LiveAble {
@Override
public void eat() {
System.out.println("吃東西");
}
@Override
public void sleep() {
System.out.println("晚上睡");
}
}
定義測試類:
public class InterfaceDemo {
public static void main(String[] args) {
// 創(chuàng)建子類對象
Animal a = new Animal();
// 調(diào)用實現(xiàn)后的方法
a.eat();
a.sleep();
}
}
輸出結(jié)果:
吃東西
晚上睡
默認方法的使用
可以繼承,可以重寫,二選一漠魏,但是只能通過實現(xiàn)類的對象來調(diào)用抛杨。
- 繼承默認方法耘眨,代碼如下:
定義接口:
public interface LiveAble {
public default void fly(){
System.out.println("天上飛");
}
}
定義實現(xiàn)類:
public class Animal implements LiveAble {
// 繼承像云,什么都不用寫,直接調(diào)用
}
定義測試類:
public class InterfaceDemo {
public static void main(String[] args) {
// 創(chuàng)建子類對象
Animal a = new Animal();
// 調(diào)用默認方法
a.fly();
}
}
輸出結(jié)果:
天上飛
- 重寫默認方法载慈,代碼如下:
定義接口:
public interface LiveAble {
public default void fly(){
System.out.println("天上飛");
}
}
定義實現(xiàn)類:
public class Animal implements LiveAble {
@Override
public void fly() {
System.out.println("自由自在的飛");
}
}
定義測試類:
public class InterfaceDemo {
public static void main(String[] args) {
// 創(chuàng)建子類對象
Animal a = new Animal();
// 調(diào)用重寫方法
a.fly();
}
}
輸出結(jié)果:
自由自在的飛
靜態(tài)方法的使用
靜態(tài)與.class 文件相關(guān)惭等,只能使用接口名調(diào)用,不可以通過實現(xiàn)類的類名或者實現(xiàn)類的對象調(diào)用办铡,代碼如下:
定義接口:
public interface LiveAble {
public static void run(){
System.out.println("跑起來~~~");
}
}
定義實現(xiàn)類:
public class Animal implements LiveAble {
// 無法重寫靜態(tài)方法
}
定義測試類:
public class InterfaceDemo {
public static void main(String[] args) {
// Animal.run(); // 【錯誤】無法繼承方法,也無法調(diào)用
LiveAble.run();
}
}
輸出結(jié)果:
跑起來~~~
私有方法的使用
- 私有方法:只有默認方法可以調(diào)用辞做。
- 私有靜態(tài)方法:默認方法和靜態(tài)方法可以調(diào)用。
如果一個接口中有多個默認方法寡具,并且方法中有重復(fù)的內(nèi)容秤茅,那么可以抽取出來,封裝到私有方法中童叠,供默認方法 去調(diào)用框喳。從設(shè)計的角度講课幕,私有的方法是對默認方法和靜態(tài)方法的輔助。同學(xué)們在已學(xué)技術(shù)的基礎(chǔ)上五垮,可以自行測試乍惊。
定義接口:
public interface LiveAble {
default void func(){
func1();
func2();
}
private void func1(){
System.out.println("跑起來~~~");
}
private void func2(){
System.out.println("跑起來~~~");
}
}
1.4 接口的多實現(xiàn)
之前學(xué)過,在繼承體系中放仗,一個類只能繼承一個父類润绎。而對于接口而言,一個類是可以實現(xiàn)多個接口的诞挨,這叫做接 口的多實現(xiàn)莉撇。并且,一個類能繼承一個父類惶傻,同時實現(xiàn)多個接口棍郎。
實現(xiàn)格式:
class 類名 [extends 父類名] implements 接口名1,接口名2,接口名3... {
// 重寫接口中抽象方法【必須】
// 重寫接口中默認方法【不重名時可選】
}
[ ]: 表示可選操作。
抽象方法
接口中银室,有多個抽象方法時涂佃,實現(xiàn)類必須重寫所有抽象方法。如果抽象方法有重名的粮揉,只需要重寫一次巡李。代碼如 下:
定義多個接口:
interface A {
public abstract void showA();
public abstract void show();
}
interface B {
public abstract void showB();
public abstract void show();
}
定義實現(xiàn)類:
public class C implements A,B{
@Override
public void showA() {
System.out.println("showA");
}
@Override
public void showB() {
System.out.println("showB");
}
@Override
public void show() {
System.out.println("show");
}
}
默認方法
接口中抚笔,有多個默認方法時扶认,實現(xiàn)類都可繼承使用。如果默認方法有重名的殊橙,必須重寫一次辐宾。代碼如下:
定義多個接口:
interface A {
public default void methodA(){}
public default void method(){}
}
interface B {
public default void methodB(){}
public default void method(){}
}
定義實現(xiàn)類:
public class C implements A,B{
@Override
public void method() {
System.out.println("method");
}
}
靜態(tài)方法
接口中,存在同名的靜態(tài)方法并不會沖突膨蛮,原因是只能通過各自接口名訪問靜態(tài)方法叠纹。
優(yōu)先級的問題
當(dāng)一個類,既繼承一個父類敞葛,又實現(xiàn)若干個接口時誉察,父類中的成員方法與接口中的默認方法重名,子類就近選擇執(zhí) 行父類的成員方法惹谐。代碼如下:
定義接口:
interface A {
public default void methodA(){
System.out.println("AAAAAAAAAAAA");
}
}
定義父類:
class D {
public void methodA(){
System.out.println("DDDDDDDDDDDD");
}
}
定義子類:
class C extends D implements A {
// 未重寫methodA方法
}
定義測試類:
public class Test {
public static void main(String[] args) {
C c = new C();
c.methodA();
}
}
輸出結(jié)果:
DDDDDDDDDDDD
1.5 接口的多繼承【了解】
一個接口能繼承另一個或者多個接口持偏,這和類之間的繼承比較相似。接口的繼承使用 extends 關(guān)鍵字氨肌,子接口繼 承父接口的方法鸿秆。如果父接口中的默認方法有重名的,那么子接口需要重寫一次怎囚。代碼如下:
定義父接口:
interface A {
public default void method(){
System.out.println("AAAAAAAAAAAAAAAAAAA");
}
}
interface B {
public default void method(){
System.out.println("BBBBBBBBBBBBBBBBBBB");
}
}
定義子接口:
interface D extends A,B{
@Override
public default void method() {
System.out.println("DDDDDDDDDDDDDD");
}
}
子接口重寫默認方法時卿叽,default關(guān)鍵字可以保留。
子類重寫默認方法時,default關(guān)鍵字不可以保留考婴。
1.6 其他成員特點
- 接口中贩虾,無法定義成員變量,但是可以定義常量沥阱,其值不可以改變整胃,默認使用public static final修飾。
- 接口中喳钟,沒有構(gòu)造方法屁使,不能創(chuàng)建對象。
- 接口中奔则,沒有靜態(tài)代碼塊蛮寂。
1.7 小結(jié)
在Java 9+版本中,接口的內(nèi)容可以有:
- 成員變量其實是常量易茬,格式:
[public] [static] [final] 數(shù)據(jù)類型 常量名稱 = 數(shù)據(jù)值;
注意:
- 常量必須進行賦值酬蹋,而且一旦賦值不能改變。
- 常量名稱完全大寫抽莱,用下劃線進行分隔范抓。
- 接口中最重要的就是抽象方法,格式:
[public] [abstract] 返回值類型 方法名稱(參數(shù)列表);
注意:實現(xiàn)類必須覆蓋重寫接口所有的抽象方法食铐,除非實現(xiàn)類是抽象類匕垫。
- 從Java 8開始,接口里允許定義默認方法虐呻,格式:
[public] default 返回值類型 方法名稱(參數(shù)列表) { 方法體 }
注意:默認方法也可以被覆蓋重寫
- 從Java 8開始象泵,接口里允許定義靜態(tài)方法,格式:
[public] static 返回值類型 方法名稱(參數(shù)列表) { 方法體 }
注意:應(yīng)該通過接口名稱進行調(diào)用斟叼,不能通過實現(xiàn)類對象調(diào)用接口靜態(tài)方法
- 從Java 9開始偶惠,接口里允許定義私有方法,格式:
- 普通私有方法:
private
返回值類型 方法名稱(參數(shù)列表) { 方法體 }- 靜態(tài)私有方法:
private static
返回值類型 方法名稱(參數(shù)列表) { 方法體 }
注意:private的方法只有接口自己才能調(diào)用朗涩,不能被實現(xiàn)類或別人使用忽孽。
多態(tài)
2.1 概述
引入
多態(tài)是繼封裝、繼承之后谢床,面向?qū)ο蟮牡谌筇匦浴?br> 生活中兄一,比如跑的動作,小貓萤悴、小狗和大象瘾腰,跑起來是不一樣的。再比如飛的動作覆履,昆蟲蹋盆、鳥類和飛機费薄,飛起來也 是不一樣的∑芪恚可見楞抡,同一行為,通過不同的事物析藕,可以體現(xiàn)出來的不同的形態(tài)召廷。多態(tài),描述的就是這樣的狀態(tài)账胧。
定義
- 多態(tài): 是指同一行為竞慢,具有多個不同表現(xiàn)形式。
前提【重點】
- 繼承或者實現(xiàn)【二選一】
- 方法的重寫【意義體現(xiàn):不重寫治泥,無意義】
- 父類引用指向子類對象【格式體現(xiàn)】
2.2 多態(tài)的體現(xiàn)
多態(tài)體現(xiàn)的格式:
父類類型 變量名 = new 子類對象筹煮; 變量名.方法名();
父類類型:指子類對象繼承的父類類型,或者實現(xiàn)的父接口類型居夹。
代碼如下:
Fu f = new Zi();
f.method();
當(dāng)使用多態(tài)方式調(diào)用方法時败潦,首先檢查父類中是否有該方法,如果沒有准脂,則編譯錯誤劫扒;如果有,執(zhí)行的是子類重寫 后方法狸膏。
代碼如下:
定義父類:
public abstract class Animal {
public abstract void eat();
}
定義子類:
class Cat extends Animal {
public void eat() {
System.out.println("吃魚");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨頭");
}
}
定義測試類:
public class Test {
public static void main(String[] args) {
// 多態(tài)形式沟饥,創(chuàng)建對象
Animal a1 = new Cat();
// 調(diào)用的是 Cat 的 eat
a1.eat();
// 多態(tài)形式,創(chuàng)建對象
Animal a2 = new Dog();
// 調(diào)用的是 Dog 的 eat
a2.eat();
}
}
2.3 多態(tài)的好處
實際開發(fā)的過程中环戈,父類類型作為方法形式參數(shù)闷板,傳遞子類對象給方法,進行方法的調(diào)用院塞,更能體現(xiàn)出多態(tài)的擴展 性與便利。代碼如下:
定義父類:
public abstract class Animal {
public abstract void eat();
}
定義子類:
class Cat extends Animal {
public void eat() {
System.out.println("吃魚");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨頭");
}
}
定義測試類:
public class Test {
public static void main(String[] args) {
// 多態(tài)形式性昭,創(chuàng)建對象
Cat c = new Cat();
Dog d = new Dog();
// 調(diào)用showCatEat
showCatEat(c);
// 調(diào)用showDogEat
showDogEat(d);
/*
以上兩個方法, 均可以被showAnimalEat(Animal a)方法所替代
而執(zhí)行效果一致
*/
showAnimalEat(c);
showAnimalEat(d);
}
public static void showCatEat (Cat c){
c.eat();
}
public static void showDogEat (Dog d){
d.eat();
}
public static void showAnimalEat (Animal a){
a.eat();
}
}
- 由于多態(tài)特性的支持拦止,showAnimalEat方法的Animal類型,是Cat和Dog的父類類型糜颠,父類類型接收子類對象汹族,當(dāng) 然可以把Cat對象和Dog對象,傳遞給方法其兴。
- 當(dāng)eat方法執(zhí)行時顶瞒,多態(tài)規(guī)定,執(zhí)行的是子類重寫的方法元旬,那么效果自然與showCatEat榴徐、showDogEat方法一致守问, 所以showAnimalEat完全可以替代以上兩方法。
- 不僅僅是替代坑资,在擴展性方面耗帕,無論之后再多的子類出現(xiàn),我們都不需要編寫showXxxEat方法了袱贮,直接使用 showAnimalEat都可以完成仿便。
- 所以,多態(tài)的好處攒巍,體現(xiàn)在嗽仪,可以使程序編寫的更簡單,并有良好的擴展柒莉。
2.4 引用類型轉(zhuǎn)換
多態(tài)的轉(zhuǎn)型分為向上轉(zhuǎn)型與向下轉(zhuǎn)型兩種:
向上轉(zhuǎn)型
-
向上轉(zhuǎn)型:多態(tài)本身是子類類型向父類類型向上轉(zhuǎn)換的過程钦幔,這個過程是默認的。
當(dāng)父類引用指向一個子類對象時常柄,便是向上轉(zhuǎn)型鲤氢。
使用格式:
父類類型 變量名 = new 子類類型();
如:Animal a = new Cat();
向下轉(zhuǎn)型
-
向下轉(zhuǎn)型:父類類型向子類類型向下轉(zhuǎn)換的過程,這個過程是強制的西潘。
一個已經(jīng)向上轉(zhuǎn)型的子類對象卷玉,將父類引用轉(zhuǎn)為子類引用,可以使用強制類型轉(zhuǎn)換的格式喷市,便是向下轉(zhuǎn)型相种。
使用格式:
子類類型 變量名 = (子類類型) 父類變量名;
如:Cat c =(Cat) a;
為什么要轉(zhuǎn)型
當(dāng)使用多態(tài)方式調(diào)用方法時,首先檢查父類中是否有該方法品姓,如果沒有寝并,則編譯錯誤。也就是說腹备,不能調(diào)用子類擁 有衬潦,而父類沒有的方法。編譯都錯誤植酥,更別說運行了镀岛。這也是多態(tài)給我們帶來的一點"小麻煩"。所以友驮,想要調(diào)用子 類特有的方法漂羊,必須做向下轉(zhuǎn)型。
轉(zhuǎn)型演示卸留,代碼如下:
定義類:
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃魚");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨頭");
}
public void watchHouse() {
System.out.println("看家");
}
}
定義測試類:
public class Test {
public static void main(String[] args) {
// 向上轉(zhuǎn)型
Animal a = new Cat();
a.eat(); // 調(diào)用的是 Cat 的 eat
// 向下轉(zhuǎn)型
Cat c = (Cat)a;
c.catchMouse(); // 調(diào)用的是 Cat 的 catchMouse
}
}
轉(zhuǎn)型的異常
轉(zhuǎn)型的過程中走越,一不小心就會遇到這樣的問題,請看如下代碼:
public class Test {
public static void main(String[] args) {
// 向上轉(zhuǎn)型
Animal a = new Cat();
a.eat(); // 調(diào)用的是 Cat 的 eat
// 向下轉(zhuǎn)型
Dog d = (Dog)a;
d.watchHouse(); // 調(diào)用的是 Dog 的 watchHouse 【運行報錯】
}
}
這段代碼可以通過編譯耻瑟,但是運行時旨指,卻報出了 ClassCastException 赏酥,類型轉(zhuǎn)換異常!這是因為淤毛,明明創(chuàng)建了 Cat類型對象今缚,運行時,當(dāng)然不能轉(zhuǎn)換成Dog對象的低淡。這兩個類型并沒有任何繼承關(guān)系姓言,不符合類型轉(zhuǎn)換的定義。
為了避免ClassCastException的發(fā)生蔗蹋,Java提供了 instanceof 關(guān)鍵字何荚,給引用變量做類型的校驗,格式如下:
變量名 instanceof 數(shù)據(jù)類型
如果變量屬于該數(shù)據(jù)類型猪杭,返回true餐塘。
如果變量不屬于該數(shù)據(jù)類型,返回false皂吮。
所以戒傻,轉(zhuǎn)換前,我們好先做一個判斷蜂筹,代碼如下:
public class Test {
public static void main(String[] args) {
// 向上轉(zhuǎn)型
Animal a = new Cat();
a.eat(); // 調(diào)用的是 Cat 的 eat
// 向下轉(zhuǎn)型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 調(diào)用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 調(diào)用的是 Dog 的 watchHouse
}
}
}
接口多態(tài)的綜合案例
3.1 筆記本電腦
筆記本電腦(laptop)通常具備使用USB設(shè)備的功能需纳。在生產(chǎn)時,筆記本都預(yù)留了可以插入USB設(shè)備的USB接口艺挪, 但具體是什么USB設(shè)備不翩,筆記本廠商并不關(guān)心,只要符合USB規(guī)格的設(shè)備都可以麻裳。 定義USB接口口蝠,具備基本的開啟功能和關(guān)閉功能。鼠標(biāo)和鍵盤要想能在電腦上使用津坑,那么鼠標(biāo)和鍵盤也必須遵守 USB規(guī)范妙蔗,實現(xiàn)USB接口,否則鼠標(biāo)和鍵盤的生產(chǎn)出來也無法使用国瓮。
3.2 案例分析
進行描述筆記本類灭必,實現(xiàn)筆記本使用USB鼠標(biāo)、USB鍵盤
- USB接口乃摹,包含開啟功能、關(guān)閉功能
- 筆記本類跟衅,包含運行功能孵睬、關(guān)機功能、使用USB設(shè)備功能
- 鼠標(biāo)類伶跷,要實現(xiàn)USB接口掰读,并具備點擊的方法
- 鍵盤類秘狞,要實現(xiàn)USB接口,具備敲擊的方法
3.3 案例實現(xiàn)
定義USB接口:
interface USB {
void open();// 開啟功能
void close();// 關(guān)閉功能
}
定義鼠標(biāo)類:
class Mouse implements USB {
public void open() {
System.out.println("鼠標(biāo)開啟蹈集,紅燈閃一閃");
}
public void close() {
System.out.println("鼠標(biāo)關(guān)閉烁试,紅燈熄滅");
}
public void click(){
System.out.println("鼠標(biāo)單擊");
}
}
定義鍵盤類:
class KeyBoard implements USB {
public void open() {
System.out.println("鍵盤開啟,綠燈閃一閃");
}
public void close() {
System.out.println("鍵盤關(guān)閉拢肆,綠燈熄滅");
}
public void type(){
System.out.println("鍵盤打字");
}
}
定義筆記本類:
class Laptop {
// 筆記本開啟運行功能
public void run() {
System.out.println("筆記本運行");
}
// 筆記本使用usb設(shè)備减响,這時當(dāng)筆記本對象調(diào)用這個功能時,必須給其傳遞一個符合USB規(guī)則的USB設(shè)備
public void useUSB(USB usb) {
// 判斷是否有USB設(shè)備
if (usb != null) {
usb.open();
// 類型轉(zhuǎn)換,調(diào)用特有方法
if(usb instanceof Mouse){
Mouse m = (Mouse)usb郭怪;
m.click();
}else if (usb instanceof KeyBoard){
KeyBoard kb = (KeyBoard)usb;
kb.type();
}
usb.close();
}
}
public void shutDown() {
System.out.println("筆記本關(guān)閉");
}
}
測試類支示,代碼如下:
public class Test {
public static void main(String[] args) {
// 創(chuàng)建筆記本實體對象
Laptop lt = new Laptop();
// 筆記本開啟
lt.run();
// 創(chuàng)建鼠標(biāo)實體對象
Usb u = new Mouse();
// 筆記本使用鼠標(biāo)
lt.useUSB(u);
// 創(chuàng)建鍵盤實體對象
KeyBoard kb = new KeyBoard();
// 筆記本使用鍵盤
lt.useUSB(kb);
// 筆記本關(guān)閉
lt.shutDown();
}
}