The Meter That Leaked Its Array and Boxed Its Arguments
A class Meter has a private final int[] samples, a constructor that assigns the incoming array reference directly, and a getter public int[] getSamples() { return samples; }. A caller runs int[] s = m.getSamples(); s[0] = 99; and the meter's reading changes although no setter exists. The same class overloads log(int), log(long) and log(Integer). Calls log(5), log(5L) and log(Integer.valueOf(5)) behave as expected, but after log(int) is deleted, log(5) still compiles and silently invokes the long version. A helper void raise(int count) { count++; } never changes the caller's variable, which the author reads as a broken method.
1. Diagnose the encapsulation failure and rewrite the constructor and getter. Does `private final` prevent the problem?
Read the worked answer
The field is private, but the references to the internal array escape the class twice: once through the constructor, which stores the caller's array, and once through the getter, which hands out the internal array. Anyone holding either reference can mutate elements at will. final only prevents reassigning the field to a different array object; it does nothing about the contents of the array.
The fix is defensive copying:
private final int[] samples;
public Meter(int[] samples) {
this.samples = Arrays.copyOf(samples, samples.length);
}
public int[] getSamples() {
return Arrays.copyOf(samples, samples.length);
}
Now external code can never reach the internal array. The cost is one copy per access, which is the standard trade-off; an alternative is to expose read-only accessors such as sampleAt(int) and size(). Note that Arrays.copyOf on an array of a reference type is a shallow copy - mutable elements would still be shared, and would need copying individually.
2. Explain why `log(5)` silently switches to `log(long)` after `log(int)` is deleted, and predict what `log(null)` does when only `log(long)` and `log(Integer)` remain.
Read the worked answer
Overload resolution runs in phases, each strictly before the next: (1) applicable by strict invocation - no boxing, no varargs; (2) applicable by widening/boxing; (3) applicable by varargs. In phase 1, the argument 5 is an int: log(int) matches exactly, and log(long) matches by widening primitive conversion. With log(int) present, the exact match wins. With it deleted, log(long) is still a phase-1 match (widening is permitted there), so it is chosen before boxing to Integer is even considered. The code compiles and behaviour changes silently - a real hazard when refactoring overload sets, because widening conversions are invisible at the call site. Integer.valueOf(5) matches the Integer overload exactly, and 5L matches long exactly - the latter is an instance method call, so null is a legal argument at compile time.
log(null) with only log(long) and log(Integer) resolves to log(Integer): null is assignable to the reference type but not to long, so Integer is the only applicable method. If both log(Integer) and log(Number) existed, the more specific Integer would win. If two unrelated reference overloads applied, the call would be ambiguous and fail to compile. The body will then dereference null and throw NullPointerException unless guarded.
3. Is `raise` broken? Explain what happens to `count` and give two redesigns that actually propagate the change.
Read the worked answer
raise is doing exactly what Java specifies, so the code is correct and the expectation is wrong. Arguments are passed by value: the caller's int is copied into the parameter, count++ updates the copy, and the copy dies when the method returns. Because the parameter is a primitive, there is no shared object to mutate.
Redesigns:
static int raised(int count) { return count + 1; }
count = raised(count);
or move the state onto an object and mutate the object's field through a setter, or pass a mutable holder and change its contents. Wrapping the value in an Integer would not work either - Integer is immutable, so count++ would just rebind the parameter to a new boxed object.
The same reasoning explains why a method can mutate the elements of an array or the fields of an object passed to it (it gets a copy of the reference) but cannot reassign the caller's reference variable itself.