<The previous article in this series | The table of contents of this series | The next article in this series>
Let's Review the Rules That Determine Which Class Member Will Be Accessed for Any Expression, Which Are True Without Exception
About: Java Programming Language
My understanding of the rules of class member access resolution wasn't accurate.
What was your understanding?
My understanding was that as for instance methods, the instance type just determines the method to be called.
Explain more.
For example, there are a base class, 'BaseClass' and a sub class, 'ExtendedClass'.
All right.
An instance of 'ExtendedClass' may be referred to by a variable of the type, 'BaseClass'.
Let's name the variable 'l_anObject'.
When we call a method, say 'aMethod', using the variable, like 'l_anObject.aMethod ()', my understanding was just, "The instance type, 'ExtendedClass', is all that counts; the variable type doesn't matter."
Ah, that is too simplistic although such simplistic understandings are pleasant.
I thought, "Whatever the variable type is, the instance will be reached, and the instance is all that matters. So, if 'ExtendedClass' has a method of that name, it will be the one that will be called."
That may be an ideal, but Java program language isn't built like that.
This example shows how my understanding was wrong.
package test.classmemberaccessresolutiontest1;
public class Test1BaseClass {
void aMethod () {
System.out.println ("From Test1BaseClass.");
}
}
package test.classmemberaccessresolutiontest2;
import test.classmemberaccessresolutiontest1.Test1BaseClass;
public class Test1ExtendedClass extends Test1BaseClass {
public void aMethod () {
System.out.println ("From Test1ExtendedClass.");
}
}
package test.classmemberaccessresolutiontest1;
import test.classmemberaccessresolutiontest2.Test1ExtendedClass;
public class Test1TestClass {
public void test () {
Test1BaseClass l_anObject = new Test1ExtendedClass ();
l_anObject.aMethod ();
}
}
According to your inaccurate understanding, the method of 'Test1ExtendedClass' would be called, but that is not how Java works.
Another intuitive, but incorrect understanding is an image in which inheriting a method is imagined as though the sub class has the inherited method as its own.
For example, 'Test2BaseClass' has a method, 'printValueA', and 'Test2ExtendedClass' extends 'Test2BaseClass' like this.
package test.classmemberaccessresolutiontest1;
public class Test2BaseClass {
protected static String s_valueA = "initial value A";
public static void printValueA () {
System.out.println (String.format ("The value A is \"%s\".", s_valueA));
}
}
package test.classmemberaccessresolutiontest1;
public class Test2ExtendedClass extends Test2BaseClass {
protected static String s_valueA = "overwritten value A";
}
package test.classmemberaccessresolutiontest1;
public class Test2TestClass {
public void test () {
Test2ExtendedClass.printValueA ();
}
}
'Test2ExtendedClass' has inherited 'printValueA', but still, 'printValueA' is of 'Test2BaseClass', not of 'Test2ExtendedClass'. If we imagine as though 'Test2ExtendedClass' has become like this,
package test.classmemberaccessresolutiontest1;
public class Test2ExtendedClass {
protected static String s_valueA = "overwritten value A";
public static void printValueA () {
System.out.println (String.format ("The value A is \"%s\".", s_valueA));
}
}
that's wrong.
If the thing was as imagined above, the overwritten value would be printed, but that isn't the case.
In fact, in the worldly world, inheriting means that the inheritor now possesses the legacy, but in Java, inheriting just means that the inheritor can see and use the legacy.
Those faulty understandings are intuitive illusions, but I understand why we tend to fall into those illusions: probably, those illusions are things hoped for but unrealized because of complications concerning the implementation of the program language.
Those understandings are simple, but cause some troubles in some cases. Here, we want to review the exact rules, not an intuitive, inexact image fraught with exceptions.
We can try.
What we want are the rules that determine which class member will be accessed for any expression.
For example, for an expression, "l_anObject.aMethod ()" above, will 'aMethod' of 'Test1ExtendedClass' be accessed or will 'aMethod' of 'Test1BaseClass' be accessed?
All right.
First, we must see the qualification to the member name in the expression.
When the expression is 'l_anObject.aMethod ()', 'l_anObject' is the qualification to the member name, 'aMethod'.
There must be always a qualification because if we just say 'aMethod ()', the compiler or the runtime can't determine which 'aMethod' it is.
When the qualification isn't explicitly stated, there is an implicit qualification. In that case, we must know the implicit qualification.
When the qualification is implicit, we seem to be more likely to make misjudgments: as the qualification isn't seen, we tend to imagine things freely.
Yes. In 'printValueA' of 'Test2BaseClass' above, the implicit qualification to 's_valueA' is 'Test2BaseClass'. If the qualification was explicitly stated so, we wouldn't make any misjudgment, but as the qualification is unseen, we tend to conceive a hope that the value of 'Test2ExtendedClass' might be accessed.
So, to be conscious of the qualification is helpful when the qualification is implicit.
When the expression is in any static context, the implicit qualification is the class in which the expression appears; when the expression is in any instance context, the implicit qualification is 'this'.
Any place where 'this' can be used is an instance context; elsewhere is a static context. For example, the inside of a static method is an static context, and the inside of an instance method is an instance context.
OK.
Second, we must know the class of the qualification. Note that the class of the qualification doesn't mean the instance class.
In the case of 'l_anObject', the class of the qualification is 'Test1BaseClass', not 'Test1ExtendedClass', the instance class.
As we learned above, we can't just jump to the instance class. The class of the qualification is always the starting point.
All right.
The qualification may be more complicated than a variable or a class, but it always has a class.
For example, the qualification may be '(new Test1ExtendedClass ())', '((Test1BaseClass) new Test1ExtendedClass ())', or whatever expression that returns a kind of Object or a class.
Yes. The class for the first example is 'Test1ExtendedClass' while the class for the second example is 'Test1BaseClass'. You know, what I mean.
I know.
Note that the class of 'this' is the class in which the expression appears.
Again, we are not talking about the instance class, but the variable type.
Although we didn't define the variable, 'this', ourselves, we should assume that it is defined implicitly like this.
class XXX {
private XXX this;
}
So it seems. Although 'this' can refer to an instance of a sub class, the variable type of 'this' doesn't miraculously change to the sub class: the variable type is fixed as 'XXX'.
Third, if the qualification represents a class, not an instance, the member of the class will be accessed (of course, the member may not be declared in the class itself, but has been inherited from a super class; you should know what I mean).
When the qualification is 'Test2BaseClass', it represents a class while when the qualification is 'l_anObject', it represents an instance.
Yes.
When the qualification is a class, of course, the member has to be static; otherwise, a compile error will occur.
As there is no instance to look to, there is no place to look for the member but the class of the qualification itself.
Now, we are dealing with only cases in which the qualifications represent instances.
For any other case, the resolution is already finished in the previous section.
We must know how the member is overridden from the class of the qualification (of course, the member may not be defined in the class of the qualification, but has been inherited from a super class) toward the instance class.
There can be some confusion about the definition of the term, 'override'.
Ah, there can. I don't know what is the official definition of 'override', but let's clarify what we mean by 'override'.
So, our definition might be different from the official one or one by another person.
It might, but we use our definition because other definitions (if there are such things) aren't useful for our purposes.
First, we don't include shadowing in overriding.
So, fields are shadowed, never overridden.
Yes.
Second, any method that is invisible from a sub class isn't overridden.
For example, let's think of this.
public class ClassA {
private void aMethod () {
System.out.println ("From ClassA.");
}
}
public class ClassB extends ClassA {
public void aMethod () {
System.out.println ("From ClassB.");
}
}
public class ClassC extends ClassB {
public void aMethod () {
System.out.println ("From ClassC.");
}
}
The method of 'ClassA' isn't overridden because it is invisible from 'ClassB' although the method of 'ClassB' is overridden.
'ClassB' created a new method that is unrelated with the method of 'ClassA'.
I see.
We need to know that any field or any static method isn't overridden.
Only instance methods can be overridden.
When we identify the last class of the continuous link of overriding from the class of the qualification toward the instance class, the member of the last class will be accessed.
So, the link must be continuous starting from the class of the qualification.
That's the reason why we have to first look at the the class of the qualification: we can't just look at the instance class and determine that the method of the instance class will be accessed.
In the example above, if the instance class is 'ClassC', the method of the instance class overrides the method of 'ClassB', but we can't say that the method of 'ClassC' will be called for the expression, '( (ClassA) new ClassC ()).aMethod ()'.
That is so.
In fact, when the method of the class of the qualification is public or protected, it is guaranteed that the same-signature method of a sub class overrides the method of the class of the qualification because the access privilege can't be weakened in the chain of inheritance.
So, the link of overriding can't be severed somewhere in the middle.
However, when the access privilege of the method of the class of the qualification is 'unspecified' (meaning being visible only from the same package), the instance class may have overridden the method of the class of the qualification even if the instance class is not in the same package with the class of the qualification.
For example, let's think of this.
package test.classmemberaccessresolutiontest1;
public class Test3BaseClass {
void aMethod () {
System.out.println ("From Test3BaseClass.");
}
}
package test.classmemberaccessresolutiontest1;
public class Test3ExtendedClass extends Test3BaseClass {
public void aMethod () {
System.out.println ("From Test3ExtendedClass.");
}
}
package test.classmemberaccessresolutiontest2;
import test.classmemberaccessresolutiontest1.Test3ExtendedClass;
public class Test3ExtendedExtendedClass extends Test3ExtendedClass {
public void aMethod () {
System.out.println ("From Test3ExtendedExtendedClass.");
}
}
package test.classmemberaccessresolutiontest1;
import test.classmemberaccessresolutiontest2.Test3ExtendedExtendedClass;
public class Test3TestClass {
public void test() {
( (Test3BaseClass) new Test3ExtendedExtendedClass ()).aMethod ();
}
}
The result is this.
From Test3ExtendedExtendedClass.
It's important that 'Test3ExtendedClass' made the method public, making it visible to 'Test3ExtendedExtendedClass'. Because of that, 'aMethod' is continuously overridden from 'Test3BaseClass' through 'Test3ExtendedExtendedClass'.
So, we have to look at intermediate sub classes, not just the class of the specification and the instance class, in order to know which method will be called.
I thought, on the other hand, "the instance class may not override the method of the class of the qualification even if the instance class is in the same package with the class of the qualification." However, that doesn't seem to be true somehow.
Um? . . . Why? I thought so too.
Let's think of this example.
package test.classmemberaccessresolutiontest1;
public class Test4BaseClass {
void aMethod () {
System.out.println ("From Test4BaseClass.");
}
}
package test.classmemberaccessresolutiontest2;
import test.classmemberaccessresolutiontest1.Test4BaseClass;
public class Test4ExtendedClass extends Test4BaseClass {
public void aMethod () {
System.out.println ("From Test4ExtendedClass.");
}
}
package test.classmemberaccessresolutiontest1;
import test.classmemberaccessresolutiontest2.Test4ExtendedClass;
public class Test4ExtendedExtendedClass extends Test4ExtendedClass {
public void aMethod () {
System.out.println ("From Test4ExtendedExtendedClass.");
}
}
package test.classmemberaccessresolutiontest1;
public class Test4TestClass {
public void test() {
( (Test4BaseClass) new Test4ExtendedExtendedClass ()).aMethod ();
}
}
The result is this.
From Test4ExtendedExtendedClass.
Um? Hmm, what's happening here?
I thought, "As 'aMethod' of 'Test4BaseClass' is invisible from 'Test4ExtendedClass', 'Test4ExtendedClass' didn't override it: 'aMethod' of 'Test4ExtendedClass' is a newly created method unrelated with 'aMethod' of 'Test4BaseClass'."
That must be so.
And I thought, "As 'Test4ExtendedExtendedClass' sees 'aMethod' of 'Test4ExtendedClass', it has overridden 'aMethod' of 'Test4ExtendedClass'. So, 'aMethod' of 'Test4BaseClass' wasn't overridden: the result must be 'From Test4BaseClass.'"
Well, I began to see what's going on.
What's going on?
As 'aMethod' of 'Test4BaseClass' hasn't been shadowed by 'aMethod' of 'Test4ExtendedClass', it is visible from 'Test4ExtendedExtendedClass'.
Um? . . . Hmm, that's true. What 'Test4ExtendedClass' did wasn't 'shadowing': what's invisible can't be shadowed. In fact, 'aMethod' of 'Test4BaseClass' and 'aMethod' of 'Test4ExtendedClass' are two different things, and both are visible from 'Test4ExtendedExtendedClass' with both having the same signature.
Java prohibits multiple inheritance in order to avoid such a situation, but there seems to be a loophole.
So, which method is 'Test4ExtendedExtendedClass' overriding?
Let's change the 'Test4TestClass' above like this.
package test.classmemberaccessresolutiontest1;
import test.classmemberaccessresolutiontest2.Test4ExtendedClass;
public class Test4TestClass {
public void test() {
( (Test4BaseClass) new Test4ExtendedExtendedClass ()).aMethod ();
( (Test4ExtendedClass) new Test4ExtendedExtendedClass ()).aMethod ();
}
}
The result is this.
From Test4ExtendedExtendedClass.
From Test4ExtendedExtendedClass.
Well, it seems that 'Test4ExtendedExtendedClass' has overridden both.
What if 'Test4ExtendedExtendedClass' doesn't declare the method 'public'?
The compiler gives us this error.
error: aMethod() in Test4ExtendedExtendedClass cannot override aMethod() in Test4ExtendedClass
attempting to assign weaker access privileges; was public
Ah-ha . . ., so it must override both.
Let's eliminate the overriding from 'Test4ExtendedExtendedClass' and add this at the last of the 'test' method.
(new Test4ExtendedExtendedClass ()).aMethod ();
What will be the output of that line?
Actually, this is it.
From Test4ExtendedClass.
Hmm . . ., 'aMethod' of 'Test4ExtendedClass' seems to have the precedence, but anyway, that doesn't seem to change the fact that 'aMethod' of 'Test4BaseClass' has been also overridden.
I will cite an example in which overriding link is severed in the middle.
package test.classmemberaccessresolutiontest1;
public class Test5BaseClass {
void aMethod () {
System.out.println ("From Test5BaseClass.");
}
}
package test.classmemberaccessresolutiontest1;
public class Test5ExtendedClass extends Test5BaseClass {
void aMethod () {
System.out.println ("From Test5ExtendedClass.");
}
}
package test.classmemberaccessresolutiontest2;
import test.classmemberaccessresolutiontest1.Test5ExtendedClass;
public class Test5ExtendedExtendedClass extends Test5ExtendedClass {
void aMethod () {
System.out.println ("From Test5ExtendedExtendedClass.");
}
}
package test.classmemberaccessresolutiontest1;
import test.classmemberaccessresolutiontest2.Test5ExtendedExtendedClass;
public class Test5TestClass {
public void test() {
( (Test5BaseClass) new Test5ExtendedExtendedClass ()).aMethod ();
}
}
The result is this.
From Test5ExtendedClass.
The result is as we expected: 'aMethod' of 'Test5ExtendedClass' isn't overridden by 'Test5ExtendedExtendedClass' because it is invisible from 'Test5ExtendedExtendedClass'.
Thus, we have to start from the class of qualification and follow the overriding link toward the instance class.
Although we didn't use the '@Override' annotation, internationally (in order to not reveal traps prematurely), but we should use the '@Override' annotation in order to avoid slips.
<The previous article in this series | The table of contents of this series | The next article in this series>