【JAVA基礎】一個案例搞懂類、對象、重載、封裝、繼承、多態、覆蓋、抽象和接口概念及區別

0 前言

初學JAVA時,總會對一些概念只知其一;不知其二,相互混淆,不明其設計的用意,如類、對象、重載、封裝、繼承、多態、覆蓋、抽象類、接口概念。爲便於理解和鞏固,本文將基於一個案例及其變形,展示各個概念的定義、設計用意、使用規範和注意事項。html

長文警告,建議先收藏後閱讀!java

1 類(Class)、對象(Object)和構造器(Constructor)

1.1 案例

要求設計一個矩形的面積計算器,輸入爲矩形的高(height)和寬(width),輸出爲矩形的面積(area)。安全

1.2 代碼

對JAVA的語法有最基本的瞭解後能夠寫出以下代碼:ide

class Rectangle{                                //建立矩形類
    public double height;                       //定義類的成員變量
    public double width;                           
    
    public Rectangle() {                        //定義無參構造器--可省略
    }
    
    public void calcuArea() {                   //定義類的方法--面積計算
        System.out.println("面積爲:"+height*width);
    }
}

public class Test{
    public static void main(String[] args) {
        Rectangle rec=new Rectangle();          //建立矩形對象--調用構造器
        rec.height=1;                           //高度賦值
        rec.width=2;                            //寬度賦值
        rec.calcuArea();                        //調用面積計算方法
    }
}

1.3 代碼分析

以上代碼包含如下概念:
一、類(Class):類是構造對象的模板或藍圖,其由成員變量和方法構成,前者記錄數據,後者記錄數據的操做過程。
二、對象(Object):對象是類的實例化,一個類能夠有多個對象。
三、構造器(Constructor):構造器是一種特殊的方法,用於對象實例化時的初始化操做。模塊化

注意:函數

  • 構造器老是和關鍵字「new」一塊兒使用。
  • 構造器的方法名必須和類名一致!
  • 任意一個類都包含至少一個構造器,當沒有自定義構造器時,編譯器會自動設定爲無參構造器;但若是自定義了有參構造器,並須要調用無參構造器時,就必須本身手動寫。
  • 構造器中,this()用於調用同一類中的其餘構造方法;super()用於調用父類的構造方法。都必須位於構造方法中的第一行,且二者不能同時存在。

思考:以上代碼雖然實現了基本功能,但其功能不夠完善,好比當用戶輸入的高度和寬度爲字符串時,代碼就會報錯,那怎麼才能在不給用戶輸入添加麻煩的狀況下實現功能呢?優化

2 重載(Overload)

2.1 案例

要求設計一個矩形的面積計算器,輸入爲矩形的高和寬(數字或者字符串輸入,假定用戶輸入的字符串都是數值型字符串),輸出爲矩形的面積。this

2.2 代碼

class Rectangle{                                    
    public double height;                           
    public double width;                           
    
    //定義無參構造器--可省略
    public Rectangle() {                            
    }
    
    //定義有參構造器1
    public Rectangle(double height,double width) { 
        this.height=height;       //this指代當前對象,用於區分方法中的形參
        this.weight=width;
    }
    
    //定義有參構造器2
    public Rectangle(String height,String width) { 
        this.height=Double.valueOf(height);
        this.weight=Double.valueOf(width);
    }
    
    public void calcuArea() {                   
        System.out.println("面積爲:"+height*width);
    }
}

public class Test{
    public static void main(String[] args) {
        Rectangle rec=new Rectangle(1,2);       //建立矩形對象--調用構造器1
        rec.calcuArea();                        //調用面積計算方法
        
        Rectangle rec1=new Rectangle("1","2");  //建立矩形對象--調用構造器2
        rec1.calcuArea();                       //調用面積計算方法
    }
}

2.3 代碼分析

以上代碼包含如下概念:
重載(Overload):指類中多個方法具備相同的名字,但參數類型或返回類型不一樣的現象。重載的設定是爲了方便用戶操做,以相同的方法名實現特定的功能,同時匹配不一樣的參數類型以知足功能的擴展性和需求的多樣性。在上面的代碼中咱們實現了構造器方法的重載,完美解決了用戶輸入多樣性的問題,但重載並不侷限於構造器方法,它能夠適用於類中的任何方法。spa

注意:設計

  • 不容許方法名和參數類型相同,但返回類型不一樣的狀況出現,由於程序沒法判斷返回哪個。
  • 方法的形參類型相同但順序不一樣也構成重載,如add(double a,int b)和add(int a,double b)。

思考:以上代碼雖然解決了以前提出的問題,可是仍存在一個巨大的安全隱患,即用戶能夠直接經過「rec.height」和「rec.width」對矩形的高和寬賦值,這會致使兩個咱們不肯意看到的情景。一是,當用戶輸入很是規數值(好比-1)時,計算的結果是沒有意義的;二是,後期更改程序時,難以調試,好比咱們後期要將height的變量類型改成String類型,那麼就必須更改每一處height賦值的地方(如代碼1.2)。那麼,如何改進呢?

3 封裝(Encapsulation)

3.1 案例

要求設計一個矩形的面積計算器,輸入爲矩形的高和寬(數值輸入),且數值都應大於零,輸出爲矩形的面積。同時要避免2.3中所述的問題。

3.2 代碼

class Rectangle{
    private double height;                          //私有化實例字段
    private double width;
    
    public void setHeight(double height) {          //定義更改器方法
        if (height<0) {
            this.height =0;
        }
        else {
            this.height = height;
        }
    }
    public void setWidth(double width) {
        if (width<0) {
            this.width =0;
        }
        else {
            this.width = width;
        }
    }
    public double getHeight() {                     //定義訪問器方法
        return height;
    }
    public double getWidth() {
        return width;
    }
    public void calcuArea() {
        System.out.println("面積爲:"+height*width);
    }
}

public class Test{
    public static void main(String[] args) {
        Rectangle rec=new Rectangle();
        rec.setHeight(-1);                         //設定高
        rec.setWidth(2);                           //設定寬
        rec.calcuArea();   
        //讀取高和寬
        System.out.println("高爲:"+rec.getHeight()+" 寬爲:"+rec.getWidth());
    }
}

3.3 代碼分析

以上代碼包含如下概念:
封裝(Encapsulation):經過私有化類的成員變量,並建立相應的公有化的更改器(即設定成員變量的獨立方法,如setHeight)和訪問器(即讀取成員變量的獨立方法,如getHeight)實現對成員變量的封裝。

設計用意:

  • 成員變量私有化的設計,使用戶沒法經過「rec.height」和「rec.width」對矩形的高和寬賦值與讀取。
  • 更改器和訪問器公有化的設計,使用戶能夠隨意調用,實現對成員變量的賦值與讀取。
  • 在更改器中咱們能夠設定數值規範,避免因用戶的錯誤輸入所帶來的問題。
  • 在後期調試中,咱們只須要改寫類的成員變量類型及相應的更改器即可,無需修改每一個實例化對象的相關代碼。

4 繼承(Inheritance)

4.1 案例

我們稍微加一點難度。
如今要求設計一個面積計算器,計算的對象不只僅是矩形,還包括平行四邊形。輸入仍然爲高和寬(假定都是規範的數值輸入),輸出爲面積。

4.2 代碼

爲了簡化代碼,前面已經實現過的內容(好比封裝)就再也不展開寫了。

class Rectangle{                                  //定義矩形類
    public double height; 
    public double width;
    
    public Rectangle(double height,double width) {
        this.height=height;
        this.width=width;
    }
    
    public void calcuArea() {
        System.out.println("矩形的面積爲:"+height*width);
    }
}

class Parallelogram{                               //定義平行四邊形類
    public double height; 
    public double width;
    
    public Parallelogram(double height,double width) {
        this.height=height;
        this.width=width;
    }
    
    public void calcuArea() {
        System.out.println("平行四邊形的面積爲:"+height*width);
    }
}

public class Test {
    public static void main(String[] args) {
        Rectangle rec=new Rectangle(1, 2);
        rec.calcuArea();
        Parallelogram par=new Parallelogram(1, 3);
        par.calcuArea();
    }
}

4.3 代碼分析

思考:以上代碼寫起來挺簡單,可是有個問題,就是代碼重複率過高!這才兩個類,要是有成百上千個這種類,不只寫的累死,後期維護也得累死。

有沒有什麼偷懶的辦法呢?固然有啦,就是後面要介紹的繼承。

4.4 改進版代碼

class Quadrangle{                               //定義父類--四邊形類
    public double height; 
    public double width;
    public String name;
    
    public Quadrangle(double height,double width,String name) {
        this.height=height;
        this.width=width;
        this.name=name;
    }
    
    public void calcuArea() {
        System.out.println(name+"面積爲:"+height*width);
    }
}

class Rectangle extends Quadrangle{             //定義子類--矩形類
    public Rectangle(double height,double width,String name) {
        super(height,width,name);               //super指調用父類的構造方法
    }
}

class Parallelogram extends Quadrangle{         //定義子類--平行四邊形類
    public Parallelogram(double height,double width,String name) {
        super(height,width,name);
    }
}

public class Test {
    public static void main(String[] args) {
        Rectangle rec=new Rectangle(1, 2,"矩形");
        rec.calcuArea();
        Parallelogram par=new Parallelogram(1, 3,"平行四邊形");
        par.calcuArea();
    }
}

4.5 改進版代碼分析

誒?咋一看,好像並無省事哦?
那是由於咱們這裏只有兩個子類,若是換成實現成百上千個這種子類,差距就會拉開了。我們來分析一下這是如何實現的。

以上代碼包含如下概念:
繼承(Inheritance):即基於已有的類(稱之爲父類或超類)來建立新的類(稱之爲子類),顧名思義,子類將繼承父類全部的成員變量及方法。繼承通常應用於類與類較類似的狀況下,好比本案例中,矩形類與平行四邊形類的成員變量和方法高度類似,能夠提取二者的共同代碼,構造一個四邊形類做爲父類,從而避免了重複代碼,也方便了後期功能的擴展及維護。

注意:

  • JAVA中類只能單繼承(即只能繼承一個父類),但不只限於一個層次,Object是全部類的終極父類,是萬物之源。
  • 子類繼承父類時須要用到關鍵字「extends」。
  • 子類能繼承父類全部的成員變量和方法(除了父類的構造方法),但不見得能夠直接訪問(好比,父類私有的屬性和方法)。
  • 若父類定義了構造函數,子類須要用super調用父類的構造方法,且必須位於子類構造方法中的第一行。
  • 子類繼承了父類,不表明只能用父類的東西,還能夠定義本身的成員變量及方法,甚至於能夠改寫父類的方法(即後文講到的覆蓋)。

5 覆蓋(Override)和多態(Polymorphism)

5.1 案例

我們稍微再加一點難度。
如今要求設計一個面積計算器,計算的對象不只僅是矩形和平行四邊形,還包括梯形。矩形和平行四邊形的輸入爲高和寬,梯形的輸入爲高、上底長和下底長(假定都是規範的數值輸入),輸出都爲面積。
規定:矩形和平行四邊形的面積計算公式爲寬x高;梯形的面積計算公式爲(上底+下底)x 高/2

5.2 代碼

思考:咱們仍然能夠採用繼承來實現,可是梯形的面積計算方法與矩形和平行四邊形不一樣,如何以最簡潔的方法實現代碼?

具體代碼以下:

class Quadrangle{                               //定義父類--四邊形類
    public double height; 
    public double width;
    public String name;
    
    public Quadrangle(double height,double width,String name) {
        this.height=height;
        this.width=width;
        this.name=name;
    }
    
    public void calcuArea() {
        System.out.println(name+"面積爲:"+height*width);
    }
}

class Rectangle extends Quadrangle{             //定義子類--矩形類
    public Rectangle(double height,double width,String name) {
        super(height,width,name);               
    }
}

class Parallelogram extends Quadrangle{         //定義子類--平行四邊形類
    public Parallelogram(double height,double width,String name) {
        super(height,width,name);
    }
}

class Trapezoid extends Quadrangle{             //定義子類--梯形類
    public double width_up;                     //自定義成員變量--上底寬
    public Trapezoid(double height,double width_up,double width_down,String name) {
        super(height,width_down, name);
        this.width_up=width_up;
    }
    @Override
    public void calcuArea() {                   //覆蓋父類的面積計算方法
        System.out.println(name+"面積爲:"+height*(width_up+width)/2);
    }
}
public class Test {
    public static void main(String[] args) {
        Rectangle rec=new Rectangle(1, 2,"矩形");
        rec.calcuArea();
        Parallelogram par=new Parallelogram(1, 3,"平行四邊形");
        par.calcuArea();
        Trapezoid tra=new Trapezoid(1, 1, 2,"梯形");
        tra.calcuArea();
    }
}

5.3 代碼分析

以上代碼用到了如下概念:
覆蓋(Override):指在繼承中,父類的有些方法在子類中不適用,子類從新定義的手段。在本案例中,梯形類對calcuArea方法實現了覆蓋。

注意:

  • 若子類中被「覆蓋」方法的參數類型不一樣,返回類型不一致,這不是覆蓋,而是重載。覆蓋要求參數類型必須同樣,且返回類型必須兼容。總之,子類對象得保證可以執行父類的一切。
  • 不能下降覆蓋方法的存取權限,如public變成private。
  • 若不但願父類的某個方法被子類覆蓋,能夠用final修飾該方法。甚至能夠擴展到將類用final修飾,則其中全部的方法均不可覆蓋,但不影響成員變量的賦值。

思考:Test類中的語句塊有點囉嗦,一樣是初始化加調用面積計算方法,三個對象實現了三次,那若是有成百上千個類豈不是要累死,這可否優化呢?

5.4 改進版代碼

對Test類進行優化能夠獲得以下代碼:

public class Test {
    public static void main(String[] args) {
        //建立對象
        ArrayList<Quadrangle> quadrangles = new ArrayList<Quadrangle>();
        quadrangles.add(new Rectangle(1, 2,"矩形"));
        quadrangles.add(new Parallelogram(1, 3,"平行四邊形"));
        quadrangles.add(new Trapezoid(1, 1, 2,"梯形"));
        //循環執行各個對象的面積計算方法
        for (Quadrangle qua : quadrangles) {
            qua.calcuArea();
        }
    }
}

5.5 改進版代碼分析

以上代碼用到了如下概念:
多態(Polymorphism):指一個對象變量(如代碼中的qua)能夠指示多種實際類型的現象。因爲矩形類、平行四邊形類和梯形類都是繼承於四邊形父類,因此其方法名一致,能夠經過一個父類的對象變量來實現子類的自動匹配,從而簡化了代碼。

多態的優缺點

  • 優勢:能夠提升可維護性(多態前提所保證的),提升代碼的可擴展性
  • 缺點:沒法直接訪問子類特有的成員。

思考:這個缺點怎麼解決呢?好比在上述代碼中,沒法經過qua.width_up獲取只有梯形的纔有的成員變量。
解決方法:能夠經過instanceof判斷對象變量的實際類型以及對象類型轉換實現相應的操做,代碼以下:

public class Test {
    public static void main(String[] args) {
        //建立對象
        ArrayList<Quadrangle> quadrangles = new ArrayList<Quadrangle>();
        quadrangles.add(new Rectangle(1, 2,"矩形"));
        quadrangles.add(new Parallelogram(1, 3,"平行四邊形"));
        quadrangles.add(new Trapezoid(1, 1, 2,"梯形"));
        //循環執行各個對象的面積計算方法
        for (Quadrangle qua : quadrangles) {    
            if (qua instanceof Trapezoid) {         //判斷對象類型
                Trapezoid tra=(Trapezoid)qua;       //對象類型轉換
                //輸出梯形類的上底寬
                System.out.println("梯形的上底寬爲:"+tra.width_up);
            }
            qua.calcuArea();
        }
    }
}

6 抽象(Abstrac)

6.1 案例

我們再加一點難度。
如今要求設計一個面積計算器,計算的對象包括平行四邊形、梯形和圓。平行四邊形的輸入爲高和寬,梯形的輸入爲高、上底長和下底長,圓的輸入爲直徑(假定都是規範的數值輸入),全部的輸出均爲面積。
規定:平行四邊形的面積計算公式爲寬x高;梯形的面積計算公式爲(上底+下底)x 高/2;圓的面積計算公式爲圓周率x半徑的平方。

那麼,基於繼承,怎樣設計最好?

6.2 代碼

思考:將可繼承的方法體(即有具體內容的方法)放在父類中以免子類中重複代碼的出現是繼承的一大優點,但其並不是是萬能的。好比在這個案例中,三個面積計算公式都不同,很難抽取出共同的方法體,但咱們又但願子方法中都有面積計算方法且儘量避免重複代碼的出現,怎麼辦呢?

聰明如你,確定想到了能夠用剛纔學到的多態知識實現,代碼以下:

class Geometry {                                //定義幾何圖形類
    public double height; 
    public String name;
    public final double PI=3.1415;
    
    public Geometry(double height,String name) {
        this.height=height;
        this.name=name;
    }
    public void calcuArea(Geometry geo) {       //定義面積計算方法,用到多態
        if (geo instanceof Parallelogram) {
            Parallelogram par=(Parallelogram)geo;
            System.out.println(par.name+"面積爲:"+par.height*par.width);
        }
        else if (geo instanceof Trapezoid) {
            Trapezoid tra=(Trapezoid)geo;
            System.out.println(tra.name+"面積爲:"+height*(tra.width_up+tra.width_down)/2);
        }
        else if (geo instanceof Cycle) {
            Cycle cyc=(Cycle)geo;
            System.out.println(name+"面積爲:"+PI*Math.pow(cyc.height/2,2));
        }
    }
}

class Parallelogram extends Geometry{           //定義平行四邊形類
    public double width;
    public Parallelogram(double height,double width,String name) {
        super(height,name);
        this.width=width;
    }
}

class Trapezoid extends Geometry{               //定義梯形類
    public double width_up;
    public double width_down;
    public Trapezoid(double height,double width_up,double width_down,String name) {
        super(height, name);
        this.width_up=width_up;
        this.width_down=width_down;
    }
}

class Cycle extends Geometry{                   //定義圓形類
    public Cycle(double diameter,String name) {
        super(diameter,name);
    }
}

public class Test {
    public static void main(String[] args) {
        //建立對象
        ArrayList<Geometry> geometries = new ArrayList<Geometry>();
        geometries.add(new Parallelogram(1, 3,"平行四邊形"));
        geometries.add(new Trapezoid(1, 1, 2,"梯形"));
        geometries.add(new Cycle(2,"圓形"));
        //循環執行各個對象的面積計算方法
        for (Geometry geo : geometries) {
            geo.calcuArea(geo);
        }
    }
}

6.3 代碼分析

思考:以上代碼確實實現了咱們的需求,完成了繼承,避免了重複代碼的出現,可是總感受哪裏不對勁。仔細觀察能夠發現,每增長一個新的子類,咱們就必須得在父類方法中作相應的修改,才能使新增子類也具有面積計算方法。做爲一個堂堂正正的父類怎麼能跟着子類的需求而變化呢?那這個父類豈不是很沒「面子」?

因此,在後期功能拓展時,如何才能避免對上層結構的改動呢?

6.4 改進版代碼

思考:既然在父類中難以提取通用的方法體,那咱們可不能夠只聲明方法,而不具體實現呢?固然能夠呀,咱們能夠用到前文提到的「覆蓋」,實現子類方法的定義(如代碼5.2),這樣就避免了對父類的修改。

可是,這個實例化後的父類(好比Geometry類)是什麼呢?有意義嗎?沒有意義的話怎麼才能避免其被實例化呢?

解決方法見代碼:

abstract class Geometry {                       //定義抽象類
    public double height; 
    public Geometry(double height) {
        this.height=height;
    }
    abstract public void calcuArea();           //定義抽象方法
}

class Parallelogram extends Geometry{
    public double width;
    public Parallelogram(double height,double width) {
        super(height);
        this.width=width;
    }
    @Override
    public void calcuArea() {
        System.out.println("平行四邊形面積爲:"+height*width);
    }
}

class Trapezoid extends Geometry{
    public double width_up;
    public double width_down;
    public Trapezoid(double height,double width_up,double width_down) {
        super(height);
        this.width_up=width_up;
        this.width_down=width_down;
    }
    @Override
    public void calcuArea() {
        System.out.println("梯形面積爲:"+height*(width_up+width_down)/2);
    }
}

class Cycle extends Geometry{
    public final double PI=3.1415;
    public Cycle(double diameter) {
        super(diameter);
    }
    @Override
    public void calcuArea() {
        System.out.println("圓形面積爲:"+PI*(height/2)*(height/2));
    }
}

public class Test {
    public static void main(String[] args) {
        ArrayList<Geometry> geometry = new ArrayList<Geometry>();
        geometry.add(new Parallelogram(1, 2));
        geometry.add(new Trapezoid(1, 1, 2));
        geometry.add(new Cycle(2));
        for (Geometry geo : geometry) {
            geo.calcuArea();
        }
    }
}

抽象類圖.jpg

6.5 改進版代碼分析

任何封閉的幾何圖形都應該具備面積計算方法,但方法不一,難以提取出相同的實現代碼,因此將其抽象。

以上代碼用到了如下概念:
抽象方法(Abstrac Method):指使用abstract修飾的方法,沒有方法體,只有聲明。定義的是一種「規範」,就是告訴子類必需要給抽象方法提供具體的實現。
抽象類(Abstrac Class):指包含抽象方法的類。經過abstract方法定義規範,而後要求子類必須定義具體實現。抽象類每每用來表徵對問題領域進行分析、設計中得出的抽象概念,是對一系列看上去不一樣,可是本質上相同的具體概念的抽象。經過抽象類,咱們就能夠作到嚴格限制子類的設計,使子類之間更加通用。同時,經過在抽象類中定義封裝的更改器和訪問器,減小了子類的代碼重複。

抽象的意義:
到這裏,你有可能會有個疑問,既然子類都得經過覆蓋實現本身的面積計算方法,爲何咱們執意要用繼承呢?

由於,咱們須要用繼承來提供一個規範,規範咱們的成員變量和方法(即便沒有具體的實現,也要有一致的方法名),沒有規範,多態就無從談起。若是說繼承的基本用意是實現代碼的複用性,那抽象就是繼承的昇華,它追求更高的精神境界,即契約(或規範)。抽象要求全部使用它的「用戶」(即子類)都必須簽定一份合約,這份合約規定子類必須實現抽象類所規定的全部方法。抽象方法意義在於就算沒法實現出方法的內容,但還能夠定義出一組子型共同的協議。

注意:

  • 有抽象方法的類只能定義成抽象類。
  • 抽象類中仍然能夠定義非抽象方法。
  • 抽象類中能夠定義構造方法,只能用於子類的調用,不能對其自身實例化。
  • 抽象類的子類必須對抽象類定義的抽象方法覆蓋。

7 接口(Interface)

7.1 案例

我們再加一點難度。
在案例6.1的基礎上增長功能,要求軸對稱圖形輸出其繞垂直中心軸旋轉獲得的幾何體的體積,體積計算方法定義爲calcuVolume。

那麼,考慮到代碼的複用性和功能的擴展性,怎樣設計最優呢?

7.2 代碼

思考:平行四邊形不是軸對稱圖形,沒法獲得旋轉的幾何體;梯形旋轉後能夠獲得圓臺;圓形旋轉後能夠獲得球。分析三個圖形的特色能夠獲得如圖所示的結構:
接口圖.jpg

爲此,能夠想到以下兩種方法:
方法一:將calcuVolume方法直接定義在具備軸對稱性的子類中。

  • 優勢:簡單粗暴。(我是一塊磚,哪裏須要就往哪裏搬)
  • 缺點:失去類似類之間的契約精神,好比有的定義的calcuVolume,有的定義的calcVolume,致使維護困難。其次,父類中沒有共同的方法,從而沒法實現多態。

方法二:將calcuVolume方法定義在Geometry抽象類中。

  • 優勢:全部Geometry的子類都將繼承calcuVolume方法,包括後續擴展的子類,從而避免了方法一的缺點。
  • 缺點:全部子類都必須實現calcuVolume方法,即使是不具有軸對稱性質的子類,也被迫在類中聲明這個方法(不含實際內容的空方法),顯然太粗魯(正所謂「強扭的瓜不甜」),這種行爲也太浪費時間,特別在非軸對稱子類數較多的狀況下。

那麼,如何才能優雅地解決這個問題呢?
詳見以下代碼:

abstract class Geometry {                       //定義抽象類
    public double height; 
    public Geometry(double height) {
        this.height=height;
    }
    public abstract void calcuArea();           //定義抽象方法
}

interface AxialSymmetry{
    public static final double PI=3.1415;
    public abstract void calcuVolume();
}

class Parallelogram extends Geometry{
    public double width;
    public Parallelogram(double height,double width) {
        super(height);
        this.width=width;
    }
    @Override
    public void calcuArea() {
        System.out.println("平行四邊形的面積爲:"+height*width);
    }
}

class Trapezoid extends Geometry implements AxialSymmetry{
    public double width_up;
    public double width_down;
    public Trapezoid(double height,double width_up,double width_down) {
        super(height);
        this.width_up=width_up;
        this.width_down=width_down;
    }
    @Override
    public void calcuArea() {
        System.out.println("梯形的面積爲:"+height*(width_up+width_down)/2);
    }
    @Override
    public void calcuVolume() {
        System.out.println("圓臺的體積爲:"+PI*height*(Math.pow(width_up/2,2)+Math.pow(width_down/2,2)+width_up*width_down/4)/3);
    }
}

class Cycle extends Geometry implements AxialSymmetry{
    final double PI=3.1415;
    public Cycle(double diameter) {
        super(diameter);
    }
    @Override
    public void calcuArea() {
        System.out.println("圓形的面積爲:"+PI*(height/2)*(height/2));
    }
    @Override
    public void calcuVolume() {
        System.out.println("球的體積爲:"+PI*Math.pow(height/2,3)*4/3);
    }
}

public class Test {
    public static void main(String[] args) {
        //實現類的多態
        ArrayList<Geometry> geometry = new ArrayList<Geometry>();
        geometry.add(new Parallelogram(1, 2));
        geometry.add(new Trapezoid(1, 1, 2));
        geometry.add(new Cycle(2));
        for (Geometry geo : geometry) {
            geo.calcuArea();
        }
        
        //實現接口的多態
        ArrayList<AxialSymmetry> axialSymmetry = new ArrayList<AxialSymmetry>();
        axialSymmetry.add(new Trapezoid(1, 1, 2));
        axialSymmetry.add(new Cycle(2));
        for (AxialSymmetry axi : axialSymmetry) {
            axi.calcuVolume();
        }
    }
}

7.3 代碼分析

以上代碼用到了如下概念:
接口(Interface):接口不是類,而是對但願符合這個接口的類的一組需求。能夠說接口是比抽象更抽象的概念。抽象類還提供某些具體實現,而接口不提供任何實現,接口中全部方法都是抽象方法。接口是徹底面向規範的,規定了一批類具備的公共方法規範。接口的意義在於全面地、專業地實現了規範和具體實現的分離,便於實現模塊化設計。

類與接口的關係:

  • 類與類之間: 繼承關係,一個類只能直接繼承一個父類,可是支持多重繼承。
  • 類與接口之間: 只有實現關係,一個類能夠實現多個接口。
  • 接口與接口之間: 只有繼承關係,一個接口能夠繼承多個接口。

類與接口的區別:

  • 普通類:具體實現
  • 抽象類:具體實現,規範(抽象方法)
  • 接口:規範!

抽象類與接口的區別:

a.成員變量

  • 抽象類能夠有成員變量,也能夠有常量
  • 接口只能有常量,默認修飾符public static final

b.方法

  • 抽象類能夠有抽象方法,也能夠有非抽象方法
  • 接口只能有抽象方法,默認修飾符 public abstract

c.構造方法

  • 抽象類有構造方法,爲子類提供
  • 接口沒有構造方法

注意:

  • 接口不能被實例化。
  • 接口中的全部方法默認爲public abstract。
  • 接口不能有成員變量,可是能夠包含常量,默認爲public static final。
  • 將類聲明爲實現某個接口,須要用到implements關鍵字,且這個類必須實現接口中全部的方法,而且這些方法只能是public的。(抽象類不是必須實現接口的方法,由於抽象類是抽象的,若是實現了具體的方法,那就喪失了抽象類的意義)
  • 接口能夠多繼承,像類同樣繼承高層次的接口。
  • 接口一樣有多態。

8 練習

8.1 案例

好了,概念講完了。我們來作個練習,把以上全部概念都用起來!
要求:儘可能使用本所介紹的知識,設計一個幾何圖形計算器,能計算圖形的面積以及軸對稱圖形旋轉後的體積。

  1. 幾何圖形:矩形、平行四邊形、梯形、等腰三角形、非等腰三角形。
  2. 輸入:寬和高(梯形再多輸入一個上底寬)
  3. 輸出:圖形的面積以及軸對稱圖形繞垂直中心軸旋轉後的體積。

8.2 代碼

結合本文所學的知識,編寫以下代碼:

//定義幾何圖形的接口
interface Geometry {  
    public static final double PI=3.1415;
    public abstract void calcuArea();     
}

//定義軸對稱圖形的接口
interface AxialSymmetry{
    public abstract void calcuVolume();
}

//定義四邊形抽象類
abstract class Quadrangle implements Geometry{
    private double height;
    private double width;
    
    public void setHeight(double height) {
        this.height = height;
    }
    public void setWidth(double width) {
        this.width = width;
    }
    public double getHeight() {
        return height;
    }
    public double getWidth() {
        return width;
    }
    public Quadrangle(double height, double width) {
        setHeight(height);
        setWidth(width);
    }
}

//定義三邊形抽象類
abstract class Triangle implements Geometry{
    private double height;
    private double width;
    private String name;
    
    public void setHeight(double height) {
        this.height = height;
    }
    public void setWidth(double width) {
        this.width = width;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getHeight() {
        return height;
    }
    public double getWidth() {
        return width;
    }
    public String getName() {
        return name;
    }
    public Triangle(double height, double width) {
        setHeight(height);
        setWidth(width);
    }
    @Override
    public void calcuArea() {
        System.out.println(getName()+"的面積爲:"+getHeight()*getWidth()/2);        
    }
}


//定義矩形類
class Rectangle extends Quadrangle implements AxialSymmetry{
    public Rectangle(double height, double width) {
        super(height, width);
    }
    @Override
    public void calcuArea() {
        System.out.println("矩形的面積爲:"+getHeight()*getWidth());        
    }
    @Override
    public void calcuVolume() {
        System.out.println("矩形旋轉獲得圓柱體體積爲:"+getHeight()*Math.pow(getWidth()/2, 2)*PI);    
    }
}

// 定義平行四邊形類
class Parallelogram extends Quadrangle{
    public Parallelogram(double height, double width) {
        super(height, width);
    }
    @Override
    public void calcuArea() {
        System.out.println("平行四邊形的面積爲:"+getHeight()*getWidth());        
    }
}

//定義梯形類
class Trapezoid extends Quadrangle implements AxialSymmetry{
    private double width_up;
    
    public void setWidth_up(double width_up) {
        this.width_up = width_up;
    }
    public double getWidth_up() {
        return width_up;
    }
    public Trapezoid(double height, double width_up, double width) {
        super(height,width);
        setWidth_up(width_up);
    }
    @Override
    public void calcuArea() {
        System.out.println("梯形的面積爲:"+getHeight()*(getWidth_up()+getWidth())/2);
    }
    @Override
    public void calcuVolume() {
        System.out.println("梯形旋轉獲得的圓臺體積爲:"+PI*getHeight()*(Math.pow(getWidth_up()/2,2)+Math.pow(getWidth()/2,2)+getWidth_up()*getWidth()/4)/3);    
    }
    
}

//定義等腰三角形類
class IsoscelesTriangle extends Triangle implements AxialSymmetry{
    public IsoscelesTriangle(double height, double width) {
        super(height, width);
        setName("等腰三角形");
    }
    @Override
    public void calcuVolume() {
        System.out.println("等腰三角形旋轉獲得的圓錐體體積爲:"+getHeight()*(PI*Math.pow(getWidth()/2, 2))/3);
    }
}

//定義非等腰三角形類
class NotIsoscelesTriangle extends Triangle{
    public NotIsoscelesTriangle(double height, double width) {
        super(height, width);
        setName("非等腰三角形");
    }
}


public class Test {
    public static void main(String[] args) {
        // 實例化
        ArrayList<Object> objects=new ArrayList<Object>();
        objects.add(new Rectangle(1, 2));
        objects.add(new Parallelogram(1, 2));
        objects.add(new Trapezoid(1, 1, 2));
        objects.add(new IsoscelesTriangle(1, 2));
        objects.add(new NotIsoscelesTriangle(1, 2));
        // 多態
        for (Object obj : objects) {
            if (obj instanceof Geometry) {
                Geometry geo=(Geometry)obj;
                geo.calcuArea();
            }
            if (obj instanceof AxialSymmetry) {
                AxialSymmetry axi=(AxialSymmetry) obj;
                axi.calcuVolume();
            }
        }
    }
}

8.3 代碼分析

注意!以上代碼必定不是最優方案,只是爲了練習本文所學知識,所以,僅作參考。關於程序的模式設計問題之後再聊。

層次結構以下:
練習圖.jpg

  • Geometry和AxialSymmetry爲接口。
  • Quadrangle和Triangle爲抽象類,都實現Geometry。
  • Parallelogram、Rectangle、Trapezoid爲類,都繼承於Quadrangle;其中,Rectangle和Trapezoid另外實現AxialSymmetry。
  • IsoscelesTriangle和NotIsoscelesTriangle爲類,都繼承於Triangle,其中IsoscelesTriangle另外實現AxialSymmetry。

9 參考文獻

【1】《Head First Java(第二版·中文版)》
【2】《Java核心技術·卷 I(原書第11版)》
【3】菜鳥教程:https://www.runoob.com/java/j...
【4】速學堂:https://www.sxt.cn/Java_jQuer...

相關文章
相關標籤/搜索