前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详解 Java 对象与内存控制(下)

详解 Java 对象与内存控制(下)

作者头像
CoderJed
发布2018-09-13 10:33:56
3060
发布2018-09-13 10:33:56
举报
文章被收录于专栏:Jed的技术阶梯Jed的技术阶梯

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作为限定符来访问父类的类变量
  • 建议使用父类类名来访问父类类变量,这样代码可读性好
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.05.30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 继承成员变量和继承方法的区别
  • 2.同名的实例变量在内存中是怎么存的?
  • 3. super 是什么?是父类的默认实例吗?
  • 4. 父类、子类的类变量
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档