我正在向我的朋友解释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不能容纳父母。
答案 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)
我想说你的例子存在缺陷,因为Child
从Parent
扩展而来,并没有真正遵循“is-a”关系。最好是建立一种Child
和Parent
从单个基类继承的关系: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)
基本上,这都是分配的方式,因为我们都熟悉编程语言中可用的数据类型。
让我们以数字数据类型及其赋值为例。
byte可以很容易地分配给short而不进行强制转换。(byte b = 127; short s = b;)
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)
当对象存储在堆上时,引用存储在堆栈上。我们可以将子对象分配给父类型引用,因为子类是父类型,子对象具有父类的引用。 虽然父母不是孩子的类型。父对象没有引用子对象,因此子引用不能指向父对象。