Subtyping in Java results in unexpected output

73 Views Asked by At

I try to understand how subtyping and inheritance works in Java. Recently I ran into a situation that confuses me. I have got the following Code with three classes:

class Super{
    String i = "Variable of superclass";
    void m() {
        System.out.println(i);
    }
}



class Sub extends Super{
    String i = "Variable of subclass";
    
    /*@Override
    void m() {
        System.out.println(i);
    }*/

}



public class Main {
    public static void main(String[] args) {
                Sub sub = new Sub();
        sub.m();    //Prints Variable of superclass
    }   
}

I am so confused with the output of m(). I just don't understand why the method, which is called on an instance of Sub, uses the variable of the superclass. I guess it has to do with variable hiding / shadowing but I can't figure out what is going on there in detail. If I do something like

Super sup = new Sub();
sup.m();

then I understand, why it prints the variable of the superclass. That's because variables are bound in a static way which I know as variable hiding / shadowing. But I really don't get, what happens in my example.

I was even more confused about what happened when I removed the comment in Sub. I understand that the method is now overridden but I don't get why the method now uses the variable of the subclass even though it's exactly the same code that would have been inherited if I left the comment. I mean, it was already there! How does overriding a method with the exact same code change anything?

I also tried adding System.out.println(this.getClass.getName()); inside the method to make sure that the method is really called on an instance of sub. And yes, it is.

If anyone could help me out understanding, what's going on here, I would be very thankful!

2

There are 2 best solutions below

9
Unmitigated On BEST ANSWER

Methods can be overriden, but fields cannot. Sub does not define a m method, so sub.m(); calls the m method from Super. In the scope of that method, only the i field defined in Super itself is visible.

The i field defined in Sub is a distinct field that shadows the field from the superclass of the same name. If you had instead defined a getI() method in both Super and Sub, calling getI() in Super (when the underlying instance is of type Sub) would use the method defined in Sub.

0
Diego Borba On

When you declare a variable with the same name in a subclass (in this case, Sub) that already exists in the superclass (in this case, Super), it's what we call variable hiding.

This means that the variable declared in the subclass is different from the variable declared in the superclass, and the subclass is not overriding the variable from the superclass but rather creating a new variable with the same name.

You can override the m() method (as it is in your example code), but you can also just change the value using constructors:

Super Class

class Super {
    // Final to make sure value is always set
    final String i;

    // Default constructor
    Super() {
        this("Variable of superclass");
    }

    // Use to overwrite
    Super(String i) {
        this.i = i;
    }

    void m() {
        System.out.println(i);
    }
}

Sub Class

class Sub extends Super {
    Sub() {
        super("Variable of subclass");
    }
}

Check this out: