继承过程中 父类子类的 字段方法 内存分配 (非java语言)

本文解析了.NET框架中的继承机制,详细介绍了对象创建过程中字段与方法列表的构建顺序,并探讨了两个面向对象原则:关注对象原则与就近原则。
名人名言:思想好比火星:一颗火星会点燃另一颗火星。一个深思熟虑的教师和班主任,总是力求在集体中创造一种共同热爱科学和渴求知识的气氛,使智力兴趣成为一些线索,以其真挚的、复杂的关系——即思想的相互关系把一个个的学生连接在一起。——苏霍姆林斯基 

首先给出部分代码,由此来分析一下运行过程中对象、字段的创建过程,和编译过程中方法列表的创建过程


View Code 

public class Animal
{
public virtual void
Eat()
{
Console.WriteLine(
"Animal Eat"
);
}

public virtual void
Sleep()
{
Console.WriteLine(
"Animal Sleep"
);
}

public void
Play()
{
Console.WriteLine(
"Animal Play"
);
}

}

public class
Cat : Animal
{
public override void
Eat()
{
base.Eat();//调用父类方法

Console.WriteLine("Cat Eat");
}

public override void
Sleep()
{
Console.WriteLine(
"Cat Sleep"
);
base
.Sleep();
}

}



以上代码 如果在Main方法中 通过 Animal a = new Cat(); 来实现一个父类引用子类对象。


这句话首先是创建了一个Animal类型的a的引用,然后 new Cat();创建了一个Cat的对象,最后把这个a这个引用指向了
              new Cat();这个对象的地址。在
这个对象创建的过程中其实有很多步骤
             
              首次访问:(在此没有显示的写出类中的构造方法)
                   顺序:子类的静态字段==》子类静态构造==》子类非静态字段==》父类的静态字段==》父类的静态构造==》父类的非静态字段
                             ==》父类的构造函数==》子类的构造函数
                         
              非首次访问:顺序是一样的,只不过少了中间静态字段和构造的过程
              
             这个过程依次类推直到递归到Object结束(在次过程中也是依次给父类分配内存的过程),且字段的在内存中的存储顺序是由上到下排列,object类的字段  排在最前面,
              原因是如果父类和子类出现了同名字段,则在子类对象创建时,编译器会自动认为这是两个不同的字段而加以区别。 
              
              说了对象的创建,其次是方法列表的创建:
              方法列表的创建是在编译时创建的,而对象的创建是在运行时,对象的创建是为了给方法列表一个引用的指针,使其它们动态关联起来。
              方法列表的创建顺序跟字段的的顺序是一样的,也是先父类后子类。(override 和 new 的不同 new主要是会阻断继承树,和隐藏父类方法,创建子类和父类同名的方法
              父先子后的原因是:在编译时创建方法列表的过程是,先生成父类的方法列表,而后在生成子类的方法列表的时候,会把父类的方法复制一份
              出来,然后拿子类的方法去和父类的比较,如果发现同名的方法,则看子类的方法修饰符是override 还是 new,如果是override 则覆盖父类
              同名的方法(以上所说的父类方法皆是virtual方法,并且这里说的覆盖只是说覆盖方法的实现,而并没有覆盖父类的方法列表,通过base.父类方法名还是可以调用父类的方法),
              如果是new  则在内存中的不同位置创建一个同名的方法。 不同名的,则直接创建。


     完成之后,我们可以通过 a这个引用来来调用Cat中的方法。


1.思考:如果把上例中Animal的play方法移到Cat中,在Main方法中打算通过a.Paly();来调用子类的Paly方法会发生什么现象? 会编译不通过,为什么呢?


按理说子类就是用来扩展父类的,理论上也允许子类有自己的特性啊(方法、字段……)。但问题不是出在子类,而是出在了调用的位置,不能通过a.Play();来调用这个方法,可能大家又不解了,会想 a就是通过 new Cat();这个对象啊出来的,为什么不能调用自己的方法勒,一层层的,最终我们找到原因是在 Animal a 这个申明引用的位置。


在此要引入OO 的一个原则: 关注对象原则——调用子类还是父类的方法,取决于创建的对象是子类对象还是父类对象,而不是它的引用类型,而引用指针类型不同的区别决定了不同的对象在方法表中不同的访问权限。


由此结合:子类可以调用父类方法和字段,而父类不能调用子类方法和字段  这个概念。就可以知道原因了。


在说说OO中的另一个原则:就近原则——对于同名字段或者方法,编译器是按照其顺序查找来引用的,也就是首先访问离它创建最近的字段或者方法。先贴一段代码,然后通过代码来分析。


View Code 

class Program
{
static void Main(string
[] args)
{
Animal a
= new
Cat();
//
MemberInfo[] m = a.GetType().GetMembers();
//
foreach (var item in m)
//
{
//
Console.WriteLine(item.Name);
//
}
//
a.Eat();
//
a.Play();
//a.Sleep();

Console.WriteLine(a.AnimalName);
}
}

public class
Animal
{
public string AnimalName = "Animal"
;
}

public class
Cat : Animal
{
public string AnimalName = "Cat"
;
}



在代码的Main方法中  Animal a = new Cat();  这个A是Animal 类型的,结合文章开始将的 在编译和运行过程中 子类、父类 的字段和方法以及实例化时候在内存中分配的先后位置可以得出:Animal 类中的 AnimalName 在内存中的位置一定位于 Cat中AnimalName在内存中的位置的前面,根据就近原则打印出的应该是Animal。


以上文章大致概括了在继承过程中的 在编译和运行过程中 子类、父类 的字段和方法以及实例化时候在内存中分配 和 执行的先后,以及两个原则,如有错误或者不足的地方请拍砖。后续后更深入的学习oo中的其它内容。

<think>首先,用户的问题是关于Java继承中,子类如何设置父类字段的默认值。用户说:“我想在Java中,子类继承父类字段后如何设置默认值 请问java 继承 父类字段 子类设置默认值 初始化”。所以,核心是子类如何为继承父类字段设置默认值。 现在,我需要回顾系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式使用$$...$$并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 用户可见层指令: - 回答结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 - 参考站内引用:有5个引用[^1]到[^5],这些是关于Java继承初始化顺序的。 回顾引用内容: - [^1]:讨论Java继承的初始化顺序,包括静态初始化、实例初始化、构造方法调用顺序。 - [^2]:警告子类重写父类方法时,子类变量在父类构造方法中可能为null。 - [^3]:详细描述初始化顺序:静态初始化(父类子类),然后实例初始化(父类变量赋值,父类构造器,子类变量赋值,子类构造器)。 - [^4]:子类对象通过super调用父类构造器设置父类私有变量。 - [^5]:不能在父类中声明未初始化的final成员,因为子类可能不初始化它,必须在父类构造器中初始化。 用户的问题焦点是:子类如何设置继承父类字段的默认值。在Java中,字段(成员变量)是继承的,但子类不能直接覆盖父类字段字段是隐藏的,而不是覆盖。子类可以声明自己的同名字段,但这会导致隐藏。 关键点: - 父类字段的初始化通常在父类构造器中设置。 - 子类可以通过super()调用父类构造器来设置父类字段的值。 - 子类也可以在自己的构造器中设置父类字段,如果父类字段是protected或public,但这不是好习惯;最好通过super调用。 - 如果子类想改变默认值,它应该在调用super时传递参数。 步骤来回答: 1. **解释继承字段访问**:在Java中,子类继承父类字段,但字段是静态绑定的;子类不能覆盖字段,只能隐藏。 2. **设置默认值的位置**: - 父类字段的默认值应该在父类中初始化(在声明时或父类构造器中)。 - 子类可以通过super()调用父类构造器来设置这些值。 - 子类不能直接为父类字段设置默认值;它必须通过父类构造器。 3. **初始化顺序**:引用[^3]详细描述了顺序:静态初始化 -> 父类实例变量赋值 -> 父类构造器 -> 子类实例变量赋值 -> 子类构造器。 4. **例子**:提供一个代码示例说明如何做。 5. **警告**:引用[^2]提到,如果子类重写父类方法,在父类构造器中使用子类变量可能为null。 结构回答: - **引言**:简述问题。 - **步骤**:逐步解释如何设置默认值。 - **代码示例**:用Java代码展示。 - **注意事项**:包括初始化顺序和潜在问题。 - **相关问题**:生成相关问题。 使用中文回答。 生成相关问题:基于主题,如初始化顺序、优化、陷阱等。 引用:在回答中自然地添加引用标识,比如在相关段落末尾。 最终回答结构: 1. 解释概念。 2. 提供方法。 3. 代码示例。 4. 注意事项。 5. 相关问题。</think>### Java子类继承父类字段设置默认值 在Java继承中,子类不能直接覆盖父类字段(成员变量),因为字段是静态绑定的。子类可以通过以下方式为继承父类字段设置默认值: 1. **在父类构造器中初始化字段**:父类应在自己的构造器中设置字段的默认值。 2. **子类通过`super()`调用父类构造器**:子类在构造器中使用`super(参数)`传递值,间接设置父类字段的默认值。 3. **避免在子类中声明同名字段**:如果子类声明了与父类同名的字段,它会隐藏父类字段(而覆盖),可能导致混淆[^2]。 关键点在于Java的初始化顺序: - 静态初始化(父类子类)在类加载时执行一次。 - 实例初始化:父类字段赋值→父类构造器→子类字段赋值→子类构造器[^3]。 子类必须在自己的构造器中调用`super()`(隐式或显式),确保父类先完成初始化[^1]。如果父类字段未初始化,编译器会报错(尤其对于`final`字段)[^5]。 #### 代码示例 以下示例展示子类如何为父类字段设置默认值: ```java // 父类 class Father { protected int age; // 父类字段 protected String name; // 父类构造器设置默认值 public Father(int age, String name) { this.age = age; this.name = name; System.out.println("父类构造器执行: age=" + age + ", name=" + name); } } // 子类 class Son extends Father { private int weight; // 子类自有字段 // 子类构造器: 通过super()设置父类字段默认值 public Son() { super(25, "默认名字"); // 调用父类构造器,设置父类字段默认值 this.weight = 70; // 设置子类字段默认值 System.out.println("子类构造器执行: weight=" + weight); } public static void main(String[] args) { Son son = new Son(); // 创建子类对象 System.out.println("最终值: age=" + son.age + ", name=" + son.name); } } ``` **输出**: ``` 父类构造器执行: age=25, name=默认名字 子类构造器执行: weight=70 最终值: age=25, name=默认名字 ``` 在这个例子中: - 子类`Son`通过`super(25, "默认名字")`调用父类构造器,为父类字段`age`和`name`设置默认值。 - 如果不调用`super()`,编译器会添加默认的`super()`(无参构造器),但父类必须有匹配的构造器[^3]。 #### 注意事项 1. **初始化顺序陷阱**:在父类构造器中,如果调用被子类重写的方法子类字段可能为`null`(因为子类实例变量尚未初始化)[^2]。例如: ```java class Father { public Father() { System.out.println("父类调用: " + getName()); // 如果子类重写getName(),可能返回null } public String getName() { return "父类"; } } class Son extends Father { private String name = "子类"; @Override public String getName() { return name; } // 在父类构造器中,name为null } ``` 输出可能显示`父类调用: null`,因为父类构造器执行时,子类字段`name`还未赋值[^2]。 2. **final字段**:父类的`final`字段必须在父类构造器中初始化,子类不能修改它[^5]。 3. **访问权限**:如果父类字段是`private`,子类无法直接访问,必须通过`super()`或父类提供的setter方法设置[^4]。 通过合理使用构造器和`super()`,可以安全地为继承字段设置默认值,避免空指针异常和初始化错误[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值