Wednesday, May 31, 2023

Quiz yourself: How private is a Java private inner class?


Given the following interface and two classes

Quiz Yourself, Oracle Java Certified, Oracle Java Certification, Oracle Java Career, Oracle Java Skill, Oracle Java Jobs, Oracle Java Prep, Oracle Java Preparation

interface Paintable {
  void paint(String color);
}
class House implements Paintable {
  private class Floor implements Paintable {
    public void paint(String color) {
      System.out.println("Floor painted to " + color);
    }
  }
  Floor f;
  public void paint(String color) {
    System.out.println("House painted to " + color);
  }
  public House() {f = new Floor();  }
  public Floor getFloor() {return f;}
}

and a method fragment

01: var itemsToPaint = new ArrayList<Paintable>();
02: var house = new House();
03: var floor = house.getFloor();
04: itemsToPaint.add(house); 
05: itemsToPaint.add(floor);
06: itemsToPaint.stream().forEach(p -> {
07:     p.paint("PINK");
08: });

Which statement is true? Choose one.

A. To make the method compilable, you must change the code as follows:
     03: House.Floor floor = house.getFloor();
B. To make the method compilable, you must change the inner class access modifier as follows:
     public class Floor implements Paintable
C. To make the method compilable, you must change the code as follows:
     05: itemsToPaint.add((Paintable) floor);
D. The method compiles without changes.

Answer. This question investigates some subtlety in the meaning of private as it relates to Java types. The code in the question declares a private inner class, Floor, within the House class. Because the class is private, the type prevents arbitrary usage outside the House class. However, this type is also the type returned by the public method getFloor(). This raises the question of how the caller of that method (in line 03 of the method fragment) will see that returned type.

Option A suggests declaring the floor variable on line 03 with the type House.Floor, but this is the private type. You should know that in code outside the House class, this approach will not compile. From this you can quickly reject option A as incorrect.

Options B and C both suggest that you must change the code—in other words, they both assert that the code does not compile as it stands. It turns out that these options are both incorrect: The code does in fact compile as written. Therefore, option D is correct.

There are three ways the variable floor on line 03 can be declared to allow that line of code to be compiled. Each declaration has slightly different consequences.

The first way. Any reference type can be stored in a variable of type Object, and the same is true at line 03. If you do this, you would be able to execute the basic methods of Object on the reference variable floor. This isn’t very helpful, however, since the variable would not be a valid argument for the add method on line 05, which expects a Paintable argument.

The second way. You can make floor of type Paintable. If both the Paintable interface and the code in the method fragment are in the same package, the compiler will let you store the returned value of getFloor in a variable of this type. Because the compiler knows that the House.Floor type is Paintable, and that interface is accessible to the code, the compiler is happy. Doing this also allows the floor variable to be a valid argument to the add method on line 05. Clearly this is preferable to using Object for the type of the floor variable and would allow the code to work—but as noted, the change isn’t necessary, because the example code is already correct.

The third way. The compiler will also allow you to declare floor using the var pseudotype. This, of course, is what the code already does. So, what happens when you do this? The compiler ascribes the variable as a so-called nondenotable synthetic type. The effect is that the method fragment knows almost nothing about the object to which floor refers. You can’t invoke the paint method on it and, in fact, you can’t even invoke methods of Object (such as equals or toString) on it—that’s surely a surprise! However, the compiler keeps track of the fact that this object refers to an instance of House.Floor, and if you pass this object into a call for any method for which that’s a valid argument type, the receiving method invocation will be valid.

In a sense, the compiler says “OK, method fragment: I know what this object is, even though you aren’t allowed to know. If you correctly pass the object to something else, I’ll handle that, but you can’t do anything else with this.”

That means that you can call the add method on line 05 because that needs a Paintable, and the compiler knows that the House.Floor type implements Paintable. In addition, if you had an accessible method defined in House that took a House.Floor argument, you could also call that with floor as an argument. But, as noted, the method fragment can’t call any method on the floor reference.

With all that said, the bottom line is that the code works as written, and no changes are needed.

This discussion is a simplification of the full story and is an attempt to explain the practical side of the behavior. If you want all the nitty-gritty details, enumerated as facts rather than as explanations, you can read more in the four references listed in the “Dig deeper” section below.

As a side note, this question almost certainly goes beyond the depth of the exam, so consider this an interesting puzzler and insight into Java, rather than an authentic example of an exam question.

Conclusion. The correct answer is option D.

Source: oracle.com

Related Posts

0 comments:

Post a Comment