详解Java对象的内存分配——下篇

1. 继承成员变量和继承方法的区别

Java继承中对成员变量和方法的处理是不同的,看如下代码:

class Base {
    
    public int count = 2;
     
    public void display() {
        System.out.println(this.count);
    }
}

class Derived extends Base {
    
    public int count = 20;

    @Override
    public void display() {
        System.out.println(this.count);
    }
    
}

public class Test {
    public static void main(String[] args) {
        
        Base b1 = new Base();  // (1)
        System.out.println(b1.count);  
        b1.display();  
        
        Derived d1 = new Derived();  // (2)
        System.out.println(d1.count);  
        d1.display();  
        
        Base b2 = new Derived();  // (3)
        System.out.println(b2.count);
        b2.display();
        
        Base b3 = d1;  // (4)
        System.out.println(b3.count);
    }
}

结果:
2
2
20
20
2
20
2

分析:

  • (1) 都输出2
  • (2) 都输出20
  • (3) 根据详解Java对象的内存分配——上篇中内容的介绍,我们知道当编译时类型和运行时类型不同时,访问实例变量将访问 Base 的 count,而调用方法将调用 Derived 的方法,所有前者输出2,后者输出20
  • (4) 把 d1 赋值给 b3,但是 b3 的类型是 Base,这意味着 d1 和 b3 指向了同一个 Java 对象,如果在程序中判断 d1 == b3,将得到 true,但是访问 b3.count 得到2,访问 d1.count 得到 20,这说明:在 b3 和 d1 指向的 Java 对象中包含了两块内存,分别存放值为2的 count 的实例变量和值为20 的 count 的实例变量
  • 总结:不管变量的编译时类型和运行时类型相不相同,通过这个变量访问它们指向的对象的方法时,方法的行为总是表现为实际类型的行为,但如果通过这些变量来访问它们指向的对象的实例变量,这些是实例变量的值总是表现出声明这些变量所用类型的行为

编译器处理方法和成员变量的区别:

  • (1) 对于父类中的 public 修饰的实例变量,当子类继承父类后,编译器会把父类中的实例变量"移动"到子类中,如果子类中没有定义同名的实例变量,那么子类就只拥有一个实例变量,就是父类的实例变量,如果子类中已经定义了同名的实例变量,那么子类将拥有同名的两个实例变量,一个是自己的,一个父类的
  • (2) 对于父类中用 public 修饰的方法,当子类继承父类后,编译器会尝试把父类中的方法"移动"到子类中,如果子类没有重写父类的方法,那么"移动"成功,子类会拥有父类的方法,如果子类重写了父类的方法,那么"移动"失败,子类只会拥有使用自己重写后的方法
  • (3) 总结一句话,子类可以同时拥有父类和自己同名的实例变量,但对于方法来说,不管是否重写父类的方法,同名的方法子类只能拥有一个,要么是父类的,要么是自己重写的

2.同名的实例变量在内存中是怎么存的?

看以下代码:

class Base { int count = 2; }

class Mid extends Base { int count = 20; }

class Sub extends Mid {int count = 200; }

public class Test {
    public static void main(String[] args) {
        Sub sub = new Sub();
        Mid mid = sub;
        Base base = sub;
        
        System.out.println(sub.count);
        System.out.println(mid.count);
        System.out.println(base.count);
    }
}

结果:
200
20
2

以上程序说明:sub、mid和base这3个变量指向的Java对象拥有3个count实例变量,也就是说,需要3块内存来存储它们
Sub sub = new Sub();这句执行完后,该对象在内存中的存储如下图所示:

内村中并不存在 Mid 和 Base 两个对象,只有一个 Sub 对象,只是这个 Sub 对象中不仅保存了在 Sub 类中定义的所有实例变量,还保存了它的所有父类所定义的全部实例变量,程序通过 Base 型变量访问该对象的count实例变量,将输出2,通过 Mid 型变量访问该对象的count实例变量,将输出20

3. super 是什么?是父类的默认实例吗?

看以下代码:

class Fruit { 
    
    String color = "unknow";
    
    public Fruit getThis() {
        return this;
    }
    
    public void info() {
        System.out.println("Fruit's info()");
    }
    
}

class Apple extends Fruit {
    
    String color = "red";

    @Override
    public void info() {
        System.out.println("Apple's info()");
    }
    
    public void accessSuperInfo() {
        super.info();
    }
    
    public Fruit getSuper() {
        return super.getThis();
    }
    
}

public class Test {
    public static void main(String[] args) {
        Apple apple = new Apple();
        Fruit fruit = apple.getSuper();
        System.out.println("apple == fruit: " + (apple == fruit));
        System.out.println("apple.color: " + apple.color);
        System.out.println("fruit.color: " + fruit.color);
        apple.info();
        fruit.info();
        apple.accessSuperInfo();
    }
}

运行结果:
apple == fruit: true
apple.color: red
fruit.color: unknow
Apple's info()
Apple's info()
Fruit's info()

注意这个方法:

public Fruit getSuper() {
    return super.getThis();
}

该方法尝试返回super关键字代表的内容,实际上,Java程序允许通过方法return this;返回调用该方法的Java对象,但不允许直接return super或者直接将super当成一个引用变量来使用,接下来会深入的分析这些语法规则

Apple apple = new Apple();
Fruit fruit = apple.getSuper();

apple == fruit 返回true,说明通过apple.getSuper()返回的是该Apple对象本身,只不过它的声明类型或者编译时类型是Fruit,所以通过 fruit 访问实例变量color,得到的是"unknow",而通过fruit调用info(),实际上就是调用apple对象的info()方法,调用accessSuperInfo();时使用super限定调用Fruit类的info()方法,所有该info()方法才真正表现出Fruit类的行为。

通过以上分析得出结论:super关键字本身并没有引用任何对象,它甚至不能被当成一个真正的引用变量,所以以下代码,编译就会报错:

super = new Fruit();
super = apple;
super = fruit;

总结:

  • (1) "super 是父类的默认实例"这种说法是不对的,这也是为了方便理解 super 的作用而使用的一种说法
  • (2) 关于父类子类对象在内存中的分配的准确的结论:
    • <1> 当程序创建一个子类对象时,系统不仅会为该子类中定义的实例变量分配内存,也会为在父类中定义的实例变量分配内存,例如:父类A中定义了2个实例变量,子类B中定义了3个实例变量,那么new B()这个对象时,会分配5块内存来保存5个实例变量
    • <2> 子类中定义的实例变量并不会覆盖父类中定义的实例变量,只是将它们"隐藏"起来了,在特殊的情况下(编译时类型为父类时)可以访问到它们
    • <3> 可以通过super作为限定来指定调用父类的方法或者访问父类的实例变量,这里的super只是一个"限定符",而不是父类的默认实例对象

4. 父类、子类的类变量

类变量属于类本身,因此关于父类和子类的类变量的继承关系并不复杂,看如下代码:

class Base { public static int count = 20; }

class Sub extends Base { 
    
    public static int count = 200; 
    
    public void info() {
        System.out.println("Sub's count = " + count);
        System.out.println("Base's count = " + Base.count);
        System.out.println("Base's count = " + super.count);
    }
    
}

public class Test {
    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.info();
    }
}

结果:
Sub's count = 200
Base's count = 20
Base's count = 20

总结:

  • 在子类中可以使用父类的类名作为主调来访问父类的类变量
  • 在子类中可以使用super作为限定符来访问父类的类变量
  • 建议使用父类类名来访问父类类变量,这样代码可读性好
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,293评论 18 399
  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,640评论 2 9
  • 整理来自互联网 1,JDK:Java Development Kit,java的开发和运行环境,java的开发工具...
    Ncompass阅读 1,516评论 0 6
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,582评论 0 11
  • 如果两个类不必彼此之间直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个了类的某一个方...
    Mitchell阅读 156评论 0 0