为啥引用子类对象不能引用父类对象?

Posted

技术标签:

【中文标题】为啥引用子类对象不能引用父类对象?【英文标题】:Why can't reference to child Class object refer to the parent Class object?为什么引用子类对象不能引用父类对象? 【发布时间】:2010-01-27 09:40:51 【问题描述】:

我正在向我的朋友解释 OOP。我无法回答这个问题。

我只是逃避说,因为 OOP 描绘的是真实世界。在现实世界中,父母可以容纳孩子,但孩子不能容纳父母。在 OOP 中也是如此。

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不能容纳父母呢。

【问题讨论】:

父母是通用的,子是专业的。铅笔是一块木头,但木头不是铅笔。铅笔是相当具体的附加属性。 【参考方案1】:

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

class Fox : Animal

因为每只狐狸都是动物。但另一种方式并不总是正确的(不是每个动物都是狐狸)。

您的 OOP 似乎也搞混了。这不是父子关系,因为不涉及组合/树。这是祖先/后代继承关系。

继承是“类型”而不是“包含”。因此,Fox 是 Animal 的一种,在你的情况下听起来不对 - “Child is a type of Parent”?类的命名是混乱的根源;)。

class Animal 
class Fox : Animal 
class Fish : Animal 

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

【讨论】:

很好的例子,但如果有几行代码,它会更有启发性:Animal a = Fox; Fish f = a; 没错,迄今为止最好的答案。 +1 哎呀!!我的意思是 ChildClass 和 ParentClass。但它具有完全不同的含义。它变成了现实世界,孩子和父母。我现在不会编辑和更改它,因为我得到了很多答案来纠正这个问题。我应该取更具体的类名。【参考方案2】:

如果它是有效的,当您阅读aChild.prop3 时,您会期待什么? aParent 上没有定义。

【讨论】:

【参考方案3】:

“子”类扩展“父”

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

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

在代码段中,就像正常的赋值操作一样,上面的内容是从右到左读取的。 代码段的第 3 行显示 - “aChild (a Child class object) is a Parent” (由于继承,子类对象天生就成为超类对象) 因此第 3 行是有效的。

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

【讨论】:

【参考方案4】:

如果我有课,说

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】:

我会说您的示例存在缺陷,因为 Child 是从 Parent 扩展而来的,它并没有特别好地遵循“is-a”关系。最好有一个ChildParent 都从一个基类继承的关系:Person

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

Person p = new Child();

... 有效,但以下无效:

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

这正是 Java 不允许这种赋值的原因:在这种情况下,额外的子字段会用什么初始化?

【讨论】:

【参考方案6】:

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

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

这就是为什么我们可以将十进制转换为 int 并将 int 转换为十进制的原因。 但是我们不能双向施放父子。因为父级不知道其子级的引用。

Int i = 5;
Decimal d = 5.5;

d = i;

or 

i = d;

两者都有效。但存储在堆上的引用类型并非如此。

【讨论】:

【参考方案7】:

我认为,您为现实生活中的父母和孩子选择了错误的模型;)在现实生活中,父母始终是孩子,孩子也可以成为父母。

如果你把它转过来,它会起作用:

class Child 
  Child[] parents;


class Parent : Child 
  Child[] children;

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

Child aChild = aParent;

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

Parent aParent = aChild;

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

【讨论】:

【参考方案8】:

我想你的意思是:

Child aChild = aParent;

您没有指定 aChild 的类型为 Child

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

【讨论】:

【参考方案9】:

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

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

【讨论】:

【参考方案10】:

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

如果将子类型的对象分配给父类型的对象,例如:

Parent aParent = aChild;

您将子对象的接口简化为基类的功能。这完全没问题,因为这意味着孩子的一些新功能没有在那种情况下使用。

如果您反过来尝试将基类强制转换为子类,您最终会得到一个可以满足其接口期望的对象。

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

Child extends Parent

 void doSomeSpecialChildStuff...

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

Parent aParent = new Child()

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

 aParent.doSomeSpecialChildStuff()

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

【讨论】:

【参考方案11】:

想想“继承”=“专业化”,虽然这乍一看似乎违反直觉。 “孩子”集是“父母”集的子集(您会看到您的示例有点误导)。自然地,可以“持有”“子”的变量不能持有“父”集的任意成员,因为它可能不在“子”子集中。另一方面,可以“容纳”“父”的变量可以容纳“子”集的每个成员。

似乎有两种查看继承的方式。正如 Kornel 所说,在编程层面上,“孩子”是“父母”能力的超集。但从概念上讲,“子”是“父”的特化,因此仅代表所有可能的“父”的一个子集。例如考虑“车辆”和“汽车”:汽车是一种特殊的车辆。所有 Cars 的集合仍然是所有车辆的子集。您甚至可以使用汽车比使用普通车辆“做更多”(例如更换轮胎、加油等),但它仍然是车辆。

【讨论】:

【参考方案12】:

您正在执行的任务称为向下转换和向上转换。当您将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. 

【讨论】:

【参考方案13】:

基本上,这一切都是以赋值的方式出现的,因为我们都熟悉编程语言中可用的数据类型。

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

    byte 可以很容易地分配给 short 而无需强制转换。(byte b = 127; short s = b;)

    short 可以很容易地分配给 int 而无需强制转换。(short s = 32,767; int i = i;) 等等……

但是当我们尝试相反的方式时,即 int to short 或 short to byte。我们得到编译时错误,然后为了修复这个编译时错误,我们进行如下类型转换:

int a = 10; 字节 b = a; //编译时错误;这是因为 int 的大小比 int 大,所以 byte 不能容纳 int。

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

同样,当涉及到分配给子类引用的父类对象时:

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

【讨论】:

【参考方案14】:

这是编译器试图阻止您创建运行时错误,例如调用不存在的对象的成员。你也许可以在 Perl 或 C 等类型系统不太严格的语言上做这样的事情,但在运行时你就得靠自己了。

如果您不介意一些限制,编译时错误比运行时错误要好。

【讨论】:

【参考方案15】:

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

【讨论】:

以上是关于为啥引用子类对象不能引用父类对象?的主要内容,如果未能解决你的问题,请参考以下文章

为什么父类引用可以指向子类对象 子类引用不能指向父类对象 泛型

JAVA中如何对父类对象强制转换子类对象的引用

在java 中子类对象为啥不能比父类对象有更严格的访问权限?

在JAVA中,子类继承父类,父类可以调用子类继承父类的方法,父类为啥不能调用子类自己定义的方法呢

java中让子类引用指向父类对象的时候怎么进行类型转换?

在java中,引用数据不就是一种对象么?为啥在调用函数中不能进行修改数值??