父类和子类间引用要注意的问题

父类与子类引用详解
本文探讨了在Java中父类与子类之间的引用问题,特别是在数据隐藏和方法覆盖情况下的行为差异。父类引用只能访问子类继承的成员,而子类引用能访问所有成员。当子类覆盖父类方法时,父类引用调用的是子类的方法实现。

       父类和子类间引用要注意的问题(索创java上第六章)

       在发生数据隐藏的情况下(数据隐藏指子类中拥有与父类某属性相同的属性,父类的属性必须通过super才能访问到),父类和子类的声明引用的是同一个子类对象.但是父类引用访问的是隐藏的成员.父类引用不能访问子类中非继承的成员.否则会抛异常.但是子类引用可以访问子类对象从父类继承的成员.

         但是如果子类覆盖了父类的同名方法,则父类引用调用该方法时,访问的是子类方法体. 

<think> 我们参考了用户提供的四个引用,这些引用主要涉及C#中父类子类的关系,包括构造函数调用顺序、成员访问、类型转换以及集合操作。用户本次的问题是“C#中父类子类的比较”,因此我们需要从多个维度比较父类子类。 根据引用[1]:构造函数调用顺序是从最顶层的基类开始,逐级向下到最终的派生类。在示例中,BaseClass的构造函数先执行,然后是SubClass1,最后是SubClass1_1。 根据引用[2]:当使用父类类型引用子类对象时,只能访问父类中定义的成员(属性方法),不能访问子类中新增的成员。例如,基类引用指向派生类对象时,无法调用派生类中新增的Methord2。 根据引用[3]:子类可以隐式转换为父类(向上转型),而父类转换为子类(向下转型)则需要显式转换,且可能抛出异常(如果父类变量实际引用的不是该子类对象)。 引用[4]则涉及从数据库中获取父类子类的集合,但具体实现未给出,我们可以忽略其细节。 因此,我们可以从以下几个方面比较父类子类: 1. 构造顺序 2. 成员访问 3. 类型转换 4. 方法重写(虽然引用中未明确,但这是重要比较点) 另外,我们还需要补充一些常见的比较点,如继承关系、多态性等。 下面我们逐一说明: ### 1. 构造顺序 在创建子类对象时,构造函数的调用顺序是:先调用基类的构造函数,再调用派生类的构造函数。如果类有多层继承,则从最顶层的基类开始,一直到最终的派生类。 ### 2. 成员访问 - 子类可以访问父类中非私有的成员(public, protected)。 - 当使用父类类型引用子类对象时,只能访问父类中定义的成员,不能访问子类中新增的成员(如引用[2]所示)。 ### 3. 类型转换 - 向上转换(子类父类):隐式转换,总是安全的。 - 向下转换(父类子类):需要显式转换(强制类型转换),如果父类变量实际指向的不是目标子类对象,则会在运行时抛出`InvalidCastException`。 ### 4. 方法重写(多态) 如果父类方法被标记为`virtual`,子类可以使用`override`关键字重写该方法。当通过父类引用调用该方法时,实际执行的是子类重写的方法(如果子类重写了)。这是多态性的体现。 ### 5. 继承关系 - 子类继承父类的成员(除了私有成员构造函数),同时可以添加自己的成员。 - 子类可以隐藏父类的成员(使用`new`关键字),但这会破坏多态。 ### 6. 访问修饰符的影响 - 父类中标记为`private`的成员,子类不可访问。 - 父类中标记为`protected`的成员,子类可以访问,但外部不可访问。 ### 7. 抽象类接口 - 抽象类(abstract class)不能实例化,只能被继承。子类必须实现抽象类中的所有抽象成员。 - 接口(interface)定义契约,子类(实现类)必须实现接口的所有成员。 ### 8. 密封类 如果父类被标记为`sealed`,则不能被继承。 ### 示例代码: ```csharp using System; public class BaseClass { public void BaseMethod() { Console.WriteLine("Base method"); } public virtual void VirtualMethod() { Console.WriteLine("Base virtual method"); } } public class SubClass : BaseClass { public void SubMethod() { Console.WriteLine("Sub method"); } public override void VirtualMethod() { Console.WriteLine("Sub override virtual method"); } } class Program { static void Main() { // 向上转换(隐式) BaseClass baseRef = new SubClass(); baseRef.BaseMethod(); // 输出: Base method baseRef.VirtualMethod(); // 输出: Sub override virtual method (多态) // baseRef.SubMethod(); // 编译错误,父类引用不能访问子类新增方法 // 向下转换(显式) if (baseRef is SubClass subRef) { subRef.SubMethod(); // 输出: Sub method } // 错误示例:如果实际对象不是SubClass,则会导致运行时错误 BaseClass anotherBase = new BaseClass(); // SubClass subRef2 = (SubClass)anotherBase; // 运行时InvalidCastException } } ``` ### 总结比较表: | 特性 | 父类 | 子类 | |--------------|------------------------------|-------------------------------| | 构造函数调用 | 在子类构造时首先被调用 | 在父类构造后调用 | | 成员访问 | 只能访问自己的成员 | 可访问自身及父类非私有成员 | | 转换方向 | 可以接受子类对象(向上转换) | 可以从父类转换(向下转换) | | 多态 | 可定义虚方法供子类重写 | 可重写父类虚方法 | | 继承 | 可以被继承 | 继承父类 | | 实例化 | 可直接实例化(非抽象类) | 可直接实例化 | 通过以上比较,我们可以看出父类子类在C#中的主要区别联系。 §§相关问题§§ 1. C#中如何实现父类方法在子类中的重写? 2. 向下转型时如何避免InvalidCastException异常? 3. 什么是C#中的密封类,它如何影响继承? 4. 抽象类接口在继承上有什么不同? </think> 在C#中,父类(基类)子类(派生类)的比较主要涉及继承关系、成员访问、类型转换多态性等方面。以下是关键点的系统分析: --- ### 一、核心比较维度 1. **构造函数调用顺序** **规则**:子类实例化时,先调用父类构造函数,再调用子类构造函数(若存在多层继承,则从最顶层基类开始逐级调用)。 **示例**(引用[1]): ```csharp var obj = new SubClass1_1(); // 输出顺序:BaseClass → SubClass1 → SubClass1_1 ``` 2. **成员访问权限** - **父类引用指向子类对象**: 只能访问父类定义的成员(属性方法),无法访问子类新增成员(引用[2]): ```csharp BaseClass obj = new ExtendClass(); obj.Methord1(); // 有效(父类方法) obj.Methord2(); // 编译错误!(子类新增方法) ``` - **子类访问父类成员**: 可直接访问父类非私有成员(`public`/`protected`)。 3. **类型转换规则** - **向上转型(子类父类)**:隐式转换,始终安全(引用[3]): ```csharp B child = new B(); A parent = child; // 隐式转换成功 ``` - **向下转型(父类子类)**:需显式转换,可能抛出`InvalidCastException`: ```csharp if (parent is B validChild) validChild.b1 = "value"; // 显式转换后访问子类成员 ``` 4. **多态性(方法重写)** - 父类方法标记 `virtual`,子类用 `override` 重写: ```csharp class BaseClass { public virtual void Print() => Console.WriteLine("Base"); } class SubClass : BaseClass { public override void Print() => Console.WriteLine("Sub"); // 重写实现 } ``` - **调用行为**: ```csharp BaseClass obj = new SubClass(); obj.Print(); // 输出"Sub"(实际调用子类方法) ``` --- ### 二、关键差异总结表 | **特性** | **父类** | **子类** | |------------------------|-----------------------------------|-----------------------------------| | 构造函数调用顺序 | 优先执行 | 在父类构造函数后执行 | | 成员访问范围 | 仅限自身定义成员 | 可访问自身+父类非私有成员 | | 类型转换方向 | 可接受子类对象(向上转型) | 需显式转换才能转为父类(向下转型) | | 方法调用行为(多态) | 若未重写,执行自身逻辑 | 可重写父类虚方法,改变行为 | | 实例化限制 | 可直接实例化(非抽象类) | 可直接实例化 | --- ### 三、应用场景与注意事项 1. **设计原则**: - 遵循 **Liskov替换原则**(父类引用应能透明使用子类对象)。 - 避免过度嵌套继承(推荐组合优于继承)。 2. **集合操作**(引用[4]): - 若需同时存储父类子类对象,可使用父类类型集合: ```csharp List<BaseClass> list = new List<BaseClass>(); list.Add(new BaseClass()); list.Add(new SubClass()); // 合法 ``` 3. **类型检查**: - 使用 `is` 或 `as` 避免转换异常: ```csharp if (parentObj is SubClass child) child.SubMethod(); ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值