什么体现了类的多态性?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么体现了类的多态性?相关的知识,希望对你有一定的参考价值。

多态是通过:
  1 接口 和 实现接口并覆盖接口中同一方法的几不同的类体现的
  2 父类 和 继承父类并覆盖父类中同一方法的几个不同子类实现的.
  一、基本概念
  多态性:发送消息给某个对象,让该对象自行决定响应何种行为。
  通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。
  java 的这种机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。
  1. 如果a是类A的一个引用,那么,a可以指向类A的一个实例,或者说指向类A的一个子类。
  2. 如果a是接口A的一个引用,那么,a必须指向实现了接口A的一个类的实例。

二、Java多态性实现机制
  SUN目前的JVM实现机制,类实例的引用就是指向一个句柄(handle)的指针,这个句柄是一对指针:
  一个指针指向一张表格,实际上这个表格也有两个指针(一个指针指向一个包含了对象的方法表,另外一个指向类对象,表明该对象所属的类型);
  另一个指针指向一块从java堆中为分配出来内存空间。
  三、总结
  1、通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。
  DerivedC c2=new DerivedC();
  BaseClass a1= c2; //BaseClass 基类,DerivedC是继承自BaseClass的子类
  a1.play(); //play()在BaseClass,DerivedC中均有定义,即子类覆写了该方法
  分析:
  * 为什么子类的类型的对象实例可以覆给超类引用?
  自动实现向上转型。通过该语句,编译器自动将子类实例向上移动,成为通用类型BaseClass;
  * a.play()将执行子类还是父类定义的方法?
  子类的。在运行时期,将根据a这个对象引用实际的类型来获取对应的方法。所以才有多态性。一个基类的对象引用,被赋予不同的子类对象引用,执行该方法时,将表现出不同的行为。
  在a1=c2的时候,仍然是存在两个句柄,a1和c2,但是a1和c2拥有同一块数据内存块和不同的函数表。
  2、不能把父类对象引用赋给子类对象引用变量
  BaseClass a2=new BaseClass();
  DerivedC c1=a2;//出错
  在java里面,向上转型是自动进行的,但是向下转型却不是,需要我们自己定义强制进行。
  c1=(DerivedC)a2; 进行强制转化,也就是向下转型.
  3、记住一个很简单又很复杂的规则,一个类型引用只能引用引用类型自身含有的方法和变量。
  你可能说这个规则不对的,因为父类引用指向子类对象的时候,最后执行的是子类的方法的。
  其实这并不矛盾,那是因为采用了后期绑定,动态运行的时候又根据型别去调用了子类的方法。而假若子类的这个方法在父类中并没有定义,则会出错。
  例如,DerivedC类在继承BaseClass中定义的函数外,还增加了几个函数(例如 myFun())
  分析:
  当你使用父类引用指向子类的时候,其实jvm已经使用了编译器产生的类型信息调整转换了。
  这里你可以这样理解,相当于把不是父类中含有的函数从虚拟函数表中设置为不可见的。注意有可能虚拟函数表中有些函数地址由于在子类中已经被改写了,所以对象虚拟函数表中虚拟函数项目地址已经被设置为子类中完成的方法体的地址了。
  4、Java与C++多态性的比较
  jvm关于多态性支持解决方法是和c++中几乎一样的,
  只是c++中编译器很多是把类型信息和虚拟函数信息都放在一个虚拟函数表中,但是利用某种技术来区别。
  Java把类型信息和函数信息分开放。Java中在继承以后,子类会重新设置自己的虚拟函数表,这个虚拟函数表中的项目有由两部分组成。从父类继承的虚拟函数和子类自己的虚拟函数。
  虚拟函数调用是经过虚拟函数表间接调用的,所以才得以实现多态的。
  Java的所有函数,除了被声明为final的,都是用后期绑定。
  四. 1个行为,不同的对象,他们具体体现出来的方式不一样,
  比如: 方法重载 overloading 以及 方法重写(覆盖)override
  class Human
  void run()输出 人在跑
  
  class Man extends Human
  void run()输出 男人在跑
  
  这个时候,同是跑,不同的对象,不一样(这个是方法覆盖的例子)
  class Test
  void out(String str)输出 str
  void out(int i)输出 i
  
  这个例子是方法重载,方法名相同,参数表不同
  ok,明白了这些还不够,还用人在跑举例
  Human ahuman=new Man();
  这样我等于实例化了一个Man的对象,并声明了一个Human的引用,让它去指向Man这个对象
  意思是说,把 Man这个对象当 Human看了.
  比如去动物园,你看见了一个动物,不知道它是什么, "这是什么动物? " "这是大熊猫! "
  这2句话,就是最好的证明,因为不知道它是大熊猫,但知道它的父类是动物,所以,
  这个大熊猫对象,你把它当成其父类 动物看,这样子合情合理.
  这种方式下要注意 new Man();的确实例化了Man对象,所以 ahuman.run()这个方法 输出的 是 "男人在跑 "
  如果在子类 Man下你 写了一些它独有的方法 比如 eat(),而Human没有这个方法,
  在调用eat方法时,一定要注意 强制类型转换 ((Man)ahuman).eat(),这样才可以...
  对接口来说,情况是类似的...
  实例:
  package domatic;
  //定义超类superA
  class superA
  int i = 100;
  void fun(int j)
  j = i;
  System.out.println("This is superA");
  
  
  // 定义superA的子类subB
  class subB extends superA
  int m = 1;
  void fun(int aa)
  System.out.println("This is subB");
  
  
  // 定义superA的子类subC
  class subC extends superA
  int n = 1;
  void fun(int cc)
  System.out.println("This is subC");
  
  

class Test
  public static void main(String[] args)
  superA a = new superA();
  subB b = new subB();
  subC c = new subC();
  a = b;
  a.fun(100);
  a = c;
  a.fun(200);
  
  
  /*
  * 上述代码中subB和subC是超类superA的子类,我们在类Test中声明了3个引用变量a, b,
  * c,通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。也许有人会问:
  * "为什么(1)和(2)不输出:This is superA"。
  * java的这种机制遵循一个原则:当超类对象引用变量引用子类对象时,
  * 被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,
  * 但是这个被调用的方法必须是在超类中定义过的,
  * 也就是说被子类覆盖的方法。
  * 所以,不要被上例中(1)和(2)所迷惑,虽然写成a.fun(),但是由于(1)中的a被b赋值,
  * 指向了子类subB的一个实例,因而(1)所调用的fun()实际上是子类subB的成员方法fun(),
  * 它覆盖了超类superA的成员方法fun();同样(2)调用的是子类subC的成员方法fun()。
  * 另外,如果子类继承的超类是一个抽象类,虽然抽象类不能通过new操作符实例化,
  * 但是可以创建抽象类的对象引用指向子类对象,以实现运行时多态性。具体的实现方法同上例。
  * 不过,抽象类的子类必须覆盖实现超类中的所有的抽象方法,
  * 否则子类必须被abstract修饰符修饰,当然也就不能被实例化了
  */
  以上大多数是以子类覆盖父类的方法实现多态.下面是另一种实现多态的方法-----------重写父类方法
  1.JAVA里没有多继承,一个类之能有一个父类。而继承的表现就是多态。一个父类可以有多个子类,而在子类里可以重写父类的方法(例如方法print()),这样每个子类里重写的代码不一样,自然表现形式就不一样。这样用父类的变量去引用不同的子类,在调用这个相同的方法print()的时候得到的结果和表现形式就不一样了,这就是多态,相同的消息(也就是调用相同的方法)会有不同的结果。举例说明:
  //父类
  public class Father
  //父类有一个打孩子方法
  public void hitChild()
  
  
  //子类1
  public class Son1 extends Father
  //重写父类打孩子方法
  public void hitChild()
  System.out.println("为什么打我?我做错什么了!");
  
  
  //子类2
  public class Son2 extends Father
  //重写父类打孩子方法
  public void hitChild()
  System.out.println("我知道错了,别打了!");
  
  
  //子类3
  public class Son3 extends Father
  //重写父类打孩子方法
  public void hitChild()
  System.out.println("我跑,你打不着!");
  
  
  //测试类
  public class Test
  public static void main(String args[])
  Father father;
  father = new Son1();
  father.hitChild();
  father = new Son2();
  father.hitChild();
  father = new Son3();
  father.hitChild();
  
  
  都调用了相同的方法,出现了不同的结果!这就是多态的表现!
参考技术A 在多态性处理时,有时需要判明某个引用到底指向哪个实例,可以使用的运算符是 instanceof。 参考技术B 子类重写父类方法 参考技术C 您的回答已成功提交!非常感谢您帮助提问者解决难追问

SB

参考技术D 重载和重写

Java三大特性之---多态

面向对象编程有三大特性:封装、继承、多态。

封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而言它的内部细节是隐藏的,暴露给外界的只是它的访问方法。

继承是为了重用父类代码。两个类若存在IS-A的关系就可以使用继承,同时继承也为实现多态做了铺垫。

那么什么是多态呢?多态的实现机制又是什么?请看我一一为你揭开:

所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定的,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而使该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以描述成如下:

酒 a = 剑南春

酒 b = 五粮液

酒 c = 酒鬼酒

这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。

诚然,要理解多态我们就必须要明白什么是“向上转型”。在继承中我们简单介绍了向上转型,这里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父类,剑南春(JNC)、五粮液(WLY)、酒鬼酒(JGJ)是子类。我们定义如下代码:

JNC a = new  JNC();

对于这个代码我们非常容易理解无非就是实例化了一个剑南春的对象嘛!但是这样呢?

Wine a = new JNC();

在这里我们这样理解,这里定义了一个Wine 类型的a,它指向JNC对象实例。由于JNC是继承与Wine,所以JNC可以自动向上转型为Wine,所以a是可以指向JNC实例对象的。这样做存在一个非常大的好处,在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能。

但是向上转型存在一些缺憾,那就是它必定会导致一些方法和属性的丢失,而导致我们不能够获取它们。所以父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在与子类中的方法和属性它就望尘莫及了。

public class Wine {
	
    public void fun1(){
        System.out.println("Wine 的Fun1.....");
        fun2();
    }
 
    public void fun2(){
        System.out.println("Wine 的Fun2...");
    }
}
 
public class JNC extends Wine{
	
    /**
     * @desc 子类重载父类方法
     *        父类中不存在该方法,向上转型后,父类是不能引用该方法的
     * @param a
     * @return void
     */
    public void fun1(String a){
        System.out.println("JNC 的 Fun1...");
        fun2();
    }
 
    /**
     * 子类重写父类方法
     * 指向子类的父类引用调用fun2时,必定是调用该方法
     */
    public void fun2(){
        System.out.println("JNC 的Fun2...");
    }
}
 
public class Test {
    public static void main(String[] args) {
        Wine a = new JNC();
        a.fun1();
    }
}

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

Output:

Wine 的Fun1.....

JNC 的Fun2...

从程序的运行结果中我们发现,a.fun1()首先是运行父类Wine中的fun1().然后再运行子类JNC中的fun2()。

分析:在这个程序中子类JNC重载了父类Wine的方法fun1(),重写fun2(),而且重载后的fun1(String a)与 fun1()不是同一个方法,由于父类中没有该方法,向上转型后会丢失该方法,所以执行JNC的Wine类型引用是不能引用fun1(String a)方法。而子类JNC重写了fun2() ,那么指向JNC的Wine引用会调用JNC中fun2()方法。

所以对于多态我们可以总结如下:

指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。

对于面向对象而已,多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

 

多态的实现

2.1实现条件

在刚刚开始就提到了继承在为多态的实现做了准备。子类Child继承父类Father,我们可以编写一个指向子类的父类类型引用,该引用既可以处理父类Father对象,也可以处理子类Child对象,当相同的消息发送给子类或者父类对象时,该对象就会根据自己所属的引用而执行不同的行为,这就是多态。即多态性就是相同的消息使得不同的类做出不同的响应。

Java实现多态有三个必要条件:继承、重写、向上转型。

继承:在多态中必须存在有继承关系的子类和父类。

重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。

向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

2.2实现形式

在Java中有两种形式可以实现多态。继承和接口。

2.2.1、基于继承实现的多态

基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。

public class Wine {
	
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Wine(){
    }
 
    public String drink(){
        return "喝的是 " + getName();
    }
 
    /**
     * 重写toString()
     */
    public String toString(){
        return null;
    }
}
 
public class JNC extends Wine{
	
    public JNC(){
        setName("JNC");
    }
 
    /**
     * 重写父类方法,实现多态
     */
    public String drink(){
        return "喝的是 " + getName();
    }
 
    /**
     * 重写toString()
     */
    public String toString(){
        return "Wine : " + getName();
    }
}
 
public class JGJ extends Wine{
	
    public JGJ(){
        setName("JGJ");
    }
 
    /**
     * 重写父类方法,实现多态
     */
    public String drink(){
        return "喝的是 " + getName();
    }
 
    /**
     * 重写toString()
     */
    public String toString(){
        return "Wine : " + getName();
    }
}
 
public class Test {
    public static void main(String[] args) {
    	
        //定义父类数组
        Wine[] wines = new Wine[2];
        //定义两个子类
        JNC jnc = new JNC();
        JGJ jgj = new JGJ();
 
        //父类引用子类对象
        wines[0] = jnc;
        wines[1] = jgj;
 
        for(int i = 0 ; i < 2 ; i++){
            System.out.println(wines[i].toString() + "--" + wines[i].drink());
        }
        System.out.println("-------------------------------");
    }
}

OUTPUT:

Wine : JNC--喝的是 JNC

Wine : JGJ--喝的是 JGJ

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

在上面的代码中JNC、JGJ继承Wine,并且重写了drink()、toString()方法,程序运行结果是调用子类中方法,输出JNC、JGJ的名称,这就是多态的表现。不同的对象可以执行相同的行为,但是他们都需要通过自己的实现方式来执行,这就要得益于向上转型了。

我们都知道所有的类都继承自超类Object,toString()方法也是Object中方法,当我们这样写时:

    Object o = new JGJ();
    System.out.println(o.toString());

输出的结果是Wine : JGJ。

Object、Wine、JGJ三者继承链关系是:JGJ—>Wine—>Object。所以我们可以这样说:当子类重写父类的方法被调用时,只有对象继承链中的最末端的方法才会被调用。但是注意如果这样写:

    Object o = new Wine();
    System.out.println(o.toString());

输出的结果应该是Null,因为JGJ并不存在于该对象继承链中。

所以基于继承实现的多态可以总结如下:对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。

如果父类是抽象类,那么子类必须要实现父类中所有的抽象方法,这样该父类所有的子类一定存在统一的对外接口,但其内部的具体实现可以各异。这样我们就可以使用顶层类提供的统一接口来处理该层次的方法。

2.2.2、基于接口实现的多态

继承是通过重写父类的同一方法的几个不同子类来体现的,那么接口就是通过实现接口并覆盖接口中同一方法的几不同的类体现的。

在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。

继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。

 

三、经典实例。

通过上面的讲述,可以说是对多态有了一定的了解。现在趁热打铁,看一个实例。该实例是有关多态的经典例子,摘自:http://blog.csdn.net/thinkGhoster/archive/2008/04/19/2307001.aspx。

public class A {
	
    public String show(D obj) {
        return ("A and D");
    }
 
    public String show(A obj) {
        return ("A and A");
    } 
}
 
public class B extends A{
	
    public String show(B obj){
        return ("B and B");
    }
 
    public String show(A obj){
        return ("B and A");
    } 
}
 
public class C extends B{
 
}
 
public class D extends B{
 
}
 
public class Test {
    public static void main(String[] args) {
    	
        A a1 = new A();
        A a2 = new B();
        B b = new B();
        C c = new C();
        D d = new D();
 
        System.out.println("1--" + a1.show(b));
        System.out.println("2--" + a1.show(c));
        System.out.println("3--" + a1.show(d));
        System.out.println("4--" + a2.show(b));
        System.out.println("5--" + a2.show(c));
        System.out.println("6--" + a2.show(d));
        System.out.println("7--" + b.show(b));
        System.out.println("8--" + b.show(c));
        System.out.println("9--" + b.show(d));      
    }
}

运行结果:

1--A and A

2--A and A

3--A and D

4--B and A

5--B and A

6--A and D

7--B and B

8--B and B

9--A and D

在这里看结果1、2、3还好理解,从4开始就开始糊涂了,对于4来说为什么输出不是“B and B”呢?

首先我们先看一句话:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。这句话对多态进行了一个概括。其实在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

分析:

从上面的程序中我们可以看出A、B、C、D存在如下关系。

首先我们分析5,a2.show(c),a2是A类型的引用变量,所以this就代表了A,a2.show(c),它在A类中找发现没有找到,于是到A的超类中找(super),由于A没有超类(Object除外),所以跳到第三级,也就是this.show((super)O),C的超类有B、A,所以(super)O为B、A,this同样是A,这里在A中找到了show(A obj),同时由于a2是B类的一个引用且B类重写了show(A obj),因此最终会调用子类B类的show(A obj)方法,结果也就是B and A。

按照同样的方法我也可以确认其他的答案。

方法已经找到了但是我们这里还是存在一点疑问,我们还是来看这句话:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。这我们用一个例子来说明这句话所代表的含义:a2.show(b);

这里a2是引用变量,为A类型,它引用的是B对象,因此按照上面那句话的意思是说有B来决定调用谁的方法,所以a2.show(b)应该要调用B中的show(B obj),产生的结果应该是“B and B”,但是为什么会与前面的运行结果产生差异呢?这里我们忽略了后面那句话“但是这儿被调用的方法必须是在超类中定义过的”,那么show(B obj)在A类中存在吗?根本就不存在!所以这句话在这里不适用?那么难道是这句话错误了?非也!其实这句话还隐含这这句话:它仍然要按照继承链中调用方法的优先级来确认。所以它才会在A类中找到show(A obj),同时由于B重写了该方法所以才会调用B类中的方法,否则就会调用A类中的方法。

所以多态机制遵循的原则概括为:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法,但是它仍然要根据继承链中方法调用的优先级来确认方法,该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

 

参考资料:http://blog.csdn.net/thinkGhoster/archive/2008/04/19/2307001.aspx。

百度文库:http://wenku.baidu.com/view/73f66f92daef5ef7ba0d3c03.html

以上是关于什么体现了类的多态性?的主要内容,如果未能解决你的问题,请参考以下文章

多态与抽象以及接口

Java编程 实现类的继承与多态 写一个动物类。成员变量为动物的种类,成员方法是动物叫声。

知识点梳理2

什么是多态性,使用多态有什么好处?

Java多态的理解

什么是多态(polymorphism)?