黑马程序员技术交流社区

标题: 关于多态性 [打印本页]

作者: 马伟恒    时间: 2012-4-20 10:48
标题: 关于多态性
class A{     // 定义类A
public void fun1(){  // 定义fun1()方法
  System.out.println("A --> public void fun1(){}") ;
}
public void fun2(){
  this.fun1() ;  // 调用fun1()方法
}
};
class B extends A{
public void fun1(){  // 此方法被子类覆写了
  System.out.println("B --> public void fun1(){}") ;
}
public void fun3(){
  System.out.println("B --> public void fun3(){}") ;
}
};
public class PolDemo01{
public static void main(String asrgs[]){
  A a = new B() ;  // 实例化子类对象
  
  
  a.fun1() ;
  a.fun2();// 此方法被子类覆写过
  a.fun3();// 此处为什么不能调用fun3方法呢??(转型之后是父类对象,无法找到子类中定义的新方法,可是下面a又是B类的实例,这是为什么呀???)
  System.out.println((a instanceof B) ); // true  ,,为什么又是B类的实例呢?不是转化成父类对象了吗??
  System.out.println((a instanceof A) ); // true
}
};





作者: 邱俊杰    时间: 2012-4-20 11:03
本帖最后由 邱俊杰 于 2012-4-20 11:08 编辑

  这句 A a = new B() ;  // 实例化子类对象、当你调用方法时还是得看父类是有否有定义这类方法。  你的代码里父类并没有fun()3;所以调用不了的
                                        而fun()1 和fun()2都在父类class A中当然可以调用、
  哥们 、你这样写好像找不到main方法噢 、 你觉得呢?
作者: zhaishuang    时间: 2012-4-20 11:05
A a = new B();这是父类引用指向了子类对象;不是你说的将B转成父类对象了;
这句话执行完会在栈内存中分配一块内存用于存储引用类型的变量A,他的变量名叫a;
在堆内存创建B对象,进行初始化动作,把它的地址值赋给a;
在编译的时候A回去找自己的方法,因为你的类A中没有fun3方法,所有此时就会报错;
而在执行的时候才执行B中的方法;
a instanceof B;这句话就是多态的应用了,因为a指向了B对象,所有这句话是对的;
a instanceof A;因为a原本就是A的引用类型,自然也没有错;

作者: liuyang    时间: 2012-4-20 11:10
楼主把下面的实现机制看看,不知道能不能帮到你
一、多态概述:

面向对象程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。“封装”通过合并特征和行为来创建新的数据类型。“实现隐藏”则通过将细节“私有化”把接口和实现分离开来。而多态的作用则是消除类型之间的耦合关系。

二、向上转型(Upcasting):

导出类是基类的一个超集,Upcasting是从一个较专用的类型向较通用的类型转换,所以总是很安全的。导出类必须至少具备有基类中所含有的方法在向上转型的过程中,类接口中唯一可能发生的事情是丢失方法,而不是获取它们。

三、多态的缺陷:

1、缺陷:“覆盖”私有方法。只有非private的方法才可以被覆盖,尽管编译器不会报错,但是也不会按照我们所期望的来执行。在导出类中,对于基类中的private方法,最好采用不同的名字。

2、缺陷:域在转型时候的问题。对于成员变量(域),导出类将拥有从基类继承而来的成员变量和自己的成员变量(变量名字相同时也是如此),而且,将分配不同的存储空间,这样,导出类将拥有两个名称一样的域。为了得到基类的域,必须现实地知名super.field才能访问,而默认的域则是导出类自己的域。

3、静态方法是与类相关联的,而非与某个对象相关联的,那么它就不具有多态行为。

四、构造器和多态:

1.         构造器不同于普通的方法,涉及到多态的时候仍然是这样。构造器实际上是static方法,只不过static声明是隐式的。

2.         编译器强制每个导出类都必须调用构造器的原因:

基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上连接,以使每个基类的构造器都能够得到调用。这样的做的意义是,因为构造器有一个特殊的任务:检查对象是否被正确地构造。导出类只能访问它自己的成员,不能访问基类中的成员(基类成员通常是private型)。只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此,必须令所有构造器都得到调用,否则就不可能正确构造完整对象。这就是上面问题的原因。

3.         初始化的实际过程:

A、在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的0;

B、 调用基类的构造器;

C、 按照声明的顺序调用成员的初始化方法;

D、调用导出类的构造器主体

4.         继承和清理

如果确实遇到需要清理的问题,那么必须用心为新类创建dispose()方法。并且忧郁继承的缘故,如果我们有其他作为垃圾回收一部分的特殊清理动作,就必须在导出类中覆盖dispose()方法。当覆盖基类的dispose()方法时,务必记住调用基类版本的dispose()方法,否则,基类的清理动作就不会发生。

       万一某个子对象要依赖于其他对象,销毁的顺序应该和初始化的顺序相反。这是因为导出类的清理可能会调用基类中的某些方法,所以需要使基类中的构建仍起作用而不应该过早地销毁它们。

       然而,成员对象中存在于其他一个或者多个对象共享的情况,问题就变得更加复杂了,你就不能简单地假设可以调用dispose()了,在这种情况下,必须使用“引用计数器”来跟踪仍旧访问着共享对象的对象数量了。

5.         构造器内部的多态方法的行为

如果要调用构造器内部的一个动态绑定方法,就要用到那个方法的被覆盖后的定义。然而,这个调用的效果可能相当难于预料,因为被覆盖的方法在对象被完全构造之前就会被调用。这可能会造成一些难以发现的隐藏错误。

       构造器的工作实际上是创建对象。在任何构造器内部,整个对象可能只是部分形成——我们只知道基类对象以及初始化。如果构造器只是在构建对象的过程中的一个步骤,并且该对象所属的泪是从这个构造器所属的类导出的。那么导出部分在当前构造器正在被嗲用的时刻仍旧是没有被促使的。然而,一个动态绑定的方法调用却会向外深入到继承层次结构内部,它可以调用导出类里的方法。如果我们是在构造器内部这样做,那么就可能会调用某个方法,而这个方法所操纵的成员可能还未进行初始化,这肯定会导致灾难。下面是一个典型的例子:

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(Stringp[] args){

       new RoundGlyph(5);

}

}

/*Output:

Glyph() before draw()

RoundGlyph.draw(),radius=0

Glyph() after draw()

RoundGlyph. RoundGlyph(),radius=5

*/




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2