1

I have seen other answers to similar questions but all of them rely on the fact that the language is defined to be like this. Following is what I am seeking an explanation to:

In an inheritance hierarchy, the parent types can hold child objects implicitly (why?), however for the child references to hold a parent object, explicit downcast is necessary (why?).

Please cite some example that explains why not doing this will fail, I mean using Animal, Dog type etc. If this question is already answered and I have missed it, citing that also will be helpful.

For example:

class Animal{
    public void eat(){};
}

class Dog extends Animal{
    public void bark(){};
}

class App{
    Animal an = new Dog(); //1. why can animal store a dog but not a dog store an animal
    Dog dog = (Dog) new Animal(); //2. Dog has the behavior of an Animal so why explicit downcast
}

I just want to know how this lines make sense, other than just knowing they are language semantics. Better if your answer looks like that as a granny explaining this to her grandchild.

Edit: I just was wondering that Dog inherits Animal and has all the behavior. Hence number 2 above should have been allowed without explicit downcasting.

Or, that (I think I got it now) when I ask a Dog to store an Animal there are possibilities that I actually get a Cow or a Horse, because Animal being parent can hold any of its subtypes. If that's the case then why has Java allowed Animal to hold subtypes since there might be behavior typical to subtypes like a Dog will bark(), for that again compiler has to check and report. I know the rules just trying to reason out in the simplest of sense.

xploreraj
  • 3,792
  • 12
  • 33
  • 51
  • 3
    Why don't you show an example of exactly what you mean? In particular, the "however for the child references to hold a parent object" bit is very confusing - a variable of type `Dog` can't hold a reference to an object whose execution-time type is just `Animal`. But in general, the downcast is explicit to tell the compiler "I know more than you here - but check it at execution time". – Jon Skeet Apr 03 '16 at 08:26
  • The code is just to understand. – xploreraj Apr 03 '16 at 08:36
  • The code is buggy and will crash precisely because the downcast is invalid. – chrylis -cautiouslyoptimistic- Apr 03 '16 at 08:49
  • To further understand whats possible have a look at [this answer](http://stackoverflow.com/a/380828/881272) on a related question. – Angelo Fuchs Apr 03 '16 at 09:00
  • 1
    Also, you will understand the use of class hierarchies better as soon as you adopt the concept of abstract classes. (I edited that into my answer as well) Your 'Animal' should be one. – Angelo Fuchs Apr 03 '16 at 09:06
  • I am somehow confused but I know the answer to it just missing it now :-) See the downcast code comment in above code and plz explain that. – xploreraj Apr 05 '16 at 18:45
  • @xploreraj I added an explanation of your particular case to my answer. – Angelo Fuchs Apr 06 '16 at 11:17

5 Answers5

5

The gain of strict type binding in Java is that you get compile time errors, instead of runtime errors, when possible.

Example:

class Animal {
    void eat() {}
}
class Dog extends Animal  {
    void bark() {}
}
class Pigeon extends Animal {
    void pick() {}
}
class MyFunction {
    void run() {
       Animal first = new Pigeon();
       // the following line will compile, but not run
       ((Dog)first).bark();
    }
}

If you have such code in a simple example like this, you will spot the problem at once. But consider having such a problem in a project, in a seldom called function at the depth of thousands of lines of code, in hundreds of classes. One day in production, the code fails and your customer is upset. And its up to you to find out why it failed, what happened and how to fix it. Its a horrible task.

So, with this somewhat complicated notation Java nudges you into thinking again about your code, the next example would be how its done better:

class MyFunction {
    void run() {
       Pigeon first = new Pigeon();
       // the following line will NOT compile
       first.bark();
       // and neither will this. Because a Pigeon is not a Dog.
       ((Dog)first).bark();
    }
}

Now you see your problem at once. This code, will not run. And you can avoid the problems ahead by using it correctly.

If you make your Animal class abstract (which you should), you will see that you can only instantiate specific animals, but not general ones. After that you will start using the specific ones when required and be relieved that you can reuse some code when using the general class.

Background

Conceptually, runtime errors are harder to find and debug, then compile time errors. Like, seriously hard. (Search for NullPointerException here on Stack Overflow and you will see hundreds of people who struggle to fix runtime exceptions)

In a Hierarchy of things (in general, not programming related) you can have something general "Thats an animal". You can also have something specific "Thats a dog". When someone talks about the general thing, you can't expect to know the specific thing. An animal can't bark up a tree, because birds could not, neither do cats.

So, in Java in particular the original programmers found it wise to decide that you need to know an object specific enough to call the functions of that object. This ensures that if you didn't pay attention, the compiler will warn you, instead of your runtime.

Your particular case

You assume that:

Dog dog = (Dog) new Animal();

should work, because a Dog is an Animal. But it won't, because not all Animals are Dogs.

BUT:

Animal an = new Dog();

works, because all Dogs are Animals. And in this specific case

Animal an = new Dog();
Dog dog = (Dog)an;

will work too, because the specific runtime state of that animal happens to be Dog. Now if you change that to

Animal an = new Pigeon();
Dog dog = (Dog)an;

it still WILL compile, but it WILL NOT run, because the second line Dog dog = (Dog)an; fails. You can't cast a Pigeon to a Dog.

So, in this case you WILL get a ClassCastException. Same if you try to cast new Animal() to Dog. An Animal is NOT a Dog. Now, that will happen at runtime and that is bad. In the Java way of thinking, compile time errors are better then runtime errors.

Angelo Fuchs
  • 9,825
  • 1
  • 35
  • 72
3

Yes. One simple real time example to understand the theory is

Every Truck driver is Driver but not you cannot say every Driver is a Truck Driver.

Where

Driver -Parent
Truck Driver - Child.


Animal an = new Dog(); //1. why can animal store a dog but not a dog store an animal

You ordered for an Animal and the shop keeper gave a Dog for you and you are happy since Dog is an Animal.

Dog do = (Dog) new Animal(); //2. Dog has the behavior of an Animal so why explicit downcast

You are asking for a Dog and the shop keeper gave an Animal to you. So it is so obvious that you check that the Animal is a Dog or not. Isn't you ?? Or you just assume that you got a Dog ?

Think.

Suresh Atta
  • 120,458
  • 37
  • 198
  • 307
2

In java references are used. Suppose we have below classes

class A{
    Integer a1;
    public A(){
        // a1 is initialized 
    }
    public void baseFeature(){
        super.baseFeature();
        // some extra code
    }
}

and

class B extends A{
    Integer b1;
    Integer b2;
    public B(){
        super();
       // b1 , b2 are initialized 
    }
    @Override
    public void baseFeature(){
        super.baseFeature();
        // some extra code
    }
    public void extraFeature(){
       // some new features not in base class
    }
}

Below all 3 statements are valid

A a = new A();

B b = new B();

A b1 = new B();

In java references are used to refer to the objects kept in Heap.

  1. A reference of type A should not be considered as if it can not hold the objects which have more memory required than an object of class A. Its the references and not the objects holders.
  2. In case of sub type object creation, Constructor call follows : Parent-constructor call followed by constructor call of the actual class whose object is being created.
  3. Sub class object can be said to be having features at-least as much as the Parent type has.
  4. A b = new B() has no confusion, as object of B has all the features of its parent. Sub class object has all the features as defined in its parent, so any parent class method can be called on the object of sub class
  5. Sub classes can have much more features, which parent class does not have, so calling a method of sub class on object of parent will lead to problems.

Suppose In Java B a = new A() is valid then if a.extraFeature() is invoked then it is obvious of an error.

This is prevented by compile time error. If in case downcasting is needed, then the programmer must do it with extra care. This is the intention of compile time error. Below cases down-casting would not lead to issues but the onus is on the programmer to see if situation is of this kind or not.

public void acceptChild(Child c){
    c.extraMethodNotWithParent();
}
Parent p = new Child();
acceptChild((Child)p);

Here programmer is given compile time warning if down-casting is not done. Programmer can have a look and can see if the actual object is really of sub class type then he/she can do explicit down-casting. Thus issues will only come if the programmer has not taken care.

nits.kk
  • 5,204
  • 4
  • 33
  • 55
0

This is because inheritance is specialization, when you inherit a class T, the child class S is a specialization of T, for that Java don't need a cast, but T can have many childs, S' inherits T, S' too, so here if you have an object of S and its referenced as T and you want to get your origin type again, you should make a cast to S and Java check the type at runtime.

an example : A man and a woman are humans, but a human is a man or woman.

Man man = new Man();
Human human = man //Legal.
Man man2 = (Man) humain; //Legal
Woman human = (Woman) humain; //Error, Java can't cast the humain to Woman, because its declared originaly as Man.

If Java is sure made the cast implecitly, if not, it reports it and don't decide in your place, This check is made at runtime.

La VloZ Merrill
  • 173
  • 1
  • 10
0

All the replies here help. I was confused. Removing all the chains in my brain, if I simply think, it now looks like:
Parent can hold subtypes.
You mean thats the benefit of polymorphism and how its achieved.
Child types can hold parent but need explicit downcast.
Since polymorphism is allowed, this downcast is a safeguard. The compiler is asked to trust that the Animal in code is actually going to be a Dog instance that the Dog type wants to hold.

xploreraj
  • 3,792
  • 12
  • 33
  • 51