核心概述:本篇我們將學習面向?qū)ο笾械慕涌诤投鄳B(tài)班眯,接口類似我們之前學習繼承時的父類或抽象類,接口與眾不同的時烁巫,接口中跟多的定義事物的功能(方法)署隘,子類或?qū)崿F(xiàn)類可以實現(xiàn)或重寫接口中的方法。而接口或繼承亚隙,則是多態(tài)的前提磁餐。合理地利用多態(tài)可以提高我們程序的可擴展性和靈活性。
第一章:接口
1.1-接口概述(了解)
什么是接口
Java中的接口是一系列方法的聲明
阿弃,是一些方法特征的集合
诊霹。
一個接口只有方法的特征(只有聲明)沒有方法的實現(xiàn)(沒有方法體),因此這些方法可以在不同的地方被不同的類實現(xiàn)渣淳,而這些實現(xiàn)可以具有不同的行為(功能)
如果說類的內(nèi)部封裝了成員變量脾还、構(gòu)造方法和成員方法,那么接口的內(nèi)部主要就是封裝了方法入愧,包含抽象方法(JDK 7及以前)荠呐,默認方法和靜態(tài)方法(JDK 8)。
總而言之,Java中的接口就是一系列方法聲明的集合蔓榄。
**為什么需要接口 **
接口的優(yōu)勢:
- 是多態(tài)的基礎
- 可以多實現(xiàn)(可以理解為多繼承)
接口是一種引用數(shù)據(jù)類型
接口的定義疲陕,它與定義類方式相似,但是使用 interface
關鍵字媚创。它也會被編譯成.class文件,但一定要明確它并不是類彤恶,而是另外一種引用數(shù)據(jù)類型钞钙。
類和接口都是java代碼鳄橘,都會轉(zhuǎn)換為字節(jié)碼文件
public class 類名.java → 類名.class
public interface 接口名.java → 接口名.class
1.2-接口的定義格式(記憶)
定義格式:關鍵字 interface
public interface 接口名稱 {
// 抽象方法
// 默認方法
// 靜態(tài)方法
}
接口中定義抽象方法
抽象方法:使用abstract
關鍵字修飾,可以省略芒炼,沒有方法體瘫怜。該方法供子類實現(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í)行語句
}
}
1.3-接口的使用方式(記憶)
我們之前學習繼承時,父類需要子類繼承斜友。而接口和繼承中父類相似炸裆,也需要一個類似子類的實現(xiàn)類來實現(xiàn)接口。
接口的實現(xiàn)
類與接口的關系為實現(xiàn)關系鲜屏,即類實現(xiàn)接口烹看,該類可以稱為接口的實現(xiàn)類,也可以稱為接口的子類洛史。
實現(xiàn)的動作類似繼承惯殊,格式相仿,只是關鍵字不同虹菲,實現(xiàn)使用 implements
關鍵字靠胜。
非抽象類實現(xiàn)接口注意事項
- 必須重寫接口中所有抽象方法。
- 繼承了接口的默認方法毕源,即可以直接調(diào)用浪漠,也可以重寫。
子類實現(xiàn)接口格式
public class 類名 implements 接口名 {
// 重寫接口中抽象方法【必須】
// 重寫接口中默認方法【可選】
}
子類實現(xiàn)接口中的
抽象方法
對于接口中定義的抽象方法霎褐,子類必須全部實現(xiàn)(重寫)址愿。代碼如下:
定義一個接口:LiveAble
public interface LiveAble {
// 定義抽象方法
public abstract void eat();
public abstract void sleep();
}
定義一個實現(xiàn)類:Animal
public class Animal implements LiveAble {
@Override
public void eat() {
System.out.println("吃東西");
}
@Override
public void sleep() {
System.out.println("晚上睡");
}
}
定義一個測試類:Test
public class Test {
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)用省艳。
直接使用默認方法娘纷,代碼如下:
定義接口:LiveAble
public interface LiveAble {
public default void fly(){
System.out.println("天上飛");
}
}
定義實現(xiàn)類:Animal
public class Animal implements LiveAble {
// 繼承,什么都不用寫跋炕,直接調(diào)用
}
定義測試類:Test
public class Test {
public static void main(String[] args) {
// 創(chuàng)建子類對象
Animal a = new Animal();
// 調(diào)用默認方法
a.fly();
}
}
/*
輸出結(jié)果:
天上飛
*/
或者重寫默認方法赖晶,代碼如下:
定義接口:LiveAble 同上
定義實現(xiàn)類:Animal
public class Animal implements LiveAble {
@Override
public void fly() {
System.out.println("自由自在的飛");
}
}
定義測試類:Test
public class Test {
public static void main(String[] args) {
// 創(chuàng)建子類對象
Animal a = new Animal();
// 調(diào)用重寫方法
a.fly();
}
}
/*
輸出結(jié)果:
自由自在的飛
*/
接口中
靜態(tài)方法
的使用
靜態(tài)與.class 文件相關,只能使用接口名調(diào)用,不可以通過實現(xiàn)類的類名或者實現(xiàn)類的對象調(diào)用遏插,代碼如下:
定義接口:LiveAble
public interface LiveAble {
public static void run(){
System.out.println("跑起來~~~");
}
}
定義實現(xiàn)類:Animal
public class Animal implements LiveAble {
// 無法重寫靜態(tài)方法
}
定義測試類:Test
public class Test {
public static void main(String[] args) {
// Animal.run(); // 【錯誤】無法繼承方法,也無法調(diào)用
LiveAble.run(); //
}
}
/*
輸出結(jié)果:
跑起來~~~
*/
接口中
不能定義成員變量
,可以定義常量
接口中捂贿,無法定義成員變量,但是可以定義常量胳嘲,其值不可以改變厂僧,默認使用public static final修飾。
定義接口:LiveAble
public interface LiveAble {
int NUM0 ; // 錯誤,必須賦值
int NUM1 =10; // 正確 , 省去了默認修飾符 public static final
public static final int NUM2= 100; // 正確 , 完整寫法
}
定義測試類:
public class Test {
public static void main(String[] args) {
System.out.println(Live.NUM1);
System.out.println(Live.NUM2);
}
}
/*
輸出結(jié)果:
10
100
*/
1.4-接口的多實現(xiàn)(記憶)
在繼承體系中了牛,一個類只能繼承一個父類(單繼承)颜屠。
而對于接口而言,一個類是可以實現(xiàn)多個接口的白魂,這叫做接口的多實現(xiàn)汽纤。
并且上岗,一個類能繼承一個父類福荸,同時實現(xiàn)多個接口。
實現(xiàn)格式
public class 類名 [extends 父類名] implements 接口名1,接口名2,接口名3... {
// 重寫接口中抽象方法【必須】
// 重寫接口中默認方法【不重名時可選】
}
[ ]中的格式: 表示可選操作肴掷。
接口多實現(xiàn)的抽象方法
接口中敬锐,有多個抽象方法時,實現(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)的默認方法
接口中痴脾,有多個默認方法時颤介,實現(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");
}
}
接口多實現(xiàn)中的靜態(tài)方法
接口中,存在同名的靜態(tài)方法并不會沖突前域,原因是只能通過各自接口名訪問靜態(tài)方法辕近。
public interface MyInterface{
public static void inter(){
system.out.println("接口靜態(tài)方法");
}
}
public class Test{
public static void main(String[] args){
//接口名直接調(diào)用
MyInterface.inter();
}
}
1.5 接口的多繼承 (記憶)
一個接口能繼承另一個或者多個接口,這和類之間的繼承比較相似匿垄。
接口的繼承使用 extends
關鍵字移宅,子接口繼承父接口的方法。如果父接口中的默認方法有重名的椿疗,那么子接口需要重寫一次漏峰。代碼如下:
定義父接口:
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");
}
}
1.6 抽象類和接口的區(qū)別(理解)
通過實例進行分析和代碼演示抽象類和接口的用法。
舉例:犬和緝毒犬
犬:
- 行為:吼叫届榄;吃飯浅乔;
緝毒犬:
- 行為:吼叫;吃飯痒蓬;緝毒童擎;
思考
由于犬分為很多種類滴劲,他們吼叫和吃飯的方式不一樣,在描述的時候不能具體化顾复,也就是吼叫和吃飯的行為不能明確班挖。
當描述行為時,行為的具體動作不能明確芯砸,這時萧芙,可以將這個行為寫為抽象行為,那么這個類也就是抽象類假丧。
可是當緝毒犬有其他額外功能時双揪,而這個功能并不在這個事物的體系中。這時可以讓緝毒犬具備犬科自身特點的同時也有其他額外功能包帚,可以將這個額外功能定義接口中渔期。
代碼
interface 緝毒{
public abstract void 緝毒();
}
//定義犬科的這個提醒的共性功能
abstract class 犬科{
public abstract void 吃飯();
public abstract void 吼叫();
}
// 緝毒犬屬于犬科一種,讓其繼承犬科渴邦,獲取的犬科的特性疯趟,
//由于緝毒犬具有緝毒功能,那么它只要實現(xiàn)緝毒接口即可谋梭,這樣即保證緝毒犬具備犬科的特性信峻,也擁有了緝毒的功能
class 緝毒犬 extends 犬科 implements 緝毒{
public void 緝毒() {
}
void 吃飯() {
}
void 吼叫() {
}
}
class 緝毒豬 implements 緝毒{
public void 緝毒() {
}
}
通過示例總結(jié)抽象類和接口的區(qū)別
相同點:
都位于繼承的頂端,用于被其他類實現(xiàn)或繼承瓮床;
都不能直接實例化對象盹舞;
都包含抽象方法,其子類都必須覆寫這些抽象方法隘庄;
區(qū)別:
抽象類為部分方法提供實現(xiàn)踢步,避免子類重復實現(xiàn)這些方法,提高代碼重用性峭沦;接口只能包含抽象方法贾虽;
一個類只能繼承一個直接父類(可能是抽象類),卻可以實現(xiàn)多個接口(接口彌補了Java的單繼承)吼鱼;
抽象類為繼承體系中的共性內(nèi)容蓬豁,接口為繼承體系中的擴展功能;
語法具體區(qū)別:
-
成員區(qū)別
- 抽象類
- 變量菇肃,常量地粪;有構(gòu)造方法;有抽象方法琐谤,也有非抽象方法
- 接口
- 常量蟆技;抽象方法
- 抽象類
-
關系區(qū)別
- 類與類
- 繼承,單繼承
- 類與接口
- 實現(xiàn),可以單實現(xiàn)质礼,也可以多實現(xiàn)
- 接口與接口
- 繼承旺聚,單繼承,多繼承
- 類與類
-
設計理念區(qū)別
-
抽象類
- 對類抽象眶蕉,包括屬性砰粹、行為
-
接口
- 對行為抽象,主要是行為
-
第二章:多態(tài)
2.1-多態(tài)概述(了解)
什么是多態(tài)造挽?
首先碱璃,多態(tài)是繼封裝、繼承之后饭入,面向?qū)ο蟮牡谌筇匦浴?/p>
多態(tài)Polymorphism嵌器,按字面意思就是“多種狀態(tài)”。
生活中谐丢,比如跑的動作爽航,小貓、小狗和大象庇谆,跑起來是不一樣的岳掐。再比如飛的動作凭疮,昆蟲饭耳、鳥類和飛機,飛起來也是不一樣的执解∧ぃ可見,同一行為衰腌,通過不同的事物新蟆,可以體現(xiàn)出來的不同的形態(tài)。多態(tài)右蕊,描述的就是這樣的狀態(tài)琼稻。
在面向?qū)ο缶幊讨校鄳B(tài)是指同一行為
饶囚,具有多個不同表現(xiàn)形式
帕翻。
多態(tài)的前提
- 繼承或者實現(xiàn)【二選一】
- 方法的重寫【意義體現(xiàn):不重寫,無意義】
- 父類引用指向子類對象【格式體現(xiàn)】
2.2-多態(tài)的語法格式(記憶)
語法格式:
父類類型 變量名 = new 子類()萝风;
變量名.方法名();
父類類型:是指子類繼承的父類類型嘀掸,或者實現(xiàn)的父接口類型。
示例代碼:
當使用多態(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的父類類型摹蘑,父類類型接收子類對象筹燕,當然可以把Cat對象和Dog對象,傳遞給方法衅鹿。
當eat方法執(zhí)行時撒踪,多態(tài)規(guī)定,執(zhí)行的是子類重寫的方法大渤,那么效果自然與showCatEat制妄、showDogEat方法一致,所以showAnimalEat完全可以替代以上兩方法泵三。
不僅僅是替代耕捞,在擴展性方面,無論之后再多的子類出現(xiàn)烫幕,我們都不需要編寫showXxxEat方法了俺抽,直接使用showAnimalEat都可以完成。
所以较曼,多態(tài)的好處磷斧,體現(xiàn)在,可以使程序編寫的更簡單捷犹,并有良好的擴展弛饭。
2.4-多態(tài)的轉(zhuǎn)型(理解)
多態(tài)的轉(zhuǎn)型分為向上轉(zhuǎn)型與向下轉(zhuǎn)型兩種:
向上轉(zhuǎn)型
多態(tài)本身是子類類型向父類類型向上轉(zhuǎn)換的過程,這個過程是默認的伏恐。
表現(xiàn)形式:當父類引用指向一個子類對象時孩哑,便是向上轉(zhuǎn)型。
父類類型 變量名 = new 子類類型();
如:Animal a = new Cat();
向下轉(zhuǎn)型
父類類型向子類類型向下轉(zhuǎn)換的過程翠桦,這個過程是強制的横蜒。
表現(xiàn)形式:一個已經(jīng)向上轉(zhuǎn)型的子類對象胳蛮,將父類引用轉(zhuǎn)為子類引用,可以使用強制類型轉(zhuǎn)換的格式丛晌,便是向下轉(zhuǎn)型仅炊。
子類類型 變量名 = (子類類型) 父類變量名;
如:
Animal a = new Cat(); // Cat 向上轉(zhuǎn)型為Animal a表示Cat轉(zhuǎn)型后的Animal類型
Cat c =(Cat) a; // 已經(jīng)向上轉(zhuǎn)型的Cat類型a,向下強制轉(zhuǎn)型為Cat
為什么還要向下轉(zhuǎn)型呢澎蛛?
當使用多態(tài)方式調(diào)用方法時抚垄,首先檢查父類中是否有該方法,如果沒有谋逻,則編譯錯誤呆馁。也就是說,不能調(diào)用子類擁有毁兆,而父類沒有的方法浙滤。編譯都錯誤,更別說運行了气堕。這也是多態(tài)給我們帶來的一點"小麻煩"纺腊。所以,想要調(diào)用子類特有的方法茎芭,必須做向下轉(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類型對象犀呼,運行時,當然不能轉(zhuǎn)換成Dog對象的薇组。
為了避免ClassCastException的發(fā)生外臂,Java提供了 instanceof
關鍵字,給引用變量做類型的校驗律胀,格式如下:
變量名 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
}
}
}
第三章:綜合案例
需求:
定義筆記本類酌毡,具備開機,關機和使用USB設備的功能蕾管。
具體是什么USB設備枷踏,筆記本并不關心,只要符合USB規(guī)格的設備都可以掰曾。
鼠標和鍵盤要想能在電腦上使用旭蠕,那么鼠標和鍵盤也必須遵守USB規(guī)范,不然鼠標和鍵盤的生產(chǎn)出來無法使用旷坦;
進行描述筆記本類掏熬,實現(xiàn)筆記本使用USB鼠標、USB鍵盤
USB接口秒梅,包含開啟功能孽江、關閉功能
筆記本類,包含運行功能番电、關機功能岗屏、使用USB設備功能
鼠標類,要符合USB接口
鍵盤類漱办,要符合USB接口
分析:
階段一:使用筆記本这刷,筆記本有運行功能,需要筆記本對象來運行這個功能
階段二:想使用一個鼠標娩井,又有一個功能使用鼠標暇屋,并多了一個鼠標對象。
階段三:還想使用一個鍵盤 洞辣,又要多一個功能和一個對象咐刨。
問題:每多一個功能就需要在筆記本對象中定義一個方法,不爽扬霜,程序擴展性極差定鸟。
解決:使用多態(tài)機制,降低鼠標著瓶、鍵盤等外圍設備和筆記本電腦的耦合性联予。
代碼:
//定義鼠標、鍵盤材原,筆記本三者之間應該遵守的規(guī)則
public interface USB {
void open();// 開啟功能
void close();// 關閉功能
}
//鼠標實現(xiàn)USB規(guī)則
public class Mouse implements USB {
public void open() {
System.out.println("鼠標開啟");
}
public void close() {
System.out.println("鼠標關閉");
}
}
//鍵盤實現(xiàn)USB規(guī)則
public class KeyBoard implements USB {
public void open() {
System.out.println("鍵盤開啟");
}
public void close() {
System.out.println("鍵盤關閉");
}
}
//定義筆記本
public class NoteBook {
// 筆記本開啟運行功能
public void run() {
System.out.println("筆記本運行");
}
// 筆記本使用usb設備沸久,這時當筆記本對象調(diào)用這個功能時,必須給其傳遞一個符合USB規(guī)則的USB設備
public void useUSB(USB usb) {
// 判斷是否有USB設備
if (usb != null) {
usb.open();
usb.close();
}
}
public void shutDown() {
System.out.println("筆記本關閉");
}
}
//測試
public class Test {
public static void main(String[] args) {
// 創(chuàng)建筆記本實體對象
NoteBook nb = new NoteBook();
// 筆記本開啟
nb.run();
// 創(chuàng)建鼠標實體對象
Mouse m = new Mouse();
// 筆記本使用鼠標
nb.useUSB(m);
// 創(chuàng)建鍵盤實體對象
KeyBoard kb = new KeyBoard();
// 筆記本使用鍵盤
nb.useUSB(kb);
// 筆記本關閉
nb.shutDown();
}
}