A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© barlay 中级黑马   /  2013-12-14 09:06  /  2060 人查看  /  18 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 barlay 于 2013-12-14 19:20 编辑

多态的前提是继承,比如类Dog extends Animal,然后在Dog中复写了Animal中的eat()方法,问题就来了,到底Dog类有没有真正覆盖Animal中的方法呢?
     自己写代码测试了一下,在Dog中通过super.eat()访问的是Animal中的方法,说明子类Dog没有复写Animal中的方法,但是通过泛型:Animal a = new Dog(); a.eat();这时访问的是Dog中复写的eat()方法。问题是方法复写到底是怎么复写的,改变了什么地方?而在子类中通过super访问的话就没有改变?

评分

参与人数 1技术分 +1 收起 理由
乔兵 + 1

查看全部评分

18 个回复

正序浏览
子类给父类赋值
父类的引用指向子类的对象
回复 使用道具 举报
Kyle 中级黑马 2013-12-14 21:59:42
18#
本帖最后由 Kyle 于 2013-12-14 22:22 编辑

想要完全弄懂这个问题需要将整个多态在内存中的机理完全弄懂才能理解。纯打文字太抽象,我直接画一张图讲解吧。



输出结果:
10
Dog

如图。 在使用父类(Animal)引用a创建子类对象(Dog)的时候。
实际在堆内存中开辟的对象new Dog()是分两块区域保存数据的,一部分是super区域,即父类内容。
一部分是子类的区域,即子类自己描述的内容。
由于变量a是Animal类的引用,所以a.num指向的其实是Animal中自己的成员num,所以输出的答案是10。

至于a.eat()就比较特别了,他是成员方法,这时候Java编译运行的时候会先去方法区中Animal.class寻找是否有eat()这个方法,
一旦发现Animal.class中存在这个方法,虚拟机会继续去子类中寻找这个方法是否被复写过,如果被复写了,就输出子类的eat()方法,如果没被复写,则输出自己的eat()方法。
这种行为在Java中被称之为“动态绑定机制”。


同时还需要注意一点,Java在动态绑定的时候,先找的是父类方法,即Animal里的eat()方法,如果Animal里没有eat(),而Dog里有eat()方法会怎么样呢?
答案是编译的时候就报错了,因为Java虚拟机一开始就找不到Animal里的eat()方法就不会向下查找子类中是否有这个方法了。

说到这顺便就把这部分的静态绑定机制也说一说。
什么是静态绑定呢? 就是eat()方法被static修饰的时候,这时候运行a.eat(),答案将会是“Animal”,即,输出Animal中的eat()方法。
这个很好理解,因为动态的机制被修改成静态了嘛,他就不会动态的去绑定了。

说了这么多,楼主理解了吗? 我说说我总结之后记忆这种多态行为的方法:

Animal a = new Dog();
除了非静态函数,编译运行的时候都是看等号左边。
非静态成员函数编译的时候看左边,运行的时候看右边。

打字加画图花了不少时间精力,版主看到了多给点分呗,嘿嘿~




评分

参与人数 1技术分 +1 收起 理由
简★零度 + 1

查看全部评分

回复 使用道具 举报
barlay 中级黑马 2013-12-14 20:28:54
17#
回音 发表于 2013-12-14 19:43
1. 误会了哈,编译的时候只是检查代码合理性,并不去运行。如果代码中出现了ref.method()而method()没有出 ...

这回真遇到大神了,太感谢了O(∩_∩)O哈!,虽然你的回答我没怎么看懂,但是感觉你说的很对,我再思考一下。
回复 使用道具 举报
回音 中级黑马 2013-12-14 19:43:03
16#
本帖最后由 回音 于 2013-12-14 20:20 编辑
barlay 发表于 2013-12-14 19:06
有想法,非常好,但是你说的有几个有矛盾的地方:
1、ref的编译时类型是Animal。所以代码中不能调用Anima ...

1. 误会了哈,编译的时候只是检查代码合理性,并不去运行。如果代码中出现了ref.method()而method()没有出现在Animal类的方法区中(这个涉及到类的加载过程,编译器在检查代码时就可以发现这个问题),就会编译失败。我贴子中的“调用”指的是代码中出现了上述形式的语句,而不是真的运行了。
2. 如果内存中不存在方法的话,那编译时就不可能发现你调用了一个类中没有的方法。事实上编译过程中方法会被加载到方法区。这个属于类的初始化过程,不需要生成对象,因而就不需要运行程序。建议你看一下类的初始化及其内存分配过程。当然我也是模模糊糊看了一些,网上关于这方面的资料好像都不太清晰。
3. 编译时类型,运行时类型这个是网上有些人的通俗说法。正规来说应该叫Run-time Type Identification, RTTI(运行时类识别)。或者叫动态绑定。我们通常说的多态(polymorphism)其实就是RTTI。可以看下英文资料http://www.eecs.qmul.ac.uk/~mmh/TIJ2/Chapter12.html,
或者中文资料http://hxraid.iteye.com/blog/428891。深究起来是很复杂的。
4. RTTI存在的一个简单证据:
public class Test
{
    public static void main(String[] args)
    {
        Parent c = new Child();
        System.out.println(c.getClass().getName());
        Parent d = new Parent();
        System.out.println(d.getClass().getName());
    }
}

class Parent
{
    int a;
}

class Child extends Parent
{
    int b;
}
显示c的类是Child,d的类是Parent。





评分

参与人数 1黑马币 +10 收起 理由
barlay + 10 很给力!

查看全部评分

回复 使用道具 举报 1 0
回音 发表于 2013-12-14 16:10
多态的根本原因在于父类引用变量在编译时拥有一个类型,叫编译时类型,在执行时有另一个类型,叫运行时类型 ...

觉得解释的很好.学习了.
回复 使用道具 举报
barlay 中级黑马 2013-12-14 19:06:22
14#
本帖最后由 barlay 于 2013-12-14 19:30 编辑
回音 发表于 2013-12-14 16:10
多态的根本原因在于父类引用变量在编译时拥有一个类型,叫编译时类型,在执行时有另一个类型,叫运行时类型 ...


有想法,非常好,但是你说的有几个有矛盾的地方:
1、ref的编译时类型是Animal。所以代码中不能调用Animal中没有的方法。
问题:代码中调用Animal中的方法不是运行时候的事吗?
2、自始至终你的每一行代码都载入了内存,没有被JVM吃掉,只是JVM它选择性的挑了一个给你看。
问题:堆内存中不存放方法的代码的,只存放成员变量和方法的引用。

我也有了新的想法:1、编译时类型不知道你从哪里听到的,但我觉得这是说的是方法区里被类加载器加载的类;
2、内存中存放的是成员方法的引用,那么既然叫Override(覆盖),那内存中方法的引用就真的被改变了,指向了方法区中子类复写的方法,而super指向的是方法区中父类原有的方法,这样就很好的解释了为什么通过多态调用父类方法和通过super调用父类方法的不同!

谢谢大侠让我灵感突现,在大侠的启发下圆满的解释了这个问题,O(∩_∩)O哈哈~
回复 使用道具 举报
在Dog中通过super.eat()访问的是Animal中的方法,说明子类Dog没有复写Animal中的方法,但是通过泛型 红色字体不对 ,这个不能说明没有复写,super 关键字 是直接调用父类的方法,更不能叫做泛型。。。Animal a = new Dog(); a.eat();这时访问的是Dog中复写的eat()方法。 这个是必然的 。。。
下面是多态的解析和代码。。。三种不同动物有各自特有的呼吸方式。所以复写了父类的呼吸方式。调用时跟别调用各自的。如果需要调用父类的方法 可以用super关键字

//多态
//多态是指同一个方法根据上下文使用不同的定义的能力。方法的覆写,和方法的重载都可以被看成多态。。
//但是Java的多态更多是动态绑定放在一起理解的。
//动态绑定是一种机制,通过此机制,对一个已经被重写的方法的调用将会发生在运行时,而不是在编译时去解析。
//下面程序演示动态的绑定

//首先说重载(overload)是发生在同一类中。与什么父类子类、继承毫无关系。
//标识一个函数除了函数名外,还有函数的参数(个数和类型)。也就是说,一个类中
//可以有两个或更多的函数,叫同一个名字而他们的参数不同。 他们之间毫无关系,是
//不同的函数,只是可能他们的功能类似,所以才命名一样,增加可读性,仅此而已!

//再说覆盖(override),是发生在子类中!也就是说必须有继承的情况下才有覆盖发生。
//我们知道继承一个类,也就有了父类了全部方法,如果你感到哪个方法不爽,功能要变,那
//就把那个函数在子类中重新实现一遍。 这样再调用这个方法的时候,就是执行子类中的过程
//了。父类中的函数就被覆盖了。(当然,覆盖的时候函数名和参数要和父类中完全一样,不然
//你的方法对父类中的方法就不起任何作用,因为两者是两个函数,毫不关系)
public class DuoTai {
        public static void main(String[] args) {
                Animal[] animal = new Animal[3];
                //创建不同的对象,都存入Animal的引用中
                animal[0] = new Animal();
                animal[1] = new Sheeps();
                animal[2] = new Fish();
               
                DuoTai dt = new DuoTai();
               
                dt.move(animal[0]);
                dt.move(animal[1]);
                dt.move(animal[2]);
               
                //move 方法首先判断animal对象是哪个类的对象,由判断执行不同的方法。
                //在判断过程中使用了instanceof运算符,它是用来判断对象类型的一个运算符。
                //当判断出它的类型后,在对其进行类型转换,得到原始的类型转换后,就可以调用它特有的方法了
                //运行结果
                /*动物做运动
                羊上山跑着玩
                鱼在水中自由游*/               
               
                //animal[0].breath();
                //animal[1].breath();
                //animal[2].breath();
               
                //定义一个数组存放Animal对象,存3个元素,分别是Animal、Sheeps、Fish。
                //分别调用breath()方法。程序执行结果
               
                /*动物呼吸
                羊是用肺呼吸的
                鱼是用腮呼吸的*/
               
                //对象是多态的,定义一个Animal对象,它既可以存Animal对象,也可以存放Animal的子类的对象
                //在子类Sheeps。Fish对象。执行breath方法时会自动调用原来对象的方法。而不是父类的方法
                //这就是动态绑定
               


        }
       
        void move(Animal animal) {
                //进行对象类型的判断
                if(animal instanceof Sheeps){
                        ((Sheeps)animal).sheepsRun();
                }else if (animal instanceof Fish) {
                        ((Fish)animal).swim();
                }else {
                        animal.run();
                }
        }


}


class Animal{
        String type;
        String name;
        int age;
       
        void eat()        {
                System.out.println("动物吃食物");               
        }
       
        void breath() {
                System.out.println("动物呼吸");
        }
       
        void run() {
                System.out.println("动物做运动");
        }
       
}




class Sheeps extends Animal {
        String sheepsType;
       
        void sheepsRun(){
                System.out.println("羊上山跑着玩");
        }
       
        void breath() {
                System.out.println("羊是用肺呼吸的");
        }
       
}


class Fish extends Animal {
        String fishType;
        void swim() {
                System.out.println("鱼在水中自由游泳");
        }
       
        void breath() {
                System.out.println("鱼是用腮呼吸的");
        }
}









评分

参与人数 1技术分 +1 收起 理由
简★零度 + 1

查看全部评分

回复 使用道具 举报
風諾 中级黑马 2013-12-14 17:20:55
12#
barlay 发表于 2013-12-14 15:24
你这在讲继承,我问的问题你仔细看了没有,都不在点上。有没有大神能给透彻的讲一下,要深入,不要浮于表 ...

我的回答你仔细理解了没有?覆盖不体现在继承上体现在哪里?

覆盖只是一种形式上的体现,实际子父类内容根本没有改变
非要直接回答你的问题就是:
1、没有真正改写父类,覆盖只是看上去
2、你自己super.eat()验证了1中说的
3、Animal a = new Dog(); a.eat();体现出来子类的方法,这种同名方法调用子类方法的体现叫做覆盖
4、复写的改变就在于子类没有和父类同名eat()方法,调用父类方法,有自己的eat()方法调用自己的
5、super是访问父类内容。3、4说了,覆盖就是一种体现(或者说出现3的情况,我们称作覆盖)

我理解的就是这样,觉得楼主你自己在按照“覆盖”这个词的意思语文意思想问题
回复 使用道具 举报
欢欢 高级黑马 2013-12-14 16:31:13
11#
class Animal
{
         public void eat()
         {
               System.out.println("fu.eat()");
         }
}

class Dog extends Animal
{
          public void eat()
          {
                    System.out.println("zi.eat()");
          }
}

Animal a = new Dog();
a.eat();
由于子类和父类有相同的方法,用父类引用创建子类对象,调用的不是父类的方法而是子类的方法,就说明子类的方法把父类的方法覆盖了。
你说的在子类中用super引用调用的是父类的方法,与覆盖重写无关。

评分

参与人数 1技术分 +1 收起 理由
乔兵 + 1

查看全部评分

回复 使用道具 举报
回音 中级黑马 2013-12-14 16:10:38
10#
本帖最后由 回音 于 2013-12-14 16:15 编辑

多态的根本原因在于父类引用变量在编译时拥有一个类型,叫编译时类型,在执行时有另一个类型,叫运行时类型。而这两个类型可以相同,也可以不同。一个引用变量的编译时类型是赋值表达式左边的类型,运行时类型是赋值表达式右边的类型。举个栗子:Animal ref = new Dog(....)
ref的编译时类型是Animal。所以代码中不能调用Animal中没有的方法。假设Dog复写了Animal中的某个方法。ref的运行时类型是Dog,运行的就是Dog复写后的方法。
那为什么super关键字可以调用父类“未被复写的”方法呢?我觉得复写这个词很具有误导性。内存中复写前和复写后的方法都存在,不是你死我活的关系,应该叫“多态”方法更准确些。super调用了父类内存中的方法,表现给我们看的效果就是“复写前”。this调用了子类内存中的方法,表现给我们看的效果就是“复写后”。自始至终你的每一行代码都载入了内存,没有被JVM吃掉,只是JVM它选择性的挑了一个给你看。
一句话总结,复写没有改变任何东西,只是多了一个选择而已。只不过因为一般只能上转换,也就是运行时类型是编译时类型的子类,看上去引用变量调用的只能是子类的“复写后”方法,“被复写”的方法“似乎”被消灭了。其实它还顽强滴活在父类的内存里,一个super就能调出来。

评分

参与人数 1技术分 +1 收起 理由
乔兵 + 1

查看全部评分

回复 使用道具 举报
barlay 中级黑马 2013-12-14 15:24:03
9#
風諾 发表于 2013-12-14 11:15
覆盖只是一种体现:
子类继承父类,同时继承了父类的成员变量和方法
在子类没有和父类重名的方法的时候, ...

你这在讲继承,我问的问题你仔细看了没有,都不在点上。有没有大神能给透彻的讲一下,要深入,不要浮于表面啊!
回复 使用道具 举报
建议遇到问题最好把代码贴一下!
回复 使用道具 举报
風諾 中级黑马 2013-12-14 11:15:04
7#
barlay 发表于 2013-12-14 10:32
(⊙o⊙)…额,搞混了。既然你说子类方法会自动覆盖父类方法,又说super代表的是当前对象的父类引用,那么 ...

覆盖只是一种体现:
子类继承父类,同时继承了父类的成员变量和方法
在子类没有和父类重名的方法的时候,你可以调用父类方法,或者子类特有方法
当子类方法和父类重名,你调用该重名方法,运行的是子类的方法,看起来就是子类方法把父类方法屏蔽了,叫做覆盖
回复 使用道具 举报
super指的是父类,调用父类的方法。而Animal a = new Dog(); a.eat();
new Dog( )对象是属于Dog的一个对象
eg:是将new Dog( )赋给 Animal 的对象a。 访问的还是Dog的对象方法。eat()
我这样理解的、
多态

评分

参与人数 1技术分 +1 收起 理由
乔兵 + 1

查看全部评分

回复 使用道具 举报
❦_H_t 发表于 2013-12-14 09:59
楼主,这是面向对象多态的知识点,而不是泛型。泛型的书写格式为
public class Person
{

(⊙o⊙)…额,搞混了。既然你说子类方法会自动覆盖父类方法,又说super代表的是当前对象的父类引用,那么在子类对象中调用父类的方法,应该是已经覆盖的方法啊,这个地方我就是搞不懂啊!
回复 使用道具 举报
楼主,这是面向对象多态的知识点,而不是泛型。泛型的书写格式为
public class Person<String>
{

}
而你的问题出在:多态中父类引用指向子类对象时,子类方法会自动覆盖父类方法。这是多态的特性
子类方法用super.eat()访问的当然是父类的方法,因为super代表的是当前对象的父类引用。你在子类对象中调用父类的方法,那么运行的肯定是父类的方法,这个有问题吗?而并不是你所说的并没有覆盖父类方法,你直接用this.eat();就会发现已经覆盖了

评分

参与人数 1技术分 +1 收起 理由
乔兵 + 1

查看全部评分

回复 使用道具 举报
首先,这是多态,不是泛型
Animal a = new Dog();a.eat();对于该语句:
编译的时候看a有没有eat()方法
实际运行的时候,是根据实例对象来调用方法,与引用类型无关
回复 使用道具 举报
super指的是父类,通过super访问父类的eat方法,a.eat()是对象访问dog里的eat啊
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马