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

时间:2010-01-27 09:40:51

标签: c# java .net c++

我正在向我的朋友解释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不能容纳父母。

14 个答案:

答案 0 :(得分:33)

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

class Fox : Animal

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

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

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

class Animal {}
class Fox : Animal {}
class Fish : Animal {}

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

答案 1 :(得分:6)

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

答案 2 :(得分:4)

class“Child”扩展“Parent”

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

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

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

而在第4行,它读到, “aParent(父类对象)是一个孩子” (继承并没有说超类对象会成为子类对象。它说的相反) 因此第4行无效。

答案 3 :(得分:2)

我想说你的例子存在缺陷,因为ChildParent扩展而来,并没有真正遵循“is-a”关系。最好是建立一种ChildParent从单个基类继承的关系:Person

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

Person p = new Child();

...有效,但以下不是:

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

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

答案 4 :(得分:1)

如果我有课,请说

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()有效。这解释了这个问题。

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

答案 5 :(得分:0)

我认为你的意思是:

Child aChild = aParent;

您未指定aChild属于Child类型。

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

答案 6 :(得分:0)

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

P.S。我认为在这种情况下你违反了主要的继承原则。

答案 7 :(得分:0)

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

如果你翻过来,它就可以了:

class Child {
  Child[] parents;
}

class Parent : Child {
  Child[] children;
}

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

Child aChild = aParent;

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

Parent aParent = aChild;

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

答案 8 :(得分:0)

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

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

Parent aParent = aChild;

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

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

例如,您可以定义一个基类,如:

Child extends Parent

 void doSomeSpecialChildStuff...

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

Parent aParent = new Child()

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

 aParent.doSomeSpecialChildStuff()

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

答案 9 :(得分:0)

认为“继承”=“专业化”,虽然这看起来反直觉。 “Childs”集合是“Parents”集的一部分(您看到您的示例有点误导)。很自然地,一个可以“保持”“孩子”的变量不能保持“父”集的一个abitrary成员,因为它可能不是“子”子集。另一方面,一个可以“保持”“父”的变量可以保存“子”集的每个成员。

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

答案 10 :(得分:0)

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

class Named
{
    public string FullName
}

class Person: Named
{
    public bool IsAlive
}

class Parent: Person
{   
   public Collection<Child> 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. 
}

答案 11 :(得分:0)

AaronLS 的堆栈答案具有完美的技术意义。

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

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

Int i = 5;
Decimal d = 5.5;

d = i;

or 

i = d;

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

答案 12 :(得分:0)

基本上,这都是分配的方式,因为我们都熟悉编程语言中可用的数据类型。

让我们以数字数据类型及其赋值为例。

  1. byte可以很容易地分配给short而不进行强制转换。(byte b = 127; short s = b;)

  2. short可以很容易地分配给int而不进行强制转换。(short s = 32,767; int i = i;) 等等...

,但是当我们尝试反向操作时,即int到short或short到byte。我们会得到编译时错误,然后要解决此编译时错误,请按如下所示进行类型转换:

int a = 10; 字节b = a; //编译时间错误;这是因为int的大小大于int的大小,所以byte无法容纳具有int的对象。

然后我们进行类型转换(即,将要分配的值转换为请求的数据类型,然后对其进行分配)。 即 字节b =(字节)a;

类似地,当父类对象分配给子类引用时:

子类不能容纳父类对象,如果它也可以在类型转换的帮助下摆脱编译时异常,但会获得运行时异常,这是因为在运行时,子变量将保存父对象,子对象可能会请求一些父项没有的功能。

答案 13 :(得分:-1)

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