Read Sean

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

你真的理解了继承和多态吗?

Posted on 2005-06-07 15:18 laogao 阅读(7321) 评论(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
写的太好了。
学习了很多。

只有注册用户登录后才能发表评论。


网站导航: