6个示例解决关于继承和多态的疑问

当看到比较有难度的题目时,就忍不住想复制粘贴过来,以供回头再学习。

参考博客:https://blog.csdn.net/lihengjing1968/article/details/50883440

参考博客:https://blog.csdn.net/ypt523/article/details/79598289

参考博客:https://www.cnblogs.com/TalkWithWorld/p/5641169.html

参考博客:https://blog.csdn.net/zhangting19921121/article/details/87185665

示例5参考博客:https://blog.csdn.net/cauchyweierstrass/article/details/48943077

示例6参考博客:https://www.cnblogs.com/jianxia612/articles/1255837.html

下面给出几个示例,见到难题就忍不住ctrl c和ctrl v.

示例1:

public class A {    

    public int a = 0;

    public void fun(){

        System.out.println("-----A-----");

    }

}

public class B extends A{

    public int a = 1;

    public void fun(){

        System.out.println("-----B-----");

    }

public static void main(String[] args){

        A classA = new B();     

        System.out.println(classA.a);

        classA.fun();

    }   

}
//输出的结果:

0

---------B--------

多态小结:

变量多态看左边,

方法多态看右边,

静态多态看左边。(静态方法只能用静态方法重写)

    关于静态方法只能用静态方法重写的说明:(静态方法称为重写不准确)

另一种记法:

分类:运行时多态和编译时多态(除了重写都看左侧类型)

          1)运行时多态:方法重写(看右侧的类型)(重写永远是针对实例方法,其他都不叫重写)

          2) 编译时多态:方法重载(先继承过来,再重载方法)。(看左侧的类型)

    JAVA静态方法形式上可以重写,但从本质上来说不是JAVA的重写。因为静态方法只与类相关,不与具体实现相关,声明的是什么类,则引用相应类的静态方法(本来静态无需声明,可以直接引用),看下例子:

示例2:

class Base{

        static void a( ){System.out.println("A");  }

                 void b( ){System.out.println("B"); }

}

public class  Inherit extends Base{

          static void a( ){System.out.println("C");  }

                  void b( ){System.out.println("D"); }

           public static void main(String args[]){

                    Base b=new Base();

                    Base  c=new Inherit();

                    b.a();

                    b.b();

                    c.a();

                    c.b();
         }

}

以上输出的结果是:            A

                            B

                            A

                            D

        非静态方法 按重写规则调用相应的类实现方法,而静态方法只与类相关。

        所谓静态,就是在运行时,虚拟机已经认定此方法属于哪个类。

        专业术语有严格的含义,用语要准确."重写"只能适用于实例方法.不能用于静态方法.对于静态方法,只能隐藏(刚才的例子可以重写那只是形式上的 ,并不满足多态的特征,所以严格说不是重写)。

         静态方法的调用不需要实例化吧..  不实例化也就不能用多态了,也就没有所谓的父类引用指向子类实例.因为不能实例化 也就没有机会去指向子类的实例。所以也就不存在多态了。

示例3:

这道题中,关键点在于说,父类构造函数调用了在子类中重写的函数之后,在new子类对象的时候,会执行子类中重写的函数,而不会执行父类中原有的函数。比如这题的setValue函数。为什么???

参考博客:https://blog.csdn.net/Bettarwang/article/details/26160183 

博客指出:父类构造方法中调用子类重写的方法时,涉及到编译时类型和运行时类型的问题,new子类对象的时候,首先调用父类构造函数,编译时类型是父类,但是运行时编程了类型是子类,因此调用的是子类重写的方法,而不是父类的方法。在写程序时,父类构造方法尽量不要调用其他方法,如果要调用,不能调用被子类重写的方法,否则会出现意想不到的错误,为了避免这种情况,不得已调用方法时,要么调用父类的私有方法,要么调用fianl方法,因为他们都是不能被子类重写的方法。

此外,在父类构造方法中调用子类重写方法时,还涉及到成员变量的初始化和赋值顺序问题,这个要研究底层的jvm原理。

示例4:感觉有难度

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(a1.show(b));

        System.out.println(a1.show(c));

        System.out.println(a1.show(d));

        System.out.println("============");

        System.out.println(a2.show(b));

        System.out.println(a2.show(c));

        System.out.println(a2.show(d));

        System.out.println("============");

        System.out.println(b.show(b));

        System.out.println(b.show(c));

        System.out.println(b.show(d)); //这里调用b.show(d)的时候,实际上b中是有继承A中的show(D d)放方法的,因此,一定会执行这个方法,而不是执行show(B b)的方法。

        

    }

}



class A {

    public String show(D d) {

        return "A and D";

    }





    public String show(A a) {

        return "A and A";

    }

}



class B extends A {

    public String show(B b) {

        return "B and B";

    }

    @Override

    public String show(A a) {

        return "B and A";

    }

}



class C extends B {}

class D extends B {}

//输出结果:想想为什么???

A and A

A and A

A and D

============ //优先调用最短的继承路径

B and A (有挑战哦)   为什么???   这里不是调用show(B b)这个方法,原因就是该方法不是对父类show方法的重写,类型不一样,多态是调用子类重写的那个方法

B and A (有挑战哦)   为什么????

A and D      //这里调用b.show(d)的时候,实际上b中是有继承A中的show(D d)放方法的,因此,一定会执行这个方法,而不是执行show(B b)的方法。

============ //对外类型会影响调用哦

B and B

B and B

A and D

示例5:有点难了

public class App {

public static void main(String[] args)throws Exception{

      Sub b=new Sub();   

        b.callName();    //这一步是多态的 使用,很好理解



}

}

class Base{

      private String baseName="base";

      public Base(){

            System.out.println("hellobase");

            callName();

      }

      public void callName(){

            System.out.println(baseName);

      }

      

}

class Sub extends Base{

      private String baseName="sub";

      public void callName(){

            System.out.println("hello");

            System.out.println(baseName);

      }

}



public static void main(String[] args ) {

    Base b = new Sub();    

}

}

输出是null  为什么???

Base b = new Sub();先初始化父类,然后调用子类的callName方法时子类的属性还没有初始化执行代码,所以打印的是null.

(经过个人的调试,终于发现了构造类时 变量的初始化顺序的秘密)

为了方便,本人重新写了一份代码,下面记录我的代码执行顺序:

1 public class ForTest {
		public static void main(String[] args){
2			A b=new B();   //加上断点  
		}
}

 class A{                    
3	int a=3;             //加上断点
	public A(){
        //默认super()
4		System.out.println(a);   //加上断点
5		method();
	}
7	public void method(){
6		System.out.println(a);
	}
}
class B extends A{
8	int a=2;   //加上断点
	public B(){
9		System.out.println("hello"); 
10		System.out.println(a);
	}
	public void method(){
11		System.out.println(a);   //加上断点
	}
}

图解:

从上一步到下一步都是点击step over:

step 1:进入调试  代码在 行2 处

step 2:  代码在行3 处。

想一下为什么一下就到了父类A中的变量赋值处?实际上在new子类的时候,调用了父类A的构造函数,你所看到的是一下子就到了父类A中的变量初始化这儿,也就是说变量在new一个类对象的时候,先执行的变量初始化的,再执行构造函数,如果仅仅是这样记忆,又会产生疑惑,你肯定会想,那这里在从父类构造函数中调用子类的callName的时候,肯定能打印出a=2 这个值啊,但是实际上打印的是0,也就是说此时的a还是默认初始值0,那难道上面说的new对象的时候 先执行变量初始化是错的???实际上,一般情况下,不用考虑父类构造函数调用子类重写的方法的时候,这样记忆确实是对的,但是原理是什么,原理这样是错的,原理上,并不是初始化先执行,下面给出我的分析:

根本原因在于,这里忘了分析最最关键的一步,实际上,new类对象的时候的时候,最先执行的还是构造函数,这里执行的一定是构造函数的第一步super()函数,它去首先构造了父类的对象,所有类都是Object的子类,所以会一直向上构造父类,知道Object,当父类构造完毕,super执行完毕,再返回本类中,从代码块,变量的初始化开始执行,这里要注意,此时,本类构造函数super()函数执行完毕,并没有继续执行构造函数中super()函数下面的语句,而是去执行了变量的初始化,然后当构造代码块和变量初始化完毕,再继续执行构造函数super()以下的语句,所以导致一种假象,我们看起来就是先执行了构造代码块和变量的初始化,最后再执行构造函数,实际上是第一步执行了构造函数,但是只执行了super(),它去构造父类对象去了。

为了验证我上面的分析,在new一个对象的时候,我们可以将super()函数显示写在构造函数中,再打上断点,你会发现,一定是先执行super()函数(如果该类父类不是Object,那么就会走到父类的构造函数,如果该类的父类是Object,也会走到父类Object,只不过Object是一种特殊的类,与jvm底层相关),然后等父类构造完毕之后,才会执行变量的初始化。

step3: 到达4 ,此时打印a,值为3

step 4 :到达5,

step 5: 到达11 ,这里父类构造函数中调用的重写方法,是子类的,为什么?看示例3后面的解析。注意,此时a的输出是0,为什么??经过step2的分析可知:此时父类对象还没有构造完成,也就是说B中构造函数中的super()还没有执行完,此时,并不会进行B中变量的初始化,所以a是默认的初始值0.

step 6: 到达8 ,此时,才会对变量a进行赋值,B的父类已经构造完毕。

step7: 到达9    ,step7和step8是执行构造函数中super剩下的步骤。

step8: 到达10

step 9:到达2 ,至此,新对象B的构造过程才算是结束

示例6:指出下面的程序错误的地方,说明原因。答案看链接

interface Playable {

    void play();

}

interface Bounceable {

    void play();

}

interface Rollable extends Playable, Bounceable {

   Ball ball = new Ball("PingPang");

}

class Ball implements Rollable {

    private String name;

    public String getName() {

        return name;

    }

    public Ball(String name) {

        this.name = name;        

    }

    public void play() {

       ball = new Ball("Football");

        System.out.println(ball.getName());

    }

}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值