2017-10-01

4: Let's Review the Exact Rules of Class Member Access Resolution

<The previous article in this series | The table of contents of this series | The next article in this series>

Main Body START

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

These Understanding Aren't Exactly Correct

-Hypothesizer

My understanding of the rules of class member access resolution wasn't accurate.

-Rebutter

What was your understanding?

-Hypothesizer

My understanding was that as for instance methods, the instance type just determines the method to be called.

-Rebutter

Explain more.

-Hypothesizer

For example, there are a base class, 'BaseClass' and a sub class, 'ExtendedClass'.

-Rebutter

All right.

-Hypothesizer

An instance of 'ExtendedClass' may be referred to by a variable of the type, 'BaseClass'.

-Rebutter

Let's name the variable 'l_anObject'.

-Hypothesizer

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."

-Rebutter

Ah, that is too simplistic although such simplistic understandings are pleasant.

-Hypothesizer

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."

-Rebutter

That may be an ideal, but Java program language isn't built like that.

-Hypothesizer

This example shows how my understanding was wrong.

@Java Source Code
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 ();
 }
}
-Rebutter

According to your inaccurate understanding, the method of 'Test1ExtendedClass' would be called, but that is not how Java works.

-Hypothesizer

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.

@Java Source Code
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 ();
 }
}
-Rebutter

'Test2ExtendedClass' has inherited 'printValueA', but still, 'printValueA' is of 'Test2BaseClass', not of 'Test2ExtendedClass'. If we imagine as though 'Test2ExtendedClass' has become like this,

@Java Source Code
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.

-Hypothesizer

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.

-Rebutter

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.

So, Let's Review the Exact Rules of Class Member Access Resolution

-Hypothesizer

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.

-Rebutter

We can try.

-Hypothesizer

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?

-Rebutter

All right.

First, We Must Identify the Qualification to the Member Name in the Expression

-Hypothesizer

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.

-Rebutter

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.

-Hypothesizer

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.

-Rebutter

So, to be conscious of the qualification is helpful when the qualification is implicit.

-Hypothesizer

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.

-Rebutter

OK.

Second, We Must Know the Class of the Qualification

-Hypothesizer

Second, we must know the class of the qualification. Note that the class of the qualification doesn't mean the instance class.

-Rebutter

In the case of 'l_anObject', the class of the qualification is 'Test1BaseClass', not 'Test1ExtendedClass', the instance class.

-Hypothesizer

As we learned above, we can't just jump to the instance class. The class of the qualification is always the starting point.

-Rebutter

All right.

-Hypothesizer

The qualification may be more complicated than a variable or a class, but it always has a class.

-Rebutter

For example, the qualification may be '(new Test1ExtendedClass ())', '((Test1BaseClass) new Test1ExtendedClass ())', or whatever expression that returns a kind of Object or a class.

-Hypothesizer

Yes. The class for the first example is 'Test1ExtendedClass' while the class for the second example is 'Test1BaseClass'. You know, what I mean.

-Rebutter

I know.

-Hypothesizer

Note that the class of 'this' is the class in which the expression appears.

-Rebutter

Again, we are not talking about the instance class, but the variable type.

-Hypothesizer

Although we didn't define the variable, 'this', ourselves, we should assume that it is defined implicitly like this.

@Java Source Code
class XXX {
 private XXX this;
}
-Rebutter

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, the Member of the Class Will Be Accessed (The Resolution Is Finished for that Case)

-Hypothesizer

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).

-Rebutter

When the qualification is 'Test2BaseClass', it represents a class while when the qualification is 'l_anObject', it represents an instance.

-Hypothesizer

Yes.

When the qualification is a class, of course, the member has to be static; otherwise, a compile error will occur.

-Rebutter

As there is no instance to look to, there is no place to look for the member but the class of the qualification itself.

Fourth, We Must Know How the Member Is Overridden From the Class of the Qualification Toward the Instance Class

-Hypothesizer

Now, we are dealing with only cases in which the qualifications represent instances.

-Rebutter

For any other case, the resolution is already finished in the previous section.

-Hypothesizer

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.

-Rebutter

There can be some confusion about the definition of the term, 'override'.

-Hypothesizer

Ah, there can. I don't know what is the official definition of 'override', but let's clarify what we mean by 'override'.

-Rebutter

So, our definition might be different from the official one or one by another person.

-Hypothesizer

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.

-Rebutter

So, fields are shadowed, never overridden.

-Hypothesizer

Yes.

Second, any method that is invisible from a sub class isn't overridden.

-Rebutter

For example, let's think of this.

@Java Source Code
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.

-Hypothesizer

'ClassB' created a new method that is unrelated with the method of 'ClassA'.

-Rebutter

I see.

-Hypothesizer

We need to know that any field or any static method isn't overridden.

-Rebutter

Only instance methods can be overridden.

-Hypothesizer

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.

-Rebutter

So, the link must be continuous starting from the class of the qualification.

-Hypothesizer

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.

-Rebutter

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 ()'.

-Hypothesizer

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.

-Rebutter

So, the link of overriding can't be severed somewhere in the middle.

-Hypothesizer

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.

-Rebutter

For example, let's think of this.

@Java Source Code
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.

@Output
From Test3ExtendedExtendedClass.
-Hypothesizer

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'.

-Rebutter

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.

-Hypothesizer

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.

-Rebutter

Um? . . . Why? I thought so too.

-Hypothesizer

Let's think of this example.

@Java Source Code
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.

@Output
From Test4ExtendedExtendedClass.

Weird Overriding of 'Unspecified' Access Privileged Methods

-Rebutter

Um? Hmm, what's happening here?

-Hypothesizer

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'."

-Rebutter

That must be so.

-Hypothesizer

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.'"

-Rebutter

Well, I began to see what's going on.

-Hypothesizer

What's going on?

-Rebutter

As 'aMethod' of 'Test4BaseClass' hasn't been shadowed by 'aMethod' of 'Test4ExtendedClass', it is visible from 'Test4ExtendedExtendedClass'.

-Hypothesizer

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.

-Rebutter

So, which method is 'Test4ExtendedExtendedClass' overriding?

-Hypothesizer

Let's change the 'Test4TestClass' above like this.

@Java Source Code
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.

@Output
From Test4ExtendedExtendedClass.
From Test4ExtendedExtendedClass.
-Rebutter

Well, it seems that 'Test4ExtendedExtendedClass' has overridden both.

What if 'Test4ExtendedExtendedClass' doesn't declare the method 'public'?

-Hypothesizer

The compiler gives us this error.

@Output
error: aMethod() in Test4ExtendedExtendedClass cannot override aMethod() in Test4ExtendedClass
attempting to assign weaker access privileges; was public
-Rebutter

Ah-ha . . ., so it must override both.

Let's eliminate the overriding from 'Test4ExtendedExtendedClass' and add this at the last of the 'test' method.

@Java Source Code
  (new Test4ExtendedExtendedClass ()).aMethod ();

What will be the output of that line?

-Hypothesizer

Actually, this is it.

From Test4ExtendedClass.

-Rebutter

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.

An Example in Which Overriding Link Is Severed in the Middle

-Hypothesizer

I will cite an example in which overriding link is severed in the middle.

@Java Source Code
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.

@Output
From Test5ExtendedClass.
-Rebutter

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.

We Should Use the '@Override' Annotation

-Hypothesizer

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.

Main Body END

<The previous article in this series | The table of contents of this series | The next article in this series>