编程思想之——多态解惑

 向上转型(多态)
package com.test;

public class Instrument
{
public void play()
{
}

public static void tune(Instrument i)
{
i.play();
}
}


package com.test;

public class Wind extends Instrument
{

public static void main(String[] args)
{
// TODO Auto-generated method stub
Wind wind = new Wind();
Instrument.tune(wind); //向上转型,把Wind的引用传给Instrument
}
}


注:关于组合与继承的选择,继承技术其实是不太常用的,而且我们应当慎用这一技术,其使用场合仅限于你确信使用该技术确实有效的情况,到底是该用组合还是用继承,一个最清晰的判断办法就是问一问自己是否需要从新类向基类过行“向上转型”,如果必须向上转型,则继承是必要的。
从Wind向上转型到Instrument可能会“缩小”接口,但不会比Instrument的全部接口更窄

总 结:向上转型可以像这样如此简单Instrument insru = new Wind()


 缺陷:“覆盖”私有方法
1. package com.test;
2.
3. public class PrivateOverride
4. {
5. private void f()
6. {
7. System.out.println("private f()");
8. }
9.
10. }
11.
12. class Derived extends PrivateOverride
13. {
14. public void f()
15. {
16. System.out.println("public f()");
17. }
18. }

注:我们所期望的输出是public f(),但是由于private 方法被自动认为是final方法,而且对导出类是屏蔽的。因此,在这种情况下,Derived类中的f()方法就是一个全新的方法,既然基类中的f()方法在子类Derived中不可见,因此甚至也不能被重载。

结论就是:只有非private 方法才可以被覆盖;但是还需要密切注意覆盖private方法的现象,这时虽然编译器不会报错,但是也不会按照我们所期望的来执行。确切地说,在导出类中,对于基类中的private方法,最好采用不同的名字。


 缺陷:域(成员变量)与静态方法不具有多态性

package com.test;

class Super
{
public int field = 0;

public int getField()
{
return field;
}
}

class Sub extends Super
{
public int field = 1;

public int getField()
{
return field;
}

public int getSuperField()
{
return super.field;
}
}

public class FieldAccess
{

public static void main(String[] args)
{
Super sup = new Sub();
System.out.println("sup.field=" + sup.field + ",sup.getField="
+ sup.getField());
}
}


输出结果:sup.field=0,sup.getField=1

注:为什么field等于0呢?当Sub对象转型为Super引用时,任何域访问操作都将由编译器解析,因此域访问不是多态的,在本例中,为Super.field和sub.field分配了不同的存储空间。这样,Sub实际上包含两个称为field的域:它自己的和它从Super处得到的。然而,在引用Sub中的field时所产生的默认域并非super版本的field域。因此,为了得到Super.field,那天须显示地指明super.field

总结:尽管这看起来好像会成为一个容易令人混淆的问题,但是在实践中,它实际上从来不会发生。首先,你通常会将所有的域都设置成private ,因此不能直接访问它们,其副作用是只能调用方法来访问。另外,你可能不会对基类中的域和导出类中的域赋予相同的名字,因为这样做容易令人混淆。

另外:如果把子类Sub中的field改为fields,然后在主方法中通过sup.fields调用是会报找不到的错误。


如果某个方法是静态的,它的行为就不具有多态性
例:
package com.test;

class StaticSuper
{
public static String staticGet()
{
return "Base staticGet()";
}

public String dynamicGet()
{
return "Base dynamicGet()";
}
}

class StaticSub extends StaticSuper
{
public static String staticGet()
{
return "Derived staticGet()";
}

public String dynamicGet()
{
return "derived dynamicGet()";
}
}

public class StaticPolymorphism
{

public static void main(String[] args)
{
StaticSuper sup = new StaticSub();
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());

}

}
输出结果:Base staticGet()
derived dynamicGet()
 构造器不具有多态性
通常,构造器不同于其他种类的方法。涉及到多态时仍是如此。尽管构造器并不 具有多态性(它们实际上是static方法,只不过该static声明是隐式的)。
例:
package com.test;

class Meal
{
Meal()
{
System.out.println("Meal()");
}
}

class Bread
{
Bread()
{
System.out.println("Bread");
}
}

class Cheese
{
Cheese()
{
System.out.println("Cheess()");
}
}

class Lunch extends Meal
{
Lunch()
{
System.out.println("Lunch()");
}
}

class PortableLunch extends Lunch
{
PortableLunch()
{
System.out.println("PortableLunch()");
}
}

public class Sandwich extends PortableLunch
{
private Bread b = new Bread();

private Cheese c = new Cheese();

public Sandwich()
{
System.out.println("Sandwich()");
}

public static void main(String[] args)
{
// TODO Auto-generated method stub
new Sandwich();
}

}

输出 结果:Meal()
Lunch()
PortableLunch()
Bread
Cheess()
Sandwich()

总结:以上例子表明一复杂对象调用构造器要遵照下面的顺序
1) 调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,等等,直到最低层的导出类。
2) 按声明顺序调用成员的初始化方法。
3) 调用导出类构造器的主体。


 构造器内部的多态方法的行为
例:

package com.test;

class Glyph
{
void draw()
{
System.out.println("Glyph.draw()");
}

Glyph()
{
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}

}

class RoundGlyph extends Glyph
{
private int radius = 1;

RoundGlyph(int r)
{
radius = r;
System.out.println("RoundGlyph.RoundGlyph().radius=" + radius);
}

void draw()
{
System.out.println("RoundGlyph.draw().radius=" + radius);
}
}

public class PolyConstructors
{

public static void main(String[] args)
{
new RoundGlyph(5);

}

}

输出结果: Glyph() before draw()
RoundGlyph.draw().radius=0
Glyph() after draw()
RoundGlyph.RoundGlyph().radius=5

注:Glyph.draw方法设计为将要被覆盖,这种覆盖是在RoundGlyph中发生的。但是Glyph构造器会调用这个方法,结果导致了对RoundGlyph.draw()的调用,这看起来似乎是我们的目的。但是如果看到输出结果,我们会发现当Glyph的构造器调用draw()方法时,radius不是默认初始值1,而是0。这可能导致在屏幕上只画了一个点,或者根本什么东西都没有:

前一节讲述的初始化顺序并不十分完整,而这正是解决这一谜题的关键所在。初始化的实际过程是:
1) 在其它任何事物发生之前,将分配给对象的存储空间初始化成二进制的零。
2) 如前所述那样调用基类构造器。此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于步骤1的缘故,我们此时会发现radius的值为0.
3) 按照声明的顺序调用成员的初始化方法。

4) 调用导出类的构造器主体。

总结:编写构造器时有一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其它方法”。在构造器内唯一能够安全调用的那些方法是基类中的final方法(也造用于private方法,它们自动属于final方法).这些方法不能被覆盖,因此也就不会出现上述令人惊讶的问题。你可能无法总是能够遵循这条准则,但是应该朝着它努力。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值