为什么不能引用子类对象引用父类对象?

我正在向我的朋友解释OOP。 我无法回答这个问题。 (我有多可耻?:()

我只是逃避说,因为OOP描绘了现实世界。 在现实世界中,父母可以容纳孩子,但孩子不能容纳父母。 OOP也是如此。我知道它很愚蠢。 :P

class Parent { int prop1; int prop2; } class Child : Parent // class Child extends Parent (in case of Java Lang.) { int prop3; int prop4; public static void Main() { Child aChild = new Child(); Parent aParent = new Parent(); aParent = aChild;// is perfectly valid. aChild = aParent;// is not valid. Why?? } } 

为什么这个陈述没有效?

  aChild = aParent;// is not valid. Why?? 

因为aChild的成员是aParent成员的超集。 那么为什么aChild不能容纳父母。

正是因为aChild是aParent能力的超集。 你可以写:

 class Fox : Animal 

因为每个狐狸都是动物。 但另一种方式并非总是如此(并非每一种动物都是狐狸)。

你似乎也把你的OOP混淆了。 这不是父子关系,因为没有涉及组合/树。 这是祖先/后裔的inheritance关系。

inheritance是“类型”而不是“包含”。 因此, Fox是一种动物 ,在你的情况下听起来不对 – “孩子是一种父母”? 类的命名是混乱的根源;)。

 class Animal {} class Fox : Animal {} class Fish : Animal {} Animal a = new Fox(); // ok! Animal b = new Fish(); // ok! Fox f = b; // obviously no! 

如果它有效,你在阅读aChild.prop3时会有什么期望? 它没有在aParent定义。

class“Child”扩展为“Parent”

“子类对象本质上是父类对象”

  Child aChild = new Child(); Parent aParent = new Parent(); aParent = aChild;// is perfectly valid. aChild = aParent;// is not valid. 

在像正常分配操作这样的代码段中,从右到左阅读上面的内容。 代码段的第3行读取 – “aChild(子类对象)是父”(由于inheritance子类对象本身成为超类对象)因此第3行是有效的。

而在第4行,它读取,“aParent(一个父类对象)是一个子”(inheritance并没有说超类对象将成为子类对象。它说相反)因此第4行无效。

我会说你的例子是有缺陷的,因为Child延伸到Parent ,它并没有真正遵循“is-a”关系。 最好有一个关系, ChildParent都从一个基类inheritance: Person

使用这种方法可以更容易地向您的朋友解释原因:

 Person p = new Child(); 

……有效,但以下情况不是:

 // We do *not know* that the person being referenced is a Child. Child c = person; 

正是这个原因导致Java中不允许这样的赋值:在这种情况下,其他子字段的初始化是什么?

我想你的意思是:

 Child aChild = aParent; 

您没有指定aChildChild类型。

Child类型的引用意味着您可以在其上调用Parent中可能不存在的成员。 因此,如果将Parent对象分配给Child引用,则可以调用Parent上不存在的成员。

从语义上讲,inheritance表示“是一种”关系。 例如,熊“是一种”哺乳动物,房子“是一种”有形资产,而快速排序“是一种”特定种类的排序算法。 因此,inheritance意味着泛化/特化层次结构,其中子类专门化其超类的更一般的结构或行为。 事实上,这是inheritance的试金石:如果B不是A的一种,那么B不应该inheritanceA.在你的情况下,它意味着,父母“是一个”孩子,但反之亦然。

PS我认为在这种情况下你违反了主要的inheritance原则。

我认为,你为现实生活中的父母和孩子选择了错误的模式;)在现实生活中,父母总是孩子,孩子可以成为父母。

如果你把它转过来,它的工作原理:

 class Child { Child[] parents; } class Parent : Child { Child[] children; } 

父母是一个孩子(他/她自己的父母),我们可以表达:

 Child aChild = aParent; 

因为每个父母都是孩子,但不是

 Parent aParent = aChild; 

因为不是所有的孩子都是父母。

如果您使用父类并对其进行扩展,则该类具有父类具有的所有function以及更多function。

如果将类型为child的对象分配给父类型的对象,如:

 Parent aParent = aChild; 

您将子对象的接口缩减为基类的function。 这是完全可以的,因为这意味着在这种情况下不会使用孩子的一些新function。

如果你反过来这样做并尝试将一个基类强制转换为一个孩子,你最终会得到一个可以满足其界面期望的对象。

例如,您定义了一个基类,如:

 Child extends Parent void doSomeSpecialChildStuff... 

现在,您创建一个Parent并将其分配给子对象。

 Parent aParent = new Child() 

您的编程语言现在认为aParent对象是Child。 问题是,现在它完全有效:

  aParent.doSomeSpecialChildStuff() 

现在,您正在调用一个未为该对象定义的方法,但该对象的接口表示已定义该方法。

认为“inheritance”=“专业化”,虽然这一开始似乎是反直觉的。 “孩子们”的集合是“父母”集的一个子集(你看到你的例子有点误导)。 自然地,可以“保持”“孩子”的变量不能保持“父”集的abitrary成员,因为它可能不是“子”子集。 另一方面,可以“保持”“父”的变量可以保存“子”集的每个成员。

似乎有两种查看inheritance的方法。 在编程层面,“儿童”是“父母”能力的超集,正如科内尔所说。 但从概念上讲,“Child”是“Parent”的特化,因此仅代表所有可能的“Parent”的子集。 想想例如“车辆”和“汽车”:汽车是一种特殊的车辆。 所有汽车的集合仍然是所有车辆的子集。 您甚至可以使用汽车“做更多”而不是使用普通车辆(例如更换轮胎,填充汽油等),但它仍然是车辆。

您正在执行的分配称为向下转换和向上转换。 向下转换是将object转换为String时发生的情况。 向上转换是将类型转换为更通用类型的相反方向。 上传永远不会失败。 向下转换仅在您向下转换为比实例化对象更具体的类型之前有效。

 class Named { public string FullName } class Person: Named { public bool IsAlive } class Parent: Person { public Collection Children } class Child : Person { public Parent parent; } public static void Main() { Named personAsNamed = new Person(); //upcast Person person = personAsNamed ; //downcast Child person = personAsNamed ; //failed downcast because object was instantiated //as a Person, and child is more specialized than(is a derived type of) Person //The Person has no implementation or data required to support the additional Child //operations. } 

如果我有课,请说

 class A{ getA(){ } } class B extend A{ getB(){ } } 

现在class B知道两个方法getA()getB() 。 但是class A只知道getA()方法。

所以,如果我们有class B obj = new class A(); 我们弄得一团糟,因为它对class B有效,可以引用方法getA()getB()但只有getA()是有效的。 这解释了这个问题。

这是我对不允许子类持有父类的引用的理解。

AaronLS的Heap-Stack答案具有完美的技术意义。

引用存储在堆栈上,而对象存储在堆上。 我们可以将子对象分配给父类型引用,因为子类型是父类型,子对象具有父类的引用。 虽然父母不是孩子的类型。 父对象没有引用子对象,因此子引用不能指向父对象。

这就是我们可以将十进制转换为int和int转换为十进制的原因。 但我们不能两种方式施展亲子。 因为父母对其孩子的参考资料一无所知。

 Int i = 5; Decimal d = 5.5; d = i; or i = d; 

两者都有效。 但是存储在堆上的引用类型不是这种情况。

引用存储在堆栈上,而对象存储在堆上。 我们可以将子对象分配给父类型引用,因为子类型是父类型,子对象具有父类的引用。 虽然父母不是孩子的类型。 父对象没有引用子对象,因此子引用不能指向父对象。