对象及内存管理——父类与子类实例的内存控制

  • Post author:
  • Post category:其他


3、父类与子类实例的内存控制




继承是面向对象的三大特征之一,也是Java语言的重要特性,而父子继承关系是Java编程中需要重点注意的地方。

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


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


class Derived extends Base {
   int count = 20;
 
   @Override
   public void display() {
     System.out.println(this.count);
   }
}


public class FieldAndMethodTest {
   public static void main(String[] args) {
     Base b = new Base();  //声明并创建Base对象    ①
     System.out.println(b.count);
     b.display();
 
     Derived d = new Derived();  //声明并创建Derived对象    ②
     System.out.println(d.count);
     d.display();
 
     Base bd = new Derived();  //声明一个Base变量,并将Derived对象赋给该变量    ③
     System.out.println(bd.count);
     bd.display();
 
     Base db1 = d;  //让db1变量指向原d变量所指的Derived对象    ④
     System.out.println(db1.count);
     }
}


输出结果为:



2



2



20



20



2



20



2


在Base类和Derived类中,分别定义了一个count实例变量和display()方法。在①行代码处,声明了一个Base变量b,并将一个Base对象赋给该变量,在②行代码处,声明了一个Derived变量d,并将一个Derived对象赋给该变量。所以前4行结果输出的结果都是变量对应的结果。在③行代码处,声明了一个Base变量bd,却将Derived对象赋给该变量,那么直接通过db访问count实例变量,输出的将是声明时类型的对象的count实例变量,也就是Base类的count实例变量;如果通过db来调用display()方法,该方法表现的是运行时类型对象的行为,也就是Derived对象的行为。在④行代码处,将变量d赋值给db1变量,这意味着db1和d变量同时指向同一个Java对象,但是分别访问它们的实例变量时却输出不同的值,这就是表明:db1、d变量所指向的Java对象包含两块内存,分别是存放count值为2和20的实例变量。




当通过变量调用方法时,方法的行为总是表现出它们实际类型的行为,但如果通过这些变量访问它们所指对象的实例变量,这些实例变量的值总是表现出声明这些变量所用类型的行为。这也就是Java继承在成员变量和方法时的区别所在。




如果子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统也就不可能把父类的方法移动到子类中。对于实例变量则不存在这种现象,即使子类中定义了与父类完全同名的实例变量,这个实例变量也依然不可能覆盖父类中定义的实例变量。



正是因为成员变量和继承方法之间存在的差别,所以对于一个引用类型的变量而言,当通过该变量访问它所引用的实例变量时,该实例变量的值取决于声明该变量时的类型;当通过该变量来调用它所引用的对象的方法时,该方法行为取决于它所实际引用的对象类型。


3、2 内存中子类的实例

class Base {
	int count = 2;
}

class Mid extends Base {
	int count = 20;
}

public class Sub extends Mid {
	int count = 200;
	
	public static void main(String[] args) {
		Sub s = new Sub();  //声明并创建Sub对象
		Mid sm = s;  //将Sub对象上转型赋值给Mid类型变量
		Base sb = s;  //将Sub对象上转型赋值给Base类型变量
		System.out.println(s.count);
		System.out.println(sm.count);
		System.out.println(sb.count);
	}
}


输出结果为:

200

20

2

程序输出的结果表示,s,sm和sb三个变量所引用的Java对象拥有三个count实例变量,也就是在内存中需要三块内存存储它们。其内存中的存储示意图如下所示。




但是实际上Sub类实际上只定义了一个count实例变量,另外两个count实例变量时Mid、Base所定义的。


class Fruit {
	String color = "未定义";
	
	public Fruit getThis() {  //该方法用于返回调用该方法的实例
		return this;
	}
	
	public void info() {
		System.out.println("Fruit方法");
	}
}

public class Apple extends Fruit {
	String color = "红色";
	
	@Override
	public void info() {
		System.out.println("Apple方法");
	}
	
	public void SuperInfo() {
		super.info();
	}
	
	public Fruit getSuper() {
		return super.getThis();
	}
	
	public static void main(String[] args) {
		Apple a = new Apple();
		Fruit f = a.getSuper();
		System.out.println("a和f所引用的对象是否相同:" + (a == f));
		System.out.println("访问a所引用对象的color实例对象:" + a.color);
		System.out.println("访问f所引用对象的color实例对象:" + f.color);
		a.info();
		f.info();
		a.SuperInfo();
	}
}


输出结果为:

a和f所引用的对象是否相同:true

访问a所引用对象的color实例对象:红色

访问f所引用对象的color实例对象:未定义

Apple方法

Apple方法

Fruit方法

Fruit类定义的getThis()方法和Apple类定义的getSuper()方法,试图达到的效果是:当一个Apple对象调用getSuper()方法时,该方法返回该Apple对象的所谓的“默认父类对象”。


提示

:Java程序允许某个通过return this;返回调用该方法的Java对象,但是不允许直接使用return super;,甚至不允许直接把super当成一个引用变量使用。super关键字本身并没有引用任何对象,甚至不能被当做一个真正的引用变量使用。

从输出的结果可以看出,通过Apple对象的getSuper()方法返回的依然是Apple对象本身,只是声明类型是Fruit。因此通过f访问color实例变量时,该实例变量的值由Fruit类决定,但是调用info()方法时,该方法的行为有f变量实际所引用的Java对象(Apple类)决定。

当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为其父类中定义的所有实例变量分配内存,即使子类定义了与父类同名的实例变量。如果在子类中定义了与父类中已有变量同名的变量,那么子类中定义的变量会隐藏父类中定义的变量。

注意:不是完全覆盖

,因此在创建子类对象时,依然会为父类中定义的、被隐藏的变量分配内存空间。

为了在子类方法中访问父类中定义的、被隐藏的实例变量,或者为了在子类中调用父类中定义的、被覆盖的方法,可以使用super.作为限定来修饰这些事例变量和事例方法。

3、3 子类和父类的类变量




由于类变量本质上属于类本身,因此通常不会涉及到实例变量那样复杂的状况,但是由于Java允许通过对象来访问类变量,因此也可以使用super.作为限定来访问父类中定义的类变量。





版权声明:本文为Never_Blue原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。