電腦程式的思維邏輯 (15) - 初識繼承和多態

來源:互聯網
上載者:User

標籤:

繼承

上節我們談到,將現實中的概念映射為程式中的概念,我們談了類以及類之間的組合,現實中的概念間還有一種非常重要的關係,就是分類,分類有個根,然後向下不斷細化,形成一個層次分類體系。這種例子是非常多的:

在自然世界中,生物有動物和植物,動物有不同的科目,食肉動物、食草動物、雜食動物等,食肉動物有狼、狗、虎等,這些又分為不同的品種 ...

開啟電商網站,在顯著位置一般都有分類列表,比如家用電器、服裝,服裝有女裝、男裝,男裝有襯衫、牛仔褲等 ...

電腦程式經常使用類之間的繼承關係來表示對象之間的分類別關係。在繼承關係中,有父類和子類,比如動物類Animal和狗類Dog,Animal是父類,Dog是子類。父類也叫基類,子類也叫衍生類別,父類子類是相對的,一個類B可能是類A的子類,是類C的父類。

之所以叫繼承是因為,子類繼承了父類的屬性和行為,父類有的屬性和行為,子類都有。但子類可以增加子類特有的屬性和行為,某些父類有的行為,子類的實現方式可能與父類也不完全一樣。

使用繼承一方面可以複用代碼,公用的屬性和行為可以放到父類中,而子類只需要關注子類特有的就可以了,另一方面,不同子類的對象可以更為方便的被統一處理。

本節主要通過圖形處理中的一些簡單例子來介紹Java中的繼承,會介紹繼承的基本概念,關於繼承更深入的討論和實現原理,我們在後續章節介紹。

Object

在Java中,所有類都有一個父類,即使沒有聲明父類,也有一個隱含的父類,這個父類叫Object。Object沒有定義屬性,但定義了一些方法,如所示:


本節我們會介紹toString()方法,其他方法我們會在後續章節中逐步介紹。toString()方法的目的是返回一個對象的文本描述,這個方法可以直接被所有類使用。

比如說,對於我們之前介紹的Point類,可以這樣使用toString方法:

Point p = new Point(2,3);System.out.println(p.toString()); 

輸出類似這樣:

[email protected]

這是什麼意思呢?@之前是類名,@之後的內容是什麼呢?我們來看下toString的代碼:

public String toString() {    return getClass().getName() + "@" + Integer.toHexString(hashCode());}

getClass().getName() 返回當前對象的類名,hashCode()返回一個對象的雜湊值,雜湊我們會在後續章節中介紹,這裡可以理解為是一個整數,這個整數預設情況下,通常是對 象的記憶體位址值,Integer.toHexString(hashCode())返回這個雜湊值的16進位表示。

為什麼要這麼寫呢?寫類名是可以理解的,表示對象的類型,而寫雜湊值則是不得已的,因為Object類並不知道具體對象的屬性,不知道怎麼用文本描述,但又需要區分不同對象,只能是寫一個雜湊值。

但子類是知道自己的屬性的,子類可以重寫父類的方法,以反映自己的不同實現。所謂重寫,就是定義和父類一樣的方法,並重新實現。

Point類 - 重寫toString()

我們再來看下Point類,這次我們重寫了toString()方法。

public class Point {    private int x;    private int y;        public Point(int x, int y) {        this.x = x;        this.y = y;    }    public double distance(Point point){        return Math.sqrt(Math.pow(this.x-point.getX(),2)                +Math.pow(this.y-point.getY(), 2));    }        public int getX() {        return x;    }        public int getY() {        return y;    }    @Override    public String toString() {        return "("+x+","+y+")";    }}

toString方法前面有一個 @Override,這表示toString這個方法是重寫的父類的方法,重寫後的方法返回Point的x和y座標的值。重寫後,將調用子類的實現。比如,如下代碼的輸出就變成了:(2,3)

Point p = new Point(2,3);System.out.println(p.toString());

圖形處理類

接下來,我們以一些圖形處理中的例子來進一步解釋,先來看幅圖:

 

這都是一些基本的圖形,圖形有線、正方形、三角形、圓形等,圖形有不同的顏色。接下來,我們定義以下類來說明關於繼承的一些概念:

  • 父類Shape,表示圖形。
  • 類Circle,表示圓。
  • 類Line,表示直線。
  • 類ArrowLine,表示帶箭頭的直線。 

圖形 (Shape)

所有圖形都有一個表示顏色的屬性,有一個表示繪製的方法,下面是代碼:

public class Shape {    private static final String DEFAULT_COLOR = "black";        private String color;        public Shape() {        this(DEFAULT_COLOR);    }    public Shape(String color) {        this.color = color;    }        public String getColor() {        return color;    }    public void setColor(String color) {        this.color = color;    }        public void draw(){        System.out.println("draw shape");    }}

以上代碼基本沒什麼可解釋的,執行個體變數color表示顏色,draw方法表示繪製,我們不會寫實際的繪製代碼,主要是示範繼承關係。

圓 (Circle)

圓繼承自Shape,但包括了額外的屬性,中心點和半徑,以及額外的方法area,用於計算面積,另外,重寫了draw方法,代碼如下:

public class Circle extends Shape {    //中心點    private Point center;        //半徑    private double r;     public Circle(Point center, double r) {        this.center = center;        this.r = r;    }    @Override    public void draw() {        System.out.println("draw circle at "                +center.toString()+" with r "+r                +", using color : "+getColor());        }        public double area(){        return Math.PI*r*r;    }}

說明幾點:

  • Java使用extends關鍵字標明繼承關係,一個類最多隻能有一個父類。
  • 子類不能直接存取父類的私人屬性和方法,比如,在Circle中,不能直接存取shape的私人執行個體變數color。
  • 除了私人的外,子類繼承了父類的其他屬性和方法,比如,在Circle的draw方法中,可以直接調用getColor()方法。 

看下使用它的代碼:

public static void main(String[] args) {    Point center = new Point(2,3);    //建立圓,賦值給circle    Circle circle = new Circle(center,2);    //調用draw方法,會執行Circle的draw方法    circle.draw();    //輸出圓面積    System.out.println(circle.area());}

程式的輸出為:

draw circle at (2,3) with r 2.0, using color : black12.566370614359172

這裡比較奇怪的是,color是什麼時候賦值的?在new的過程中,父類的構造方法也會執行,且會優先於子類先執行。在這個例子中,父類Shape的預設構造方法會在子類Circle的構造方法之前執行。關於new過程的細節,我們會在後續章節進一步介紹。

直線 (Line)

線繼承自Shape,但有兩個點,有一個擷取長度的方法,另外,重寫了draw方法,代碼如下:

public class Line extends Shape {    private Point start;    private Point end;        public Line(Point start, Point end, String color) {        super(color);        this.start = start;        this.end = end;    }    public double length(){        return start.distance(end);    }        public Point getStart() {        return start;    }    public Point getEnd() {        return end;    }        @Override    public void draw() {        System.out.println("draw line from "                + start.toString()+" to "+end.toString()                + ",using color "+super.getColor());    }}

這裡我們要說明的是super這個關鍵字,super用於指代父類,可用於調用父類構造方法,訪問父類方法和變數:

  • 在line構造方法中,super(color)表示調用父類的帶color參數的構造方法,調用父類構造方法時,super(...)必須放在第一行。
  • 在draw方法中,super.getColor()表示調用父類的getColor方法,當然不寫super.也是可以的,因為這個方法子類沒有同名的,沒有歧義,當有歧義的時候,通過super.可以明確表示調用父類的。
  • super同樣可以引用父類非私人的變數。

可以看出,super的使用與this有點像,但super和this是不同的,this引用一個對象,是實實在在存在的,可以作為函數參數,可以作為傳回值,但super只是一個關鍵字,不能作為參數和傳回值,它只是用於告訴編譯器訪問父類的相關變數和方法。

帶箭頭直線 (ArrowLine)

帶箭頭直線繼承自Line,但多了兩個屬性,分別表示兩端是否有箭頭,也重寫了draw方法,代碼如下:

public class ArrowLine extends Line {        private boolean startArrow;    private boolean endArrow;        public ArrowLine(Point start, Point end, String color,             boolean startArrow, boolean endArrow) {        super(start, end, color);        this.startArrow = startArrow;        this.endArrow = endArrow;    }    @Override    public void draw() {        super.draw();        if(startArrow){            System.out.println("draw start arrow");        }        if(endArrow){            System.out.println("draw end arrow");        }    }}

ArrowLine繼承自Line,而Line繼承自Shape,ArrowLine的對象也有Shape的屬性和方法。

注意draw方法的第一行,super.draw()表示調用父類的draw()方法,這時候不帶super.是不行的,因為當前的方法也叫draw()。

需要說明的是,這裡ArrowLine繼承了Line,也可以直接在類Line裡加上屬性,而不需要單獨設計一個類ArrowLine,這裡主要是示範繼承的層次性。

圖形管理器

使用繼承的一個好處是可以統一處理不同子類型的對象,比如說,我們來看一個圖形管理者類,它負責管理畫板上的所有繪圖物件並負責繪製,在繪製代碼中,只需要將每個對象當做Shape並調用draw方法就可以了,系統會自動執行子類的draw方法。代碼如下:

public class ShapeManager {    private static final int MAX_NUM = 100;    private Shape[] shapes = new Shape[MAX_NUM];    private int shapeNum = 0;        public void addShape(Shape shape){        if(shapeNum<MAX_NUM){            shapes[shapeNum++] = shape;            }    }        public void draw(){        for(int i=0;i<shapeNum;i++){            shapes[i].draw();        }    }}

ShapeManager使用一個數組儲存所有的shape,在draw方法中調用每個shape的draw方法。ShapeManager並不知道每個shape具體的類型,也不關心,但可以調用到子類的draw方法。

我們來看下使用ShapeManager的一個例子:

public static void main(String[] args) {    ShapeManager manager = new ShapeManager();        manager.addShape(new Circle(new Point(4,4),3));    manager.addShape(new Line(new Point(2,3),            new Point(3,4),"green"));    manager.addShape(new ArrowLine(new Point(1,2),             new Point(5,5),"black",false,true));        manager.draw();}

建立了三個shape,分別是一個圓、直線和帶箭頭的線,然後加到了shape manager中,然後調用manager的draw方法。

需要說明的是,在addShape方法中,參數Shape shape,聲明的類型是Shape,而實際的類型則分別是Circle,Line和ArrowLine。子類對象賦值給父類引用變數,這叫向上轉型,轉型就是轉換類型,向上轉型就是轉換為父類類型。

變數shape可以引用任何Shape子類類型的對象,這叫多態,即一種類型的變數,可引用多種實際類型對象。這樣,對於變數shape,它就有兩個類型,類型Shape,我們稱之為shape的靜態類型,類型Circle/Line/ArrowLine,我們稱之為shape的動態類型。在ShapeManager的draw方法中,shapes[i].draw()調用的是其對應動態類型的draw方法,這稱之為方法的動態綁定。

為什麼要有多態和動態綁定呢?建立對象的代碼 (ShapeManager以外的代碼)和操作對象的代碼(ShapeManager本身的代碼),經常不在一起,操作對象的代碼往往只知道對象是某種父類型,也往往只需要知道它是某種父類型就可以了。

可以說,多態和動態綁定是電腦程式的一種重要思維方式,使得操作對象的程式不需要關注對象的實際類型,從而可以統一處理不同對象,但又能實現每個對象的特有行為。後續章節我們會進一步介紹動態綁定的實現原理。

小結

本節介紹了繼承和多態的基本概念:

  • 每個類有且只有一個父類,沒有聲明父類的其父類為Object,子類繼承了父類非private的屬性和方法,可以增加自己的屬性和方法,可以重寫父類的方法實現。
  • new過程中,父類先進行初始化,可通過super調用父類相應的構造方法,沒有使用super的話,調用父類的預設構造方法。
  • 子類變數和方法與父類重名的情況下,可通過super強制訪問父類的變數和方法。
  • 子類對象可以賦值給父類引用變數,這叫多態,實際執行調用的是子類實現,這叫動態綁定。

但關於繼承,還有很多細節,比如執行個體變數重名的情況。另外,繼承雖然可以複用代碼,便於統一處理不同子類的對象,但繼承其實是把雙刃劍,使用不當,也有很多問題。讓我們下節來討論這些問題,而關於繼承和多態的實現原理,讓我們再下節來討論。

----------------

未完待續,查看最新文章,敬請關注公眾號“老馬說編程”(掃描下方二維碼),從入門到進階,深入淺出,老馬和你一起探索Java編程及電腦技術的本質。原創文章,保留所有著作權。

電腦程式的思維邏輯 (15) - 初識繼承和多態

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.