Wednesday, August 2, 2023

Quiz yourself: How Java resolves access to static elements in a type declaration

Quiz Yourself, Oracle Java, Oracle Java Certification, Oracle Java Prep, Oracle Java Preparation, Oracle Java Guides, Oracle Java Learning, Oracle Java Tutorial and Materials

Oh no! The situation seems to be complicated for several reasons.


Given the following code

class SuperS {
  public static String msg = "SuperS";
  public static String method() { return "SuperM"; }
}
class SubS extends SuperS {
  private static String msg = "SubS";
  public static String method() { return "SubM"; }
}
class Super {
  static SuperS superS;
  public static void main(String[] args) {
    var v = superS;
    System.out.println(v.msg);
    System.out.println(v.method());
    v = (SubS) v;
    System.out.println(v.msg);
    System.out.println(v.method());
  }
}

What is the result? Choose one.

A. Compilation fails.

B. NullPointerException is thrown at runtime.

C. ClassCastException is thrown at runtime.

D. The following is printed:
SuperS
SuperM
SubS
SubM

E. The following is printed:
SuperS
SuperM
SuperS
SuperM

Answer. This question investigates some aspects of how Java resolves access to static elements in a type. In this question, the situation seems to be complicated for several reasons.

◉ The reference variable v is declared using var rather than an explicit type name such as SuperS.
◉ The qualifying prefix to the static elements is a variable rather than a class name.
◉ The variable v that’s used as a prefix contains a null reference rather than pointing to an actual object. You can see this because the static field superS is not explicitly initialized in the code and, as such, is guaranteed to be initialized to null.
◉ It appears that the public static field msg in the parent class is shadowed by a private field of the same name in the subclass.

Consider the first of those issues. In this question, the var pseudotype is used to request that the compiler infer the type of the variable v from the type of the expression used to initialize v. That expression is the static field superS, which has type SuperS and the value null; therefore, v is also of type SuperS and has the value null. From that point forward, there is no difference in the behavior of the variable v from how it would behave if it had been given an explicit type. In particular, var does not create dynamic typing in the way that occurs in languages such as JavaScript and Python.

From this, you know that this declaration

var v = superS;

is identical in effect to the following explicit form

SuperS v = superS;

and, in this case, it has identical effect to this form

SuperS v = null;

Note the use of a reference variable (v), instead of a class name, as the prefix in an expression referring to a static element. Java borrowed a lot of C++ syntax, and one of those syntax elements is the ability to use an instance expression as a prefix in the way used here. When static methods were added to interfaces in Java 8, this ability was not propagated to that feature, presumably because it creates ambiguous code.

Given this syntax, there’s a tendency to assume that the reference is followed to the actual object, and then the element (field or method) is found in that object (which approximately describes the behavior if the element in question were an instance feature). However, this is not a valid model for the behavior with static elements. Static elements belong to the class, not to any particular instance of that class. The compiler creates code that simply determines the target class and obtains the element from that. Notice there’s nothing like late binding or polymorphism going on in this case. As a side note, there’s also nothing of that sort going on with instance fields; late-binding behavior relates only to overridable instance methods.

This tells you that the value of the prefix object is entirely irrelevant because it plays no part in accessing static elements. That illuminates the remaining pieces of this part of the puzzle: It doesn’t matter if the reference is the null value, because it’s never used.

Also, casting the value to the subtype and reassigning it, as in the following line, is also irrelevant:

v = (SubS) v;

An assignment like that might change the value of the variable (although here it does not), but it cannot change the type of that variable, which is determined entirely by its declaration. Indeed, nothing can change the type of a variable at runtime, even though the cast on the right of the assignment does create a temporary expression that has the cast type.

One more issue to consider is whether a shadowing variable in a subclass can be less accessible than the variable that it shadows, as is the situation with the two msg fields in this example. Perhaps surprisingly, this is permitted.

Why might that be a surprise? Well, broadly, the Liskov substitution principle tells you that substitute elements in a child type should not cause surprises when they are compared with their original elements in a parent type. For this reason, an overriding method in Java cannot be less accessible than the overridden method, cannot declare checked exceptions that are not permitted from the overridden method, and (somewhat simplified) must provide an assignment-compatible return type. However, these are static fields, one shadowing the other, not overriding methods, so they’re not really substitutes, and it turns out that they’re not subject to the same rules.

What’s perhaps odder is that if a static method in a class hides a static method in a parent class, the method in the subclass is subject to these rules even though it’s not really overriding either. But, since both method() methods are declared public in this question, that does not cause a problem here.

From this, remembering that the type of the variable v is SuperS, you can determine that the code will print the text from the SuperS class twice, and it will never print the text from the SubS class. You also know that no errors are reported during compilation or execution. Therefore, the correct answer is option E and options A, B, C, and D are incorrect.

Conclusion. The correct answer is option E.

Source: oracle.com

Related Posts

0 comments:

Post a Comment