Read Sean

          Read me, read Sean.
          posts - 508, comments - 655, trackbacks - 9, articles - 4

          你真的理解了繼承和多態嗎?

          Posted on 2005-06-07 15:18 laogao 閱讀(7675) 評論(14)  編輯  收藏 所屬分類: On JavaProgramming in General

          最近被同事問起一道SCJP的題目,是跟繼承和多態有關的。具體的題目我就不重復了,來看一段我自己敲的代碼:

           1package sean.work.test;
           2
           3public class DoYouReallyUnderstandPolymorphism {
           4    
           5    public static void main(String[] args) {
           6        A a = new A();
           7        B b = new B();
           8        a.s = "[AA]";
           9        b.s = "[BB]";
          10        System.out.println(a.s);      // prints "[AA]"
          11        System.out.println(b.s);      // prints "[BB]"
          12        System.out.println(a.getS()); // prints "[AA]"
          13        System.out.println(b.getS()); // prints "[BB]"
          14        System.out.println("====================");
          15        a = b; // a now refers to object b
          16        System.out.println(a.s);      // prints "[A]"  <<--1-- the class A copy
          17        System.out.println(b.s);      // prints "[BB]"
          18        System.out.println(a.getS()); // prints "[BB]"
          19        System.out.println(b.getS()); // prints "[BB]"
          20        System.out.println("====================");
          21        ((A)b).s = "[AA]"// <<--2-- changes the class A copy in object b 
          22        System.out.println(a.s);      // prints "[AA]" <<--3-- class A copy changed
          23        System.out.println(b.s);      // prints "[BB]"
          24        System.out.println(a.getS()); // prints "[BB]"
          25        System.out.println(b.getS()); // prints "[BB]"
          26    }

          27
          28}

          29
          30class A {
          31    String s = "[A]";
          32    String getS() {
          33        return s;
          34    }

          35}

          36
          37class B extends A{
          38    String s = "[B]";
          39    String getS() {
          40        return s;
          41    }

          42}

          43

          這里我們的B類繼承自A類,重寫了getS()方法,于是我們可以利用到多態。如果你留意15、16、21、22這幾行的話,你也許就會知道我在說什么了。假如你覺得這樣的打印結果是理所當然,那么我想你可以完全忽略這篇隨筆,因為我要講的就集中在這幾行,而你已經很清楚的理解背后的含義。

          下面跟感興趣的朋友們說說我的理解:

          直觀的講,我們很容易輕信當"a = b;"以后,變量a指向的對象是B類的b那個對象,自然a.s就應該等同于b.s,然而事實并非如此。當B繼承A時,父類A的字段s并沒有被B的字段s取代,而是保留了一份拷貝,所謂重寫(Override),那是對方法而言的。于是,當我們new B()時,在實際創建的對象中,包含了兩個版本的字段s,一個"[A]"(屬于A類)一個"[B]"(屬于B類)。而方法getS()只有一個版本。這就是在繼承過程中字段和方法的區別。也就是說,重寫的概念和字段無關。在第16行,我們通過a.s訪問的是b這個對象中保留的A類的字段s;而在21行,我們改變的正是這個A類版本的s字段。

          多態的精髓在于動態確定對象的行為,而對象的行為體現在方法而非字段,字段代表的更多的是對象的狀態。于是只有方法的多態而沒有字段的多態。從上面的代碼可以看出,不管你用什么類型的變量存放對象b的引用,最終調用的方法版本都是b的真實類型那個版本,這就是多態的威力。

          從編譯的角度來看,上面代碼中的s字段和getS()方法的不同在于:s字段是在編譯期靜態鏈接(你可以改變它的值,但是它在對象中的相對地址已經確定好),而getS()方法是在運行期動態鏈接的。

          說了這么多,真的不知道我表達清楚沒有,畢竟沒有系統研究過OO和編譯原理,說得不當的地方還請多多包涵。最后,請不要學我這里的編碼風格,因為很顯然應該對main方法中的代碼段執行Extract Method重構了,我這里只是為了注釋方便。

          // BTW,時不時抽空想想這樣的問題感覺真不錯。

          Feedback

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2005-06-07 16:26 by 小毅
          謝謝您,學到很容易忽略的東西,覺得很重要
          再謝謝你...

          # 原來字段和方法有這樣的區別哦  回復  更多評論   

          2005-06-08 09:02 by emu
          >>只有方法的多態而沒有字段的多態

          以前都沒有想過這個問題哦。

          前兩天遇到一個內嵌類的方法重載時候的古怪問題,不知是否與此有關,正好回頭去研究一下。

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2005-06-08 12:16 by FS
          Java的對象模型偶不太清楚,不過通過對Delphi的對象模型的了解,我感覺應該和Java沒有太大的區別。

          按照樓主所說的,當執行了語句“a=b”后,變量a和b的內容應該是不變的,所變的是a指向的對象實體中的指向類的指針的內容,在Delphi中,這個指針叫做vptr,CPP中也是如此叫法。vptr直接指到類的VMT(不知道Java中如何叫法);因此,再次使用a.s訪問實例變量,則要進入b指向的對象實體中進行字段的查找,但由于b里面存在兩個s字段,并且以類型信息為區分方式,所以找到的是類型A的字段s。至于編譯處理方面,我想應該是把類型信息作為字段的前綴或者后綴。

          同樣,當執行了“((A)b).s=“[AA]””后,由于已經把b的類型轉化為A類型,而且b指針的未改變,所以在b的對象實體中,實際改變的是屬于類型A的字段s的內容。個人感覺,其實樓主已經說出了,對b中不同類型的同名字段的訪問方式。

          最后留下一個問題:這種對象模型中的字段處理方式如果在繼承字段訪問量小的情況下是很浪費空間的。那么,這樣處理的優點又是什么?

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2005-06-08 12:18 by FS
          按照OO這種東西在語言設計上的考慮,對象所涉及到的所有內容就是三塊:對象指針,對象實體和對象所屬類型的內存布局。而對象實體中只存放狀態信息,即對象所屬類型的實例字段的內容,因此,從這個角度考慮,多態和實例字段的信息是沒有任何關系的。但對于類字段,即申明為static的字段就未必了。

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2005-07-20 23:24 by 丑男
          System.out.println(a.s); // prints "[A]"
          這句確實我沒想到!看來真被難倒了

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2006-05-15 17:30 by songxu
          非常感謝

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2006-07-15 17:22 by tcpipok
          我是想到了,不過就像樓主所說的,這種編碼風格不好,子類盡量不要用父類用過的變量名,真的是容易搞混,這種代碼用來考試可以,但用來編程不好.

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2006-08-17 20:34 by 小小
          請問:
          java中字符串的連接是不是一種多態的表現?

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2007-03-05 22:52 by 谷鈺
          @tcpipok
          我不知道你是否理解OO真正的精髓,以上用法展示了面向對象設計的核心思想,通過虛方法調用,實現不同子類對同一行為(方法)的不同實現。希望以下例子可以給大家一些啟發:

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2007-03-05 23:05 by 谷鈺
          public class Employee {

          protected String name = "張三";

          private double salary = 1000;

          public Employee(String name, double salary) {
          this.name = name;
          this.salary = salary;
          }

          public Employee() {
          this("王老五", 1);
          }

          public String getDetails() {
          return "姓名:" + this.name + ", 工資: " + this.salary;
          }

          public double getSalary() {
          return this.salary;
          }

          public void setSalary(double salary) {
          this.salary = salary;
          }

          public String getName() {
          return this.name;
          }

          public void setName(String name) {
          this.name = name;
          }

          public boolean equals(Object o) {
          if (this == o) {
          return true;
          }

          if (o instanceof Employee) {
          Employee te = (Employee) o;

          return this.salary == te.getSalary() && this.name.equals(te.getName());
          }

          return false;
          }

          public int hashCode() {
          if (this.name == null) {
          return 0;
          }
          return this.name.hashCode();
          }

          public String toString() {
          return "$$" + this.getDetails();
          // return super.toString();
          }

          public static void printAll(Employee[] all) {
          System.out.println("所有員工:");
          for (int i = 0; i < all.length; ++i) {
          System.out.println(all[i].getDetails());
          }
          }

          public static void main(String[] args) {
          Employee e = new Employee("張三", 2500);
          System.out.println(e.getDetails());

          Manager m = new Manager("李四", 4500, "軟件開發部");
          System.out.println(m.getDetails());

          Employee ee = new Manager("王五", 4500, "軟件銷售部");
          System.out.println(ee.getDetails());

          if (ee instanceof Manager) {
          Manager mmm = (Manager) ee;
          System.out.println(mmm.getDepartment());
          }

          Employee e2 = new Employee("張三", 2500);
          System.out.println(e.equals(e2));

          System.out.println(e);
          System.out.println("##" + e);

          Employee[] eAll = {
          e, m, ee
          };
          printAll(eAll);
          }
          }

          class Manager extends Employee {
          private String department = "44";

          public Manager() {
          super();
          this.department = "計算機";
          }

          public Manager(String name, double salary, String department) {
          super(name, salary);

          this.department = department;
          }

          public String getDetails() {
          return super.getDetails() + ", 管理部門: " + this.department;
          }

          public String getDepartment() {
          return this.department;
          }
          }

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2007-03-05 23:11 by 谷鈺
          核心在
          public static void printAll(Employee[] all) {
          System.out.println("所有員工:");
          for (int i = 0; i < all.length; ++i) {
          System.out.println(all[i].getDetails());
          }
          }
          函數的設計上,通過Employee隱藏所有雇員子類的細節,通過getDetails()方法來隱藏不同雇員(Manger,Engineer等)對詳細信息的函數細節。

          只有這樣的函數才能最大程度上復用,既當設計有新的Employee子類定義也不許要對此函數做任何的修改,個人認為面向對象的最大好處是提高程序的復用性,降低維護成本。這些需要多態,繼承才能作到。:)

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2007-03-06 09:38 by 大胃
          謝謝 谷鈺 的回復!

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2008-05-21 17:06 by tonytang
          to 谷鈺,你的代碼也沒有回答tcpipok的疑問啊。子類和父類定義相同的屬性有意義嗎?

          # re: 你真的理解了繼承和多態嗎?  回復  更多評論   

          2008-06-25 11:00 by shell
          寫的太好了。
          學習了很多。
          主站蜘蛛池模板: 曲沃县| 加查县| 南岸区| 长治市| 明水县| 运城市| 贺州市| 延寿县| 北流市| 北川| 崇明县| 儋州市| 前郭尔| 达拉特旗| 遂川县| 武冈市| 曲阳县| 库伦旗| 富裕县| 景东| 固原市| 南京市| 屏边| 礼泉县| 南溪县| 古田县| 巩留县| 新竹市| 汨罗市| 连城县| 靖边县| 平原县| 罗城| 溧水县| 石渠县| 阿拉善右旗| 涡阳县| 贺州市| 崇州市| 大庆市| 六安市|