java实例对象的编译时类型和运行时类型

为什么要区分编译时类型和运行时类型?

看这样一句代码:Person p=new Women()(Women类继承自Person类)那么,假如p的属性修饰符为public 访问属性时得到的是Person类的属性还是Women类的属性,方法调用又是哪个类?答案:会得到Person类的属性,调用Women类的方法。为什么会这样呢?这里就需要知道什么是编译时类型和运行时类型,Java程序状态会分为编译和运行这两种状态,编译时,JVM会在栈中静态创建基本数据变量,和引用数据变量的引用,回到刚刚那句代码,显然,p这个引用就是在编译时创建的,那么,p的编译时类型就是Person了,当运行这句java代码时,JVM在堆中为p新建一块内存,对应new Women()这句代码,所以p的运行时类型就是Women。有这样一条规则,对象调用编译时类型的属性和运行时类型的方法。下面先用代码表示这样的结果,然后再说明我个人的一些理解。

code1

public class TestDemo1 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Person p=new Women();
        System.out.println("p.name:"+p.name);
        p.show();
    }

}
class Person{
    public String name;
    public Person()
    {
        name="person";
    }
    public void show()
    {
        System.out.println("class person's show()");
    }
}

class Women extends Person
{
    public String name;
    public Women()
    {
        name="women";
    }
    public void show()
    {
        System.out.println("class women's show()");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

结果如下 
这里写图片描述

从代码运行结果可以看出,p调用的属性属于Person类,而调用的方法是Women类的,验证了上面的规则–对象调用编译时类型的属性和运行时类型的方法

个人理解

这里属于我个人的理解,可能有错,以后发现会重新修正 
根据上述规则 
根据继承的特点我们可以知道,子类会继承父类非私有的属性和方法,也就是说,父类的(非私有)属性也会出现在子类中,当然,这是显而易见的,然而关键在于,如果子类重新定义了这一属性,会怎么样呢?实际上,父类的属性并不会被覆盖,为了方便起见,我把从父类继承来的属性记为– 属性<父类> 而自己重新定义的同名属性为–属性<子类> 这样,在子类中,会有两个属性 即:属性<父类> 属性<子类>,那么如何调用呢?–解答:<>中的内容对应着调用该属性的对象的编译时类型,编译时类型为父类,调用属性<父类> ,另一种情况就是调用子类的属性了。下面用图来表示: 
这里写图片描述 
Class A中定义属性a,Class B继承自A,重新定义了属性a,此时,B中有编译时类型为A的属性a和编译时类型为B的属性a,Class C继承自B,自己重新定义了属性a,这时,C具有三种编译时类型的属性a。这样就好看多了,不知道应该调用的属性是哪个类的,就只要分析自己的编译时类型就可以了,调用方法其实不用在意,直接调用运行时类型的方法即可(运行时类型还是比较容易看的)。 
就上图的例子我们用代码测试如下

code2


public class TestDemo2 {

    public static void main(String[] args) {
        // 编译时类型为A,输出应该是A
        System.out.println("编译时类型为A,输出应该是A");
        A a=new A();
        System.out.println(a.name);
        A ab=new B();
        System.out.println(ab.name);
        A ac=new C();
        System.out.println(ac.name);
        // 编译时类型为B,输出应该是B
        System.out.println("编译时类型为B,输出应该是B");
        B b=new B();
        System.out.println(b.name);
        B bc=new B();
        System.out.println(bc.name);
        // 编译时类型为C,输出应该是C
        System.out.println("编译时类型为C,输出应该是C");
        C c=new C();
        System.out.println(c.name);
    }

}
class A
{
    String name="A";
}
class B extends A
{
    String name="B";
}
class C extends B
{
    String name="C";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

运行结果如下 
这里写图片描述

根据运行结果上述解释应该是合理的!

posted @ 2019-05-13 15:31  天涯海角路  阅读(561)  评论(1)    收藏  举报