Java中抽象類(lèi)和接口的用法詳解
一. 抽象類(lèi)
在面向?qū)ο蟮母拍钪?,所有的?duì)象都是通過(guò)類(lèi)來(lái)描繪的,但是反過(guò)來(lái),并不是所有的類(lèi)都是用來(lái)描繪對(duì)象的,如果 一個(gè)類(lèi)中沒(méi)有包含足夠的信息來(lái)描繪一個(gè)具體的對(duì)象,這樣的類(lèi)就是抽象類(lèi)。
1. 抽象類(lèi)的語(yǔ)法
在Java中,一個(gè)類(lèi)如果被 abstract 修飾稱(chēng)為抽象類(lèi),抽象類(lèi)中被 abstract 修飾的方法稱(chēng)為抽象方法,抽象方法不用給出具體的實(shí)現(xiàn)體。
抽象類(lèi)也是類(lèi),內(nèi)部可以包含普通方法和屬性,甚至構(gòu)造方法
// 抽象類(lèi):被abstract修飾的類(lèi)
public abstract class Shape {
// 抽象方法:被abstract修飾的方法,沒(méi)有方法體
abstract public void draw();
abstract void calcArea();
// 抽象類(lèi)也是類(lèi),也可以增加普通方法和屬性
protected double area; // 面積
public double getArea(){
return area;
}
}
2. 抽象類(lèi)的特性
- 抽象類(lèi)使用abstract來(lái)修飾
- 抽象類(lèi)當(dāng)中可以包含普通類(lèi)所能包含的成員,可以包含抽象方法
- 抽象方法使用abstract修飾,這個(gè)方法沒(méi)有具體的實(shí)現(xiàn)
- 抽象方法沒(méi)有加訪問(wèn)限定符時(shí),默認(rèn)是public.
- 抽象類(lèi)不可以被實(shí)例化
- 抽象方法不能被private、static、final修飾, 因?yàn)槌橄蠓椒ㄒ蛔宇?lèi)重寫(xiě) ,要滿(mǎn)足重寫(xiě)的規(guī)則
- 抽象類(lèi)存在的最大意義就是為了被繼承
- 如果一個(gè)普通類(lèi)繼承了一個(gè)抽象類(lèi),此時(shí)必須重寫(xiě)抽象類(lèi)中的抽象方法
- 如果一個(gè)抽象類(lèi)A繼承了一個(gè)抽象類(lèi)B,此時(shí)A當(dāng)中可以不重寫(xiě)B(tài)中的抽象方法;但是如果再被普通類(lèi)繼承,就需要重寫(xiě)A和B中所有的抽象方法
- 抽象類(lèi)中不一定包含抽象方法,但是有抽象方法的類(lèi)一定是抽象類(lèi)
- 抽象類(lèi)中可以有構(gòu)造方法,供子類(lèi)創(chuàng)建對(duì)象時(shí),初始化抽象類(lèi)的成員變量
abstract class Shape {
//抽象方法
public abstract void draw();
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("畫(huà)一個(gè)矩形!");
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("畫(huà)一個(gè)圓!");
}
}
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("畫(huà)一個(gè)三角形!");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("畫(huà)一朵?!");
}
}
public class Test {
//向上轉(zhuǎn)型實(shí)現(xiàn)多態(tài)
public static void drawMap(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Rect rect = new Rect();
drawMap(rect);
drawMap(new Cycle());
drawMap(new Triangle());
drawMap(new Flower());
}
}
3. 抽象類(lèi)的作用
抽象類(lèi)本身不能被實(shí)例化, 要想使用, 只能創(chuàng)建該抽象類(lèi)的子類(lèi). 然后讓子類(lèi)重寫(xiě)抽象類(lèi)中的抽象方法; 使用抽象類(lèi)相當(dāng)于多了一重編譯器的校驗(yàn)。
使用抽象類(lèi)的場(chǎng)景思考,代碼中的實(shí)際工作不應(yīng)該由父類(lèi)完成, 而應(yīng)由子類(lèi)完成;那么此時(shí)如果不小心誤用成父類(lèi) 了, 使用普通類(lèi)編譯器是不會(huì)報(bào)錯(cuò)的;但是父類(lèi)是抽象類(lèi)就會(huì)在實(shí)例化的時(shí)候提示錯(cuò)誤, 讓我們盡早發(fā)現(xiàn)問(wèn)題.
很多語(yǔ)法存在的意義都是為了 “預(yù)防出錯(cuò)”, 例如 final 關(guān)鍵字也是類(lèi)似;創(chuàng)建的變量用戶(hù)不去修改, 不 就相當(dāng)于常量嘛? 但是加上 final 能夠在不小心誤修改的時(shí)候, 讓編譯器及時(shí)提醒我們.
充分利用編譯器的校驗(yàn), 在實(shí)際開(kāi)發(fā)中是非常有意義的.
二. 接口
1. 接口的概念
在現(xiàn)實(shí)生活中,接口的例子比比皆是,比如:筆記本上的USB口,電源插座等。

電腦的USB口上,可以插:U盤(pán)、鼠標(biāo)、鍵盤(pán)…所有符合USB協(xié)議的設(shè)備
電源插座插孔上,可以插:電腦、電視機(jī)、電飯煲…所有符合規(guī)范的設(shè)備
通過(guò)上述例子可以看出:接口就是公共的行為規(guī)范標(biāo)準(zhǔn),大家在實(shí)現(xiàn)時(shí),只要符合規(guī)范標(biāo)準(zhǔn),就可以通用。
在Java中,接口可以看成是:多個(gè)類(lèi)的公共規(guī)范,是一種引用數(shù)據(jù)類(lèi)型。
Java接口是一系列方法的聲明,是一些方法特征的集合,一個(gè)接口只有方法的特征沒(méi)有方法的實(shí)現(xiàn),因此這些方法可以在不同的地方被不同的類(lèi)實(shí)現(xiàn),而這些實(shí)現(xiàn)可以具有不同的行為(功能)。
接口可以理解為一種特殊的類(lèi),里面全部是由全局常量和公共的抽象方法所組成。接口是解決Java無(wú)法使用多繼承的一種手段,但是接口在實(shí)際中更多的作用是制定標(biāo)準(zhǔn)的?;蛘呶覀兛梢灾苯影呀涌诶斫鉃?00%的抽象類(lèi),既接口中的方法必須全部是抽象方法。(JDK1.8之前可以這樣理解)
2. 語(yǔ)法規(guī)則
接口的定義格式與定義類(lèi)的格式基本相同,將class關(guān)鍵字換成 interface關(guān)鍵字,就定義了一個(gè)接口。
public interface 接口名稱(chēng){
// 抽象方法
public abstract void method1();
// public abstract 是固定搭配,可以不寫(xiě)
public void method2();
abstract void method3();
void method4();
// 注意:在接口中上述寫(xiě)法都是抽象方法,跟推薦方式4,代碼更簡(jiǎn)潔
}
小建議:
創(chuàng)建接口時(shí), 接口的命名一般以大寫(xiě)字母 I 開(kāi)頭.
接口的命名一般使用 “形容詞” 詞性的單詞.
阿里編碼規(guī)范中約定, 接口中的方法和屬性不要加任何修飾符號(hào), 保持代碼的簡(jiǎn)潔性.
3. 接口的使用
接口不能直接使用,必須要有一個(gè)"實(shí)現(xiàn)類(lèi)"來(lái)"實(shí)現(xiàn)"該接口,實(shí)現(xiàn)接口中的所有抽象方法。
子類(lèi)和父類(lèi)之間是extends 繼承關(guān)系,類(lèi)與接口之間是 implements 實(shí)現(xiàn)關(guān)系。
public class 類(lèi)名稱(chēng) implements 接口名稱(chēng){
// ...
}
請(qǐng)實(shí)現(xiàn)筆記本電腦使用USB鼠標(biāo)、USB鍵盤(pán)的例子
- USB接口:包含打開(kāi)設(shè)備、關(guān)閉設(shè)備功能
- 筆記本類(lèi):包含開(kāi)機(jī)功能、關(guān)機(jī)功能、使用USB設(shè)備功能
- 鼠標(biāo)類(lèi):實(shí)現(xiàn)USB接口,并具備點(diǎn)擊功能
- 鍵盤(pán)類(lèi):實(shí)現(xiàn)USB接口,并具備輸入功能
// USB接口
public interface USB {
void openDevice();
void closeDevice();
}
// 鼠標(biāo)類(lèi),實(shí)現(xiàn)USB接口
public class Mouse implements USB {
@Override
public void openDevice() {
System.out.println("打開(kāi)鼠標(biāo)");
}
@Override
public void closeDevice() {
System.out.println("關(guān)閉鼠標(biāo)");
}
public void click(){
System.out.println("鼠標(biāo)點(diǎn)擊");
}
}
// 鍵盤(pán)類(lèi),實(shí)現(xiàn)USB接口
public class KeyBoard implements USB {
@Override
public void openDevice() {
System.out.println("打開(kāi)鍵盤(pán)");
}
@Override
public void closeDevice() {
System.out.println("關(guān)閉鍵盤(pán)");
}
public void inPut(){
System.out.println("鍵盤(pán)輸入");
}
}
// 筆記本類(lèi):使用USB設(shè)備
public class Computer {
public void powerOn(){
System.out.println("打開(kāi)筆記本電腦");
}
public void powerOff(){
System.out.println("關(guān)閉筆記本電腦");
}
public void useDevice(USB usb){
usb.openDevice();
if(usb instanceof Mouse){
Mouse mouse = (Mouse)usb;
mouse.click();
}else if(usb instanceof KeyBoard){
KeyBoard keyBoard = (KeyBoard)usb;
keyBoard.inPut();
}
usb.closeDevice();
}
}
// 測(cè)試類(lèi):
public class TestUSB {
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();
// 使用鼠標(biāo)設(shè)備
computer.useDevice(new Mouse());
// 使用鍵盤(pán)設(shè)備
computer.useDevice(new KeyBoard());
computer.powerOff();
}
}
4. 接口的特性
1.接口類(lèi)型是一種引用類(lèi)型,使用interface來(lái)修飾,但是不能直接new接口的對(duì)象
2.類(lèi)和接口之間用implements來(lái)實(shí)現(xiàn)
3.接口中每一個(gè)方法都是public的抽象方法, 即接口中的方法會(huì)被隱式的指定為 public abstract(只能是public abstract,其他修飾符都會(huì)報(bào)錯(cuò))
- 要注意的是從JDK1.8開(kāi)始,允許有可以實(shí)現(xiàn)的方法,但這個(gè)方法只能是由default修飾的
- JDK1.8中: 接口中可以有靜態(tài)的方法
4.接口中的方法是不能在接口中實(shí)現(xiàn)的,只能由實(shí)現(xiàn)接口的類(lèi)來(lái)實(shí)現(xiàn)
5.重寫(xiě)接口中方法時(shí),不能使用default訪問(wèn)權(quán)限修飾
6.接口中可以含有變量,但是接口中的變量會(huì)被隱式的(默認(rèn))指定為 public static final 變量
7.實(shí)現(xiàn)類(lèi)重寫(xiě)接口中的抽象方法,必須加上public來(lái)修飾
8.接口中不能有靜態(tài)代碼塊和構(gòu)造方法
9.接口雖然不是類(lèi),但是接口編譯完成后字節(jié)碼文件的后綴格式也是.class
10.如果不想實(shí)現(xiàn)接口當(dāng)中的抽象方法,那么實(shí)現(xiàn)類(lèi)必須設(shè)置為抽象類(lèi);但是如果這個(gè)類(lèi)再被其它類(lèi)繼承,那么必須重寫(xiě)抽象方法
11.一個(gè)類(lèi)可以實(shí)現(xiàn)多個(gè)接口,使用implements用逗號(hào)隔開(kāi)
5. 實(shí)現(xiàn)多個(gè)接口
在Java中,類(lèi)和類(lèi)之間是單繼承的,一個(gè)類(lèi)只能有一個(gè)父類(lèi),即Java中不支持類(lèi)的多繼承,但是一個(gè)類(lèi)可以實(shí)現(xiàn)多個(gè)接口,使用implements用逗號(hào)隔開(kāi);可以用接口達(dá)到多繼承的效果,解決了Java中類(lèi)不支持多繼承的問(wèn)題。
下面的代碼展示了 Java 面向?qū)ο缶幊讨凶畛R?jiàn)的用法: 一個(gè)類(lèi)繼承一個(gè)父類(lèi), 同時(shí)也可以實(shí)現(xiàn)多種接口.
//動(dòng)物類(lèi)
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
//提供一組接口, 分別表示 "會(huì)飛的", "會(huì)跑的", "會(huì)游泳的".
interface IFlying {
void fly();
}
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
//貓, 是會(huì)跑的.
class Cat extends Animal implements IRunning {
public Cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在用四條腿跑");
}
}
//魚(yú), 是會(huì)游的.
class Fish extends Animal implements ISwimming {
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(this.name + "正在用尾巴游泳");
}
}
//青蛙, 既能跑, 又能游
class Frog extends Animal implements IRunning, ISwimming {
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在往前跳");
}
@Override
public void swim() {
System.out.println(this.name + "正在蹬腿游泳");
}
}
//鴨子,可以飛、跑和游泳
class Duck extends Animal implements IRunning, ISwimming, IFlying {
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在用翅膀飛");
}
@Override
public void run() {
System.out.println(this.name + "正在用兩條腿跑");
}
@Override
public void swim() {
System.out.println(this.name + "正在漂在水上");
}
}繼承表達(dá)的含義是 is - a 語(yǔ)義, 而接口表達(dá)的含義是 具有 xxx 特性 .
貓是一種動(dòng)物, 具有會(huì)跑的特性.
青蛙也是一種動(dòng)物, 既能跑, 也能游泳
鴨子也是一種動(dòng)物, 既能跑, 也能游, 還能飛
這樣的設(shè)計(jì)充分體現(xiàn)出多態(tài)的好處, 我們可以"忘記"類(lèi)型; 有了接口之后, 類(lèi)的使用者就不必關(guān)注具體類(lèi)型, 而只關(guān)注某個(gè)類(lèi)是否具備某種能力;通過(guò)接口可以讓其具備這種能力,進(jìn)而通過(guò)接口引用實(shí)現(xiàn)多態(tài)。
比如下面的funRun方法
public static void funRun(IRunning running) {
running.run();
}
在這個(gè)方法內(nèi)部, 我們并不關(guān)注到底是哪種動(dòng)物, 只要參數(shù)是會(huì)跑的就行
public class Test {
public static void funRun(IRunning running) {
running.run();
}
public static void main(String[] args) {
Cat cat = new Cat("小貓");
funRun(cat);
Frog frog = new Frog("小青蛙");
funRun(frog);
}
}

甚至參數(shù)可以不是 “動(dòng)物”, 只要會(huì)跑!
class Robot implements IRunning {
private String name;
public Robot(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + "正在用輪子跑");
}
}
public class Test {
public static void funRun(IRunning running) {
running.run();
}
public static void main(String[] args) {
Robot robot = new Robot("機(jī)器人");
funRun(robot);
}
}

6. 接口間的繼承
在Java中,類(lèi)和類(lèi)之間是單繼承的,一個(gè)類(lèi)可以實(shí)現(xiàn)多個(gè)接口,接口與接口之間可以多繼承。
用接口可以達(dá)到多繼承的目的。 接口可以繼承一個(gè)接口, 達(dá)到復(fù)用的效果. 使用 extends 關(guān)鍵字.
接口間的繼承相當(dāng)于把多個(gè)接口合并在一起.
interface IFLying {
void flying();
}
interface ISwimming {
void swimming();
}
interface IRunning {
void running();
}
//把IRunning,ISwimming,Iflying全部繼承到IThreehabitat上
interface IThreehabitat extends IFLying,IRunning,ISwimming{
}
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
}
class Duck extends Animal implements IThreehabitat {
public Duck(String name) {
super(name);
}
@Override
public void flying() {
System.out.println(name + "正在飛!");
}
@Override
public void swimming() {
System.out.println(name + "正在游泳!");
}
@Override
public void running() {
System.out.println(name + "正在跑!");
}
}
public class Test {
public static void func(IThreehabitat iThreehabitat) {
iThreehabitat.flying();
iThreehabitat.running();
iThreehabitat.swimming();
}
public static void main(String[] args) {
func(new Duck("小黃"));
}
}
三. 抽象類(lèi)和接口的區(qū)別
核心區(qū)別:
抽象類(lèi)中可以包含普通方法和普通字段, 這樣的普通方法和字段可以被子類(lèi)直接使用(不必重寫(xiě)), 而接口中不能包含普通方法, 子類(lèi)必須重寫(xiě)所有的抽象方法
相同點(diǎn):
- 都不能被實(shí)例化。
- 接口的實(shí)現(xiàn)類(lèi)和抽象類(lèi)的子類(lèi)只有全部實(shí)現(xiàn)了接口或者抽象類(lèi)中的抽象方法后才可以被實(shí)例化。
不同點(diǎn):
- 抽象類(lèi)可以有構(gòu)造方法,接口中不能有構(gòu)造方法。
- 接口只能定義抽象方法不能實(shí)現(xiàn)方法,抽象類(lèi)既可以定義抽象方法,也可以實(shí)現(xiàn)方法。
- 抽象類(lèi)中可以包含靜態(tài)方法,接口中不能包含靜態(tài)方法。
- 抽象類(lèi)中可以有普通成員變量,接口中沒(méi)有普通成員變量,接口中的所有成員變量為public static final修飾的靜態(tài)常量。
- 接口可以被多重實(shí)現(xiàn),抽象類(lèi)只能被單一繼承。

到此這篇關(guān)于Java中抽象類(lèi)和接口的用法詳解的文章就介紹到這了,更多相關(guān)Java抽象類(lèi) 接口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot+Mybatis+Pagehelper分頁(yè)實(shí)現(xiàn)
本篇文章主要講述的是Spring Boot+Mybatis+Pagehelper分頁(yè)實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
IDEA在創(chuàng)建包時(shí)如何把包分開(kāi)實(shí)現(xiàn)自動(dòng)分層(方法詳解)
這篇文章主要介紹了IDEA在創(chuàng)建包時(shí)如何把包分開(kāi)實(shí)現(xiàn)自動(dòng)分層,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
java實(shí)現(xiàn)釘釘機(jī)器人消息推送的示例代碼
這篇文章主要介紹了java實(shí)現(xiàn)釘釘機(jī)器人消息推送的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
SpringBoot調(diào)用service層的三種方法
在Spring?Boot中,我們可以通過(guò)注入Service層對(duì)象來(lái)調(diào)用Service層的方法,Service層是業(yè)務(wù)邏輯的處理層,它通常包含了對(duì)數(shù)據(jù)的增刪改查操作,本文給大家介紹了SpringBoot調(diào)用service層的三種方法,需要的朋友可以參考下2024-05-05
SpringCloud FeignClient 超時(shí)設(shè)置
FeignClient?默認(rèn)的超時(shí)時(shí)間可能不滿(mǎn)足你的需求,你可以通過(guò)幾種方式來(lái)自定義這些超時(shí)設(shè)置,具有一定的參考價(jià)值,感興趣的可以了解一下2024-08-08
使用Spring?Retry實(shí)現(xiàn)業(yè)務(wù)異常重試
在系統(tǒng)中經(jīng)常遇到業(yè)務(wù)重試的邏輯,比如三方接口調(diào)用,timeout重試三遍,異常處理重試的兜底邏輯等,本文給大家介紹一下如何使用Spring?Retry優(yōu)雅的實(shí)現(xiàn)業(yè)務(wù)異常重試,需要的朋友可以參考下2024-01-01

