15.10.1 Field Access Using a Primary

The type of the Primary must be a reference type T, or a compile-time error occurs. The meaning of the field access expression is determined as follows:

Note, specifically, that only the type of the Primary expression, not the class of the actual object referred to at run time, is used in determining which field to use.

Thus, the example:

class S { int x = 0; }
class T extends S { int x = 1; }
class Test {
	public static void main(String[] args) {


		T t = new T();
		System.out.println("t.x=" + t.x + when("t", t));

		S s = new S();
		System.out.println("s.x=" + s.x + when("s", s));

		s = t;
		System.out.println("s.x=" + s.x + when("s", s));

}
static String when(String name, Object t) { return " when " + name + " holds a " + t.getClass() + " at run time."; } }

produces the output:


t.x=1 when t holds a class T at run time.
s.x=0 when s holds a class S at run time.
s.x=0 when s holds a class T at run time.

The last line shows that, indeed, the field that is accessed does not depend on the run-time class of the referenced object; even if s holds a reference to an object of class T, the expression s.x refers to the x field of class S, because the type of the expression s is S. Objects of class T contain two fields named x, one for class T and one for its superclass S.

This lack of dynamic lookup for field accesses allows Java to run efficiently with straightforward implementations. The power of late binding and overriding is available in Java, but only when instance methods are used. Consider the same example using instance methods to access the fields:

class S { int x = 0; int z() { return x; } }
class T extends S { int x = 1; int z() { return x; } }
class Test {


	public static void main(String[] args) {
		T t = new T();
		System.out.println("t.z()=" + t.z() + when("t", t));
		S s = new S();
		System.out.println("s.z()=" + s.z() + when("s", s));
		s = t;
		System.out.println("s.z()=" + s.z() + when("s", s));
	}

	static String when(String name, Object t) {
		return " when " + name + " holds a "
			+ t.getClass() + " at run time.";
	}
}

Now the output is:


t.z()=1 when t holds a class T at run time.
s.z()=0 when s holds a class S at run time.
s.z()=1 when s holds a class T at run time.

The last line shows that, indeed, the method that is accessed does depend on the run-time class of referenced object; when s holds a reference to an object of class T, the expression s.z() refers to the z method of class T, despite the fact that the type of the expression s is S. Method z of class T overrides method z of class S.

The following example demonstrates that a null reference may be used to access a class (static) variable without causing an exception:


class Test {
	static String mountain = "Chocorua";

	static Test favorite(){
		System.out.print("Mount ");
		return null;
	}

	public static void main(String[] args) {
		System.out.println(favorite().mountain);
	}
}

It compiles, executes, and prints:

Mount Chocorua

Even though the result of favorite() is null, a NullPointerException is not thrown. That "Mount " is printed demonstrates that the Primary expression is indeed fully evaluated at run time, despite the fact that only its type, not its value, is used to determine which field to access (because the field mountain is static).