Let's start from some background.
In Inheritance Java tutorial it is stated:
Private Members in a Superclass
A subclass does not inherit the private members of its parent class.
However, if the superclass has public or protected methods for
accessing its private fields, these can also be used by the subclass.
In Polymorphism chapter it is stated:
The Java virtual machine (JVM) calls the appropriate method for the
object that is referred to in each variable. It does not call the
method that is defined by the variable's type. This behavior is
referred to as virtual method invocation and demonstrates an aspect
of the important polymorphism features in the Java language.
Finally, in Hiding fields chapter of the same tutorial it is stated:
Within a class, a field that has the same name as a field in the
superclass hides the superclass's field, even if their types are
different. Within the subclass, the field in the superclass cannot be
referenced by its simple name. Instead, the field must be accessed
through super, which is covered in the next section. Generally
speaking, we don't recommend hiding fields as it makes code difficult
to read.
Thus, subclass has no way to access private members of superclass directly. However, they still exist, and could be accessed or modified using non-private accessors/mutators.
Now back to the question itself.
In your first example, you override neither accessor nor mutator - you just call the inherited ones all the time. They return and modify the value of f of Base.
Why inherited accessor/mutator do not return/modify the value of f of Base2? Because even if f of Base was not private, it would not be overridden, but just hidden.
In your second example, you override the accessor. This is where you start to involve polymorphism. This short answer might be helpful to understand it. When you call b.setF(3)
, you set the value of f of Base. However, when you call getF()
you get the value of f of Base2, except for the case when you call it with keyword super.
Please note, that in your last call System.out.println(((Base)b).getF())
casting to Base does nothing since b is already declared as Base. You can not call an overridden method of superclass without using super (as you already know, only instance methods can be overridden).
In your third example, you override the mutator. The situation is opposite to your second example. When you call b.setF(3)
, you set the value of f of Base2. But you always get f of Base from the getter, because the getter is not overriden. Thus, all 4 calls to getF()
return the initial value of f of Base.
In you last example, you override both accessor and mutator. Therefore, they operate on f of Base2. The only call that return the initial value of f of Base is obviously super.getF()
.
This is definitely not a perfect explanation, but I hope it helps.