Java SE 8 Programmer I (OCAJP 8) study materials
Java SE 8 Programmer I (OCAJP 8) study resources

Booking Overloads, Leaked Arrays and a Static Counter

Java SE 8 Programmer I (OCAJP 8) · Put your knowledge to work in context.

All case studies

Booking Overloads, Leaked Arrays and a Static Counter

A training company has one Booking class with four overloads: void reserve(int n), void reserve(long n), void reserve(Integer n) and void reserve(int... n). A test class calls reserve(2), reserve(2L), reserve(Integer.valueOf(2)) and reserve(). A reviewer predicts two compile-time ambiguities: one for reserve(2) (between int and long) and one for reserve() (between the wrapper and the varargs version). The class also stores its seat table as private final int[] seats = new int[4]; and exposes it with public int[] getSeats() { return seats; }. A caller does b.getSeats()[0] = 99; and the reservation grid silently changes; the team argues the field cannot be tampered with because it is private final. Finally, a static counter bookings is incremented in the two-argument constructor, which the one-argument constructor reaches through this(n, "ANY").

1. Which overload does each of the four calls bind to, and are any of them ambiguous? Justify with the three-phase overload resolution rule.

Read the worked answer

None of the four calls is ambiguous.

Overload resolution has three phases and stops at the first phase that finds an applicable method: (1) strict invocation - identity, widening primitive, widening reference, but no boxing and no varargs; (2) loose invocation - boxing/unboxing allowed; (3) variable arity, where int... can accept any number of arguments.

  • reserve(2): phase 1. Applicable to reserve(int) by identity and to reserve(long) by widening. The most specific method wins, and int is more specific than long, so reserve(int) is called.
  • reserve(2L): phase 1, exact match reserve(long).
  • reserve(Integer.valueOf(2)): the argument type is Integer, which is applicable to reserve(Integer) by identity in phase 1, so phase 1 succeeds immediately and reserve(Integer) is called - reserve(int) would need unboxing and is never considered.
  • reserve(): phases 1 and 2 find nothing applicable, so phase 3 selects the varargs method; the compiler creates an empty int[0], giving reserve(int...).

If reserve(int) were deleted, reserve(2) would widen to reserve(long) rather than box to reserve(Integer), because phase 1 succeeds and phase 2 is never reached.

2. Is `b.getSeats()[0] = 99;` an encapsulation failure? What does `private final` actually guarantee, and how should the accessor be written?

Read the worked answer

Yes, it is a leak. The accessor returns a copy of the reference, not the array. The caller and the object now share one array object, so the caller can change elements without going through the class, and can also do b.getSeats()[10] = 1; to trigger ArrayIndexOutOfBoundsException from outside.

private limits visibility of the field name; it does not make the referenced object immutable. final only means the field can be assigned once - it prevents rebinding seats to another array, but seats[0] = 99; is not an assignment to the field and stays perfectly legal.

Correct accessor options:

  • Return a defensive copy: public int[] getSeats() { return seats.clone(); } - callers may mutate their copy harmlessly.
  • Return a read-only or copied view, e.g. Collections.unmodifiableList(...) over a boxed list, or Arrays.copyOf(seats, seats.length).
  • Expose only scalar operations (getSeat(int index), setSeat(int index, int value)) with bounds checks, so state changes are controlled and validated.

The same trap applies to java.util.Date and mutable collections returned from getters.

3. How many times does `bookings` increase for `new Booking(3)` if the increment is written only in the two-argument constructor, and how does `this(n, "ANY")` change the picture?

Read the worked answer

It increases exactly once per new Booking(3).

this(n, "ANY") is an explicit constructor invocation. It must be the first statement of the one-argument constructor. The JVM does not run both constructor bodies in sequence from the start: the one-argument constructor immediately delegates, the two-argument constructor runs super() implicitly, then executes its body (including bookings++), and only then does control return to the one-argument constructor, which continues after the this(...) call with any remaining statements.

So if bookings++ appears only in the two-argument constructor, new Booking(3) counts once, and new Booking(3, "PRO") also counts once - every instance increments the shared static field exactly once. If the increment were written in both constructors, new Booking(3) would count twice: once in the delegated constructor and once after returning to the one-argument body. That double counting is the real defect to look for.

Related rules worth checking: this(...) and super(...) cannot both appear in one constructor, neither may appear anywhere but the first statement, and a recursive this(...) cycle is a compile-time error. Because bookings is static, it belongs to the class, is shared by all instances, and must be reset via a static method - not per instance.