Inheritance

By now you should be pretty familiar with creating classes that contain variables and functions. You should understand that different instances of the same class contain the same types of variables and functions, but those instances can have different values for those variables and do different things based on those values. Here’s an example:


This is a companion discussion topic for the original entry at https://happycoding.io/tutorials/java/inheritance

My query revolves around these two topics → (a) “Casting” & (b) “Overriding Functions”.
If we reference the subtype with a super type variable then according to the “casting” concept, we should not be able to call a function on the subclass using this variable until and unless we caste it.

Similarly, if the sub class overrides a function of its parent class( i.e. if the same function exist in the parent class as well), then when we call this function without casting the variable( the one with a reference to the subtype with a super type variable) then it should call the function on the parent class. But in fact, it calls the function on the subclass. How does this work?

This is a good question, and it’s a common confusion when you’re learning about inheritance.

The way I think about it is there are two things to ask:

  1. What can the compiler know ahead of time, when you’re writing your code?
  2. What does the Java Runtime Environment know when you actually run your code?

Let’s say you have these classes:

public class MessagePrinter {
  public void printMessage() {
    System.out.println("Hello world!");
  }
}

public class GoodbyePrinter extends MessagePrinter {
  public void printMessage() {
    System.out.println("Goodbye world!");
  }
}

public class BetterMessagePrinter extends MessagePrinter {
  private String name;

  public void setName(String name) {
    this.name = name;
  }

  public void printMessage() {
    System.out.println("Hello " + name);
  }
}

Now in your code, you can write a function like this:

public void doSomething(MessagePrinter mp) {
  mp.printMessage();
}

Which you can call like this:

MessagePrinter one = new MessagePrinter();
doSomething(one);

GoodbyePrinter two = new GoodbyePrinter();
doSomething(two);

BetterMessagePrinter three = new BetterMessagePrinter();
three.setName("ascyrax");
doSomething(three);

All of this will work: because GoodbyePrinter and BetterMessagePrinter both extend MessagePrinter, you can pass instances of them into the doSomething() function.

However, back in your doSomething() function, you can’t do this:

public void doSomething(MessagePrinter mp) {
  mp.setName("ascyrax");  // Compiler error!
  mp.printMessage();
}

This will give you a compiler error, because the compiler can’t guarantee that the mp argument will have a setName() function. After all, what if you pass in an instance of GoodbyePrinter?

So all of that was to explain the first part: your compiler can’t know ahead of time what subtypes you’ll pass in, which is why you’re only allowed to reference methods from the parent class.

However, the Java Runtime Environment does know which subtype an instance is when the code is running. That’s why it’s able to call the overridden printMessage() methods in the above example.

1 Like

Its crystal clear now😌. Thanks a ton.