1

So I am having some confusion understanding dynamic binding vs upcasting.

My original understanding was that when you create a base_class reference and assign it to a derived class object like:

base_class obj = new derived_class();

you can now call the methods/member functions of derived_class with obj.method_of_derived class(), without dynamic binding.

I thought you only needed dynamic binding when the base_class and derived_class have functions/methods that have the same name, and some sort of overriding needs to happen during the compile time to assign them correctly.

Am I totally wrong?

or is there another reason the below code doesn't work?

public class Ticket {

    protected String flightNo;

    public Ticket(){

        this.flightNo = new String();

    }

    public void setTicket(lnode ticket){

        this.flightNo= ticket.getFlightNumber();
    }

    public void displayTicket(){

        System.out.println("Flight number: " + this.flightNo);   
     }
   }

And a derived class extended from the above class

public class originalTicket extends Ticket {
    protected boolean catchAnotherFlight;
    protected boolean baggageTransfer;

    public originalTicket(){

        catchAnotherFlight = false;
        baggageTransfer = false;
    }

    public void setoriginalTicket(lnode ticket){

        setTicket(ticket);

    }
  }

and I am unable to do this:

Ticket object = new originalTicket();
object.setoriginalTicket();//Can't call the originalTicket method.

I have written some programs using upcasting in C++ but I always used dynamic binding. I was just trying to pick up Java and now I am a little startled that I got all the concepts wrong all along. Thanks.

Luc Aux
  • 149
  • 10
  • 2
    *"...when you create a base_class pointer and assign it to a derived class object..."* When you assign the *object* to the variable (which is a reference variable, we don't usually use "pointer" in Java), not the other way around. :-) – T.J. Crowder Mar 13 '18 at 08:30
  • @T.J.Crowder Sorry it's my first time trying to use Java. I meant reference. and totally other way around. – Luc Aux Mar 13 '18 at 08:32
  • 1
    You can always only access fields and methods of the declared class. If your object is declared as `Ticket` you'll always see property and methods of class `Ticket`, you won't be able to use `originalTicket` methods without casting. That's why you can't call `setoriginalTicket` on `object` (you also have a missing argument, but it's not the primary reason it won't work) – BackSlash Mar 13 '18 at 08:36
  • @BackSlash Okay, Confused. So isn't this casting? `base_class ref1 = new derived_class();` Now shouldn't the ref1 be able to call the methods of the derived class? – Luc Aux Mar 13 '18 at 08:38
  • 1
    @LucAux You can consider it as implicit upcast; coming from C++, I'd prefer the term "conversion", though. Either wording, you reduce the derived class' interface to the base class' one. Anything unknown in the base class is not accessible any more via the reference (Java *and* C++) or pointer (C++) without explicit downcast (apart from Derived class having overridden virtual base class' methods/functions). – Aconcagua Mar 13 '18 at 08:50
  • @LucAux Maybe you should have a look at [vtables](https://en.wikipedia.org/wiki/Virtual_method_table), it might give you some understanding how it is possible to call the derived class' version of an overridden function via base type reference/pointer (that's the difficult case, not calling normal/non-virtual functions...). – Aconcagua Mar 13 '18 at 08:56

6 Answers6

3

My original understanding was that when you create a base_class pointer and assign it to a derived class object like:

base_class obj = new derived_class();

you can now call the methods/member functions of derived_class with obj.method_of_derived class(), without dynamic binding.

That's incorrect. obj is a reference variable of type base_class, so base_class defines the interface you have access to via obj (the fields and methods you can use with that reference). So you can use obj.setTicket and obj.displayTicket, but not obj.originalTicket because originalTicket isn't part of the base_class interface. (If you know the object the variable refers to is actually a derived_class object, you could use use casting to change the interface you have to the object: ((derived_class)obj).originalTicket.)

What you may be thinking of is that although the interface you have to the object through obj is defined by base_class, the implementation is from derived_class. So to take a simpler example:

class Base {
    public void a() {
        System.out.println("base a");
    }
}
class Derived extends Base {
    @Override
    public void a() {
        // (Often you'd use `super.a()` here somewhere, but I'm not going to
        // in this specific example)
        System.out.println("derived a");
    }
    public void b() {
        System.out.println("derived b");
    }
}

Then:

Base obj = new Derived();
obj.a(); // "derived a"
obj.b(); // Compilation error

Note that although our interface to the object is of type Base, when we call obj.a(), we get Derived's implementation of it — because the object is actually a Derived instance.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • may I ask then, what is the purpose of upcasting without dynamic binding? – Luc Aux Mar 13 '18 at 08:42
  • 2
    @Luc The purpose is that you can use a common interface provided by some class `Base` without having to know if the real type of the object instance actually is `Derived_1`, `Derived_2`, `Derived_3`, ... All these derived classes can act as type `Base`, however, they might change parts of the behaviour of the base class. Imagine a simple printer device with two interfaces, ethernet and USB. Lot of differences... But do you now want to check at every location in your printer driver if you have one or the other connection in use? Certainly not. – Aconcagua Mar 13 '18 at 09:09
  • 1
    So you create a common interface for both (the base class) and two implementations, one for ethernet, one for USB, and let both classes derive from the base. You can then use the base class' interface anywhere without having to care if connection is ethernet or USB; the only point where you have to know about which connection is active is when you instantiate the driver... – Aconcagua Mar 13 '18 at 09:09
  • @Aconcagua Thanks a lot. – Luc Aux Mar 13 '18 at 09:11
3

Let me try to explain using more layman terms:

public abstract class Animal {
    public abstract void move();
}

public class Cat extends Animal {
    @Override public void move() {
        moveWithLegs();
    }

    public void moveWithLegs() {
        // Implementation
    }
}

public class Fish extends Animal {
    @Override public void move() {
        moveBySwimming();
    }

    public void moveBySwimming() {
        // Implementation
    }
}

Animal animal = new Cat();
animal.move(); // Okay
animal.moveWithLegs(); // Not okay
((Cat) animal).moveWithLegs(); // Okay
((Fish) animal).moveWithLegs(); // ClassCastException exception

The object animal is just an object reference to anything that is an animal. Even though the actual instance type is Cat, the compiler would simply treat it as if it is an Animal.

Therefore, at compile time, you are only allowed to invoke methods defined by Animal class.

If you know animal is an instance of Cat, and you would want to call a method in Cat class, you need to cast this. By casting, you are telling the compiler, "Hey I know this thing is a cat, I want you to treat this like a cat."

Therefore, by casting, you get the access to the methods in Cat class, which also includes all members inherited from Animal class.

If you are not sure if animal is a Cat or Fish, and yet you still cast and call moveWithLegs(), you would get a ClassCastException at runtime, which means you broke the contract that you agreed on at compile time.

Jai
  • 8,165
  • 2
  • 21
  • 52
2

Let's consider C++ as a starting point, as it is the language you are coming from:

class Base
{
    void f() { };
};

class Derived : public Base
{
    void g() { };
};

Base* b = new Derived();
b->g();

Would it work?

You only can call methods (Java)/functions (C++) that are known in the base class. You can override them by making them virtual (C++; Java: all non-static methods are implicitly virtual!):

class Base
{
    virtual void f() { };
};

class Derived : public Base
{
    virtual void f() override { };
};

Base* b = new Derived();
b->f(); // calls Derived's variant of!

Getting back to the first example: You need a downcast to call the new function:

Base* b = new Derived();
static_cast<Derived*>(b)->g(); // if you know for sure that the type is correct
auto d = dynamic_cast<Derived*>(b);
if(d) d->g(); // if you are not sure about the actual type

Exactly the same in Java, just that you do not have explicit pointers, but implicit references...

Base b = new Derived();
((Derived)b).g();
Aconcagua
  • 24,880
  • 4
  • 34
  • 59
2

you can now call the methods/member functions of derived_class with obj.method_of_derived_class(), without dynamic binding.

You can only call the declared interface's methods in Java. In your case only methods of base_class.

I thought you only needed dynamic binding when the base_class and derived_class have functions/methods that have the same name, and some sort of overriding needs to happen during the compile time to assign them correctly.

Method calls in inheritance are always evaluated in runtime with Java. So, in your example this would mean, that you have to declare your classes in the following way:

public class Ticket {
    ...
    public void setTicket() {
        System.out.println("I am the superclass");
    }
    ...
}

And this is your subclass

public class OriginalTicket extends Ticket {
    ...
    @Override
    public void setTicket(){
        System.out.println("I am the subclass");
        super.setTicket();
    }
    ...
  }

You have to call setTicket() this way.

Ticket object = new OriginalTicket();
object.setTicket();

At runtime it doesn't matter if you declare your variable's interface as Ticket or OriginalTicket. Output will always be:

"I am the subclass"
"I am the superclass"

Calling setTicket() through the OriginalTicket interface won't make any different during runtime evaluation.

OriginalTicket object = new OriginalTicket();
object.setTicket();

Output will the be the same:

"I am the subclass"
"I am the superclass"

If you omit the super.setTicket() from the implementation, Ticket.setTicket() method is not called, and output will be:

"I am the subclass"
Mike Williamson
  • 4,915
  • 14
  • 67
  • 104
Oresztesz
  • 2,294
  • 1
  • 15
  • 26
0

Mostly when you do polymorphism, you're using abstract class or Interface as Base.

As we know we're doing UPCASTING at runtime-polymorphism, and this is achieved by method-overriding. So you can only access override methods in a base class. If you want to access the derived class completely you need to tell the compiler explicitly by casting it.

Mudasir H
  • 76
  • 4
0

I had the same confusion tonight when studying Java, and spent a lot of time asking questions and reading questions. To complement what others have said, I believe one paragraph in Absolute Java 6th edition, page 531, completely dispelled my confusion:

The type of a class variable determines which method names can be used with the variable, but the object named by the variable determines which definition of the method name is used. A special case of this rule is the following: The type of a class parameter determines which method names can be used with the parameter, but the argument determines which definition of the method name is used.

The first sentence explains why I (and probably you too) thought that upcasting and dynamic binding has a strange relationship. First we need to understand the difference between type of object and type of reference (textbook uses variable), and then we need to understand that dynamic binding actually uses BOTH of the types. This confused the hell out of me tonight but finally I can sleep tightly.

Nicholas Humphrey
  • 1,220
  • 1
  • 16
  • 33