简体   繁体   中英

Why can't reference to child Class object refer to the parent Class object?

I was explaining OOP to my friend. I was unable to answer this question.

I just escaped by saying, since OOP depicts the real world. In real world, parents can accommodate children but children cannot accommodate parents. same is the case in 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??
 
  }
}

Why isn't this statement valid?

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

since aChild 's members are superset of aParent 's members. Then why can't aChild accommodate a parent.

Exactly because aChild is a superset of aParent's abilities. You can write:

class Fox : Animal

Because each Fox is an Animal. But the other way is not always true (not every Animal is a Fox).

Also it seems that you have your OOP mixed up. This is not a Parent-Child relationship, because there's no composition/trees involved. This is a Ancestor/Descendant inheritance relation.

Inheritance is "type of" not "contains". Hence it's Fox is a type of Animal , in your case it doesn't sound right -- "Child is a type of Parent" ? The naming of classes was the source of confusion ;).

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

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

If it was valid, what would you expect when you read aChild.prop3 ? It is not defined on aParent .

class "Child" extends "Parent"

"child class object is inherently a parent class object"

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

in a code segment like a normal assignment operation, the above is read from right to left. line 3 of the code segment reads - "aChild (a Child class object) is a Parent" (due to inheritence child class objects become superclass objects inherently) thus line no.3 is valid.

whereas in line no.4 it reads, "aParent (a Parent class object) is a child" (inheritence doesn't say that superclass objects will become child class objects. it says the opposite) thus line no.4 is invalid.

If I have a class, say

class A{
    getA(){

    }
}

class B extend A{
    getB(){

    }
}

Now class B knows two methods getA() and getB() . but class A knows only getA() method.

So, if we have class B obj = new class A(); we have made a mess , as it is valid for class B to reference methods getA() and getB() but only getA() is valid. That explains the issue.

This is my understanding of not allowing child class hold reference of parent class.

I would say your example is flawed in that Child extends from Parent , which doesn't really follow the "is-a" relationship particularly well. Far better to have a relationship whereby both Child and Parent inherit from a single base class: Person .

Using that approach it would be easier to explain to your friend why:

Person p = new Child();

... is valid, but the following is not:

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

It is precisely this reason why this assignment is disallowed in Java: What would the additional child fields be initialised with in this case?

The Heap-Stack answer by AaronLS makes perfect technical sense.

References are store on stack while objects are store on heap. We can assign child object to parent type reference because child is type of parent and child object has reference for parent class. While parent is not of type child. Parent object doesn't have reference to child so child reference can't point to parent object.

This is the reason why we can cast decimal to int and int to decimal. But we can not cast parent-child both ways. Because parent has no clue about its children's references.

Int i = 5;
Decimal d = 5.5;

d = i;

or 

i = d;

Both are valid. But same is not the case with reference types which are stored on heap.

I think you mean:

Child aChild = aParent;

You did not specify that aChild is of type Child .

The reference to a Child type will mean that you can call members on it that may no exist in the Parent . So, if you assign a Parent object to a Child reference, you will be able to call members that do not exist on the Parent .

Semantically, inheritance denotes an “is a” relationship. For example, a bear “is a” kind of mammal, a house “is a” kind of tangible asset, and a quick sort “is a” particular kind of sorting algorithm. Inheritance thus implies a generalization/ specialization hierarchy, wherein a subclass specializes the more general structure or behavior of its superclasses. Indeed, this is the litmus test for inheritance: If B is not a kind of A, then B should not inherit from A. In your case it means, that Parent "is a" child, but not vice versa.

PS I think in this case you violates main inheritance principles.

I think, you chose a wrong model for real-life Parents and Children ;) In real life, a Parent is always a Child and a Child can be a Parent.

If you turn it around, it works:

class Child {
  Child[] parents;
}

class Parent : Child {
  Child[] children;
}

A parent is-a child (of his/her own parents) and we can express:

Child aChild = aParent;

because every parent is a child as well, but not

Parent aParent = aChild;

because not all children are parents.

If you take a parent class and extend it the class has all the features the parent class has plus some more.

If you assign an object of the type child to an object of the type parent like:

Parent aParent = aChild;

you reduce the interface of the child object to the features of the base class. This is perfectly ok because it means that some new features of the child aren't used in that context.

If you do it the other way round and try to cast a base class to a child you would end up with an object that could live up the expectations on its interface.

For example you define a base class like:

Child extends Parent

 void doSomeSpecialChildStuff...

Now you create a Parent and assign it to a child object.

Parent aParent = new Child()

Your programming language now thinks the aParent object is a Child. The problem is that now it would be perfectly valid to this:

 aParent.doSomeSpecialChildStuff()

Now you are calling a method that isn't defined for the object but the interface of the object says it is defined.

Think "inheritance" = "specialisation", although this seems counter-intuitive at first. The set of "Childs" is a subset of the set of "Parents" (you see that your example is a bit misleading). It naturally follows that a variable that can 'hold' a "Child" cannot a hold an abitrary member of the "Parent" set because it may not be in it the "Child" subset. On the other way, a variable that can 'hold' a "Parent" can hold every member of the "Child" set.

There seems to be 2 ways of viewing inheritance. On the programming level, "Child" is a superset of "Parent" abilities, as Kornel said. But conceptually, "Child" is a specialisation of "Parent" thus representing only a subset of all possible "Parent". Think for example "Vehicle" and "Car": a Car is a special Vehicle. The set of all Cars is still a subset of all vehicles. You may even "do more" with a Car than with a general Vehicle (eg changing tires, fill in gasoline etc.) but it's still a Vehicle.

The assignments you are performing are called downcasting and upcasting. Downcasting is what happens when you cast an object to a String . Upcasting is the opposite where you are casting a type to a more general type. Upcasting never fails. Downcasting is only valid up until the point where you are downcasting to a type more specific than what the object was instantiated as.

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. 
}

Basically, it all comes as way of assignment, as we all are familiar with the data types available in programming languages.

let's take an example of numeric data types and it's assignment.

  1. byte can be easily assigned to short without casting.(byte b = 127; short s = b;)

  2. short can be easily assigned to int without casting.(short s = 32,767; int i = i;) and so on....

but when we try reverse way, ie, int to short or short to byte. we get compile time error, then to fix this compile time error, we do type casting as follows below:

int a = 10; byte b = a; //compile time error ; this is because size of int is greater then int, so byte cannot accommodate to have int.

then we do type casting(ie, value to be assigned gets converted into requested data type and then it gets assigned). ie, byte b = (byte)a;

Similarly, when it comes to parent class object assigning to child class reference :

child class cannot accommodate to have parent class object, in case if it does also with the help of type casting, will be able to get rid of compile time exception, but will get runtime exception, which is because at run time, child variable will be holding the parent object and there could be possibility where child may request for some features which parent doesn't have.

It's the compiler trying to stop you creating runtime bugs like calling a member of an object that doesn't exist. You maybe able to do such a thing on languages with less strict type systems like Perl or C but then you're on your own at runtime.

Compile time errors are better than runtime errors if you don't mind a few restrictions.

References are store on stack while objects are store on heap. We can assign child object to parent type reference because child is type of parent and child object has reference for parent class. While parent is not of type child. Parent object doesn't have reference to child so child reference can't point to parent object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM