抽象類的產(chǎn)生:
當(dāng)編寫一個(gè)類時(shí),我們往往會(huì)為該類定義一些方法,這些方法是用來描述該類的功能具體實(shí)現(xiàn)方式,那么這些方法都有具體的方法體。
但是有的時(shí)候,某個(gè)父類只是知道子類應(yīng)該包含怎么樣的方法,但是無法準(zhǔn)確知道子類如何實(shí)現(xiàn)這些方法。比如一個(gè)圖形類應(yīng)該有一個(gè)求周長(zhǎng)的方法,但是不同的圖形求周長(zhǎng)的算法不一樣。那該怎么辦呢?
分析事物時(shí),發(fā)現(xiàn)了共性內(nèi)容,就出現(xiàn)向上抽取。會(huì)有這樣一種特殊情況,就是方法功能聲明相同,但方法功能主體不同。那么這時(shí)也可以抽取,但只抽取方法聲明,不抽取方法主體。那么此方法就是一個(gè)抽象方法。
寵物店的案例中,貓、狗、鴨子都能叫,因此將叫聲的方法主體抽象到父類中,聲明為抽象方法
1.抽象類
1.1.抽象類和抽象方法的定義
抽象類:使用abstract 關(guān)鍵字修飾的類叫做抽象類
abstract class 類名 {
}
抽象類和普通類的區(qū)別:
1.抽象類需要修飾符abstract修飾,普通方法不允許
2.普通類可以實(shí)例化,抽象類不能實(shí)例化
抽象方法:使用abstract修飾的方法叫抽象方法,抽象方法不允許有方法體。
訪問修飾符 abstract 返回類型 方法名 ();
抽象方法和普通方法的區(qū)別:
1.抽象方法需要修飾符abstract修飾,普通方法不允許
2.抽象方法沒有方法體,普通方法有方法體
注:
● 子類如果不是抽象類,則子類必須重寫抽象類中的全部抽象類方法
1.2.抽象類的規(guī)則
抽象類專門用于繼承關(guān)系,在繼承關(guān)系中充當(dāng)父類。
(1)抽象類不允許被實(shí)例化,既不讓new
為什么呢?抽象類不是一個(gè)完整的類,因?yàn)槌橄箢愔锌赡苡谐橄蠓椒?,而抽象方法沒有方法體,沒有方法體的方法是半成品,所以不允許實(shí)例化。

(2)抽象類中可以有屬性,普通方法,構(gòu)造方法,main方法
public abstract class Shape {
//抽象類中可以定義屬性
double param1;
double param2;
static final double PI = 3.14;
//抽象類中可以定義普通方法
public void sayHello(){ }
//抽象類中可以定義抽象方法
public abstract double daC();
//抽象類中可以定義main方法
public static void main(String[] args) {
}
}
(3)如果一個(gè)類中包含抽象方法,那么這個(gè)類必須是抽象類
public abstract class Shape {
//抽象方法
public abstract double daC();
}
錯(cuò)誤的情況示例:

(4)抽象類中可以沒有抽象方法
public abstract class Shape {
//抽象類中可以沒有抽象方法
}
(5)父類可以通過抽象方法要求子類實(shí)現(xiàn)抽象方法。
子類繼承抽象父類后,子類從父類繼承了抽象方法,由于此時(shí)子類中包含了抽象方法,所以子類也必須是抽象類。抽象類不允許實(shí)例化,導(dǎo)致無法創(chuàng)建子類對(duì)象。若要能夠?qū)嵗宇悓?duì)象,就必須保證子類不是抽象類。只要子類實(shí)現(xiàn)了重寫從父類繼承的抽象方法,那么子類中就沒有抽象方法了,就可以實(shí)例化了。
代碼示例:
abstract class Shape {
public static final Double PI = 3.1415926;
//定義抽象方法
public abstract double calcC(double n1,double n2);
}
class Rectangle extends Shape{
//實(shí)現(xiàn)抽象方法
@Override
public double calcC(double n1, double n2) {
return (n1 + n2) * 2;
}
}
class Circle extends Shape{
//實(shí)現(xiàn)抽象方法
@Override
public double calcC(double n1, double n2) {
return 2 * Shape.PI * n1;
}
}
public class TestAbs{
public static void main(String[] args) {
//實(shí)例化Rectangle對(duì)象
Shape shape1 = new Rectangle();
double c1 = shape1.calcC(3, 4);
System.out.println(c1);
//實(shí)例化Circle對(duì)象
shape1 = new Circle();
double c2 = shape2.calcC(3, 4);
System.out.println(c2);
}
}
1.3.誰不與abstract共存
- private:私有的方法子類是無法繼承到的,也不存在覆蓋,而abstract和private一起使用修飾方法,abstract既要子類去實(shí)現(xiàn)這個(gè)方法,而private修飾子類根本無法得到父類這個(gè)方法?;ハ嗝?。
- final:final修飾的類不允許被繼承,與abstract相悖。
- static:static是靜態(tài),只有一份,用于共享。abstract是為了讓子類重寫,是多份,用于子類私有。
1.4.抽象類的優(yōu)勢(shì)
抽象類中己經(jīng)實(shí)現(xiàn)的方法可以被其子類使用,使代碼可以被復(fù)用,同時(shí)提供了抽象方法,保證了子類具有自身的獨(dú)特性。
1.5.抽象類的局限性
在有些應(yīng)用場(chǎng)合,僅僅使用抽象類和抽象方法會(huì)有一定的局限性。下面通過“寵物店”來進(jìn)一步分析、認(rèn)識(shí)這種局限性,并學(xué)會(huì)使用接口來改進(jìn)設(shè)計(jì)。
寵物店中,貓是喵喵的叫,獵豹也是喵喵的叫,狗是汪汪的叫,鴨子是嘎嘎的叫,如果還有寵物魚,干脆不叫,因此在類圖中設(shè)計(jì)的結(jié)果如下。

此時(shí),使用抽象類就會(huì)出現(xiàn)以下問題:
第一,叫的方法不再通用,因?yàn)橛胁唤械膶櫸?/p>
第二,子類繼承寵物抽象類之后,寫出來的叫的方法可能會(huì)出現(xiàn)代碼重復(fù)的情況,如獵豹和貓都是“喵喵”叫,這時(shí)候,就不再符合代碼復(fù)用的要求。
對(duì)于第一個(gè)叫的方法不再通用問題,最自然的想法就是將叫這個(gè)方法變?yōu)槌橄蠓椒ǎ缓笥善渥宇惾?shí)現(xiàn),這樣做雖然解決了第一個(gè)問題,但是會(huì)造成代碼冗余的問題,如這里的獵豹和貓的叫方法也會(huì)一樣,也就是第二個(gè)問題更加突出。要解決上述問題,最理想的方式就是使用接口。
2.接口
2.1.接口與定義規(guī)范
接口是interface,相當(dāng)于抽象類,在繼承關(guān)系中充當(dāng)父類的角色,在接口中通過定義抽象方法來指定規(guī)范,子類去實(shí)現(xiàn)接口,要實(shí)現(xiàn)接口中的所有抽象方法。
接口可以多實(shí)現(xiàn)。也就實(shí)現(xiàn)了子類的多繼承問題。
與定義類的class不同,接口定義時(shí)需要使用interface關(guān)鍵字。
接口定義語法格式:
public interface 接口名 {
抽象方法1;
抽象方法2;
抽象方法3;
}
定義接口:
public interface IShout {
}
定義接口就是定義規(guī)范,接口中的抽象方法就是具體的規(guī)范。
2.2.實(shí)現(xiàn)類與遵循規(guī)范
接口實(shí)現(xiàn)語法格式:
class 類 implements 接口 {
重寫接口中方法
}
在類實(shí)現(xiàn)接口后,該類就會(huì)將接口中的抽象方法繼承過來,此時(shí)該類需要重寫該抽象方法,完成具體的邏輯。
實(shí)現(xiàn)接口:
interface IShout extends IFly{//extends Object 接口不是類,所以沒有默認(rèn)繼承Object
//接口沒有最高層,類的最高層是Object
//接口中不允許有普通方法,所以接口中的方法都是抽象的
/*public void sayHello(){
}*/
//接口不是類,所以沒有構(gòu)造
/*
public IShout(){
}
*/
//定義常量
String TYPE="動(dòng)物";
//定義抽象方法
void shout();
}
class Rabbit implements IShout{
//實(shí)現(xiàn)shout()方法
@Override
public void shout() {
}
//實(shí)現(xiàn)fly()方法
@Override
public void fly() {
}
}
實(shí)現(xiàn)接口就是遵循接口的規(guī)范。
2.3.接口的多實(shí)現(xiàn)
了解了接口的特點(diǎn)后,那么想想為什么要定義接口,使用抽象類描述也沒有問題,接口到底有啥用呢?
接口最重要的體現(xiàn):解決多繼承的弊端。將多繼承這種機(jī)制在java中通過多實(shí)現(xiàn)完成了。
interface Fu1{
void show1();//接口定義規(guī)范show1()
}
interface Fu2{
void show2();//接口定義規(guī)范show2()
}
class Zi implements Fu1,Fu2 {//多實(shí)現(xiàn)。同時(shí)實(shí)現(xiàn)多個(gè)接口。
public void show1(){}//子類實(shí)現(xiàn)show1()規(guī)范
public void show2(){}//子類實(shí)現(xiàn)show2()規(guī)范
}
怎么解決多繼承的弊端呢?
- 弊端:多繼承時(shí),當(dāng)多個(gè)父類中有相同功能時(shí),子類調(diào)用會(huì)產(chǎn)生不確定性。
- 其實(shí)核心原因就是在于多繼承父類中功能有主體,而導(dǎo)致調(diào)用運(yùn)行時(shí),不確定運(yùn)行哪個(gè)主體內(nèi)容。
為什么多實(shí)現(xiàn)能解決了呢?
- 因?yàn)榻涌谥械墓δ芏紱]有方法體,由子類來明確。
2.4. 類繼承類同時(shí)實(shí)現(xiàn)接口
- 接口和類之間可以通過實(shí)現(xiàn)(implements)產(chǎn)生關(guān)系
- 類與類之間可以通過繼承(extends)產(chǎn)生關(guān)系
- 當(dāng)一個(gè)類已經(jīng)繼承了一個(gè)父類,它又需要擴(kuò)展額外的功能,這時(shí)接口就派上用場(chǎng)了。
- 子類通過繼承父類擴(kuò)展功能,通過繼承擴(kuò)展的功能都是子類應(yīng)該具備的基礎(chǔ)功能。如果子類想要繼續(xù)擴(kuò)展其他類中的功能呢?這時(shí)通過實(shí)現(xiàn)接口來完成。
class Fu {
public void show(){}
}
interface Inter {
pulbic abstract void show1();
}
interface Outer {
pulbic abstract void show2();
}
class Zi extends Fu implements Inter,Outer {
public void show1() {
}
public void show2() {
}
}
接口的出現(xiàn)避免了單繼承的局限性。父類中定義的事物的基本功能。接口中定義的事物的擴(kuò)展功能。
2.5.接口的規(guī)定
- 接口中可以定義變量,但是變量必須有固定的修飾符修飾,public static final 所以接口中的變量也稱之為常量,其值不能改變。
- 接口中可以定義方法,方法也有固定的修飾符,public abstract
- 接口不可以new,不可以實(shí)例化。
- 子類必須覆蓋掉接口中所有的抽象方法后,子類才可以實(shí)例化。否則子類是一個(gè)抽象類。
- 接口不能繼承類
- 接口可以繼承接口
- 接口沒有最高層,類的最高層是Object
- 接口中不允許有普通方法,所以接口中的方法都是抽象(除了default方法和static方法)
- 接口不是類,所以沒有構(gòu)造
2.6.接口的繼承
學(xué)習(xí)類的時(shí)候,知道類與類之間可以通過繼承產(chǎn)生關(guān)系,接口和類之間可以通過實(shí)現(xiàn)產(chǎn)生關(guān)系,那么接口與接口之間會(huì)有什么關(guān)系。
多個(gè)接口之間可以使用extends進(jìn)行繼承。
interface Fu1{
void show();
}
interface Fu2{
void show1();
}
interface Fu3{
void show2();
}
interface Zi extends Fu1,Fu2,Fu3{
void show3();
}
在開發(fā)中如果多個(gè)接口中存在相同方法,這時(shí)若有個(gè)類實(shí)現(xiàn)了這些接口,那么就要實(shí)現(xiàn)接口中的方法,由于接口中的方法是抽象方法,子類實(shí)現(xiàn)后也不會(huì)發(fā)生調(diào)用的不確定性。
2.7. JDK1.8中接口的新特性
2.7.1.default方法
JDK1.8中可以定義默認(rèn)方法,默認(rèn)方法是由default關(guān)鍵字修飾的
默認(rèn)方法必須有方法實(shí)現(xiàn),也可以被重寫。
interface IShout {
//定義默認(rèn)方法,有方法實(shí)現(xiàn),子類可以直接使用,也可以重寫
public default void method(){
System.out.println(" interface default method ");
}
}
public class Rabbit implements IShout{
@Override
public void method() {
System.out.println(" rabbit method is running. ");
}
public static void main(String[] args) {
Rabbit rabbit = new Rabbit();
rabbit.method();
}
}
2.7.2.static方法
interface IShout {
//定義抽象方法,有方法實(shí)現(xiàn),可以直接使用接口名調(diào)用
public static void method(){
System.out.println(" interface static method ");
}
}
public class Rabbit implements IShout{
public static void main(String[] args) {
IShout.method();
}
}
接口中的static方法由接口名調(diào)用,static方法只有一份