Difficulty: Advanced
Predict the output of these snippets involving field hiding, static method hiding, and overloaded methods with primitives, wrappers and varargs.
Output-prediction questions are the favourite weapon of campus recruiters, because one small snippet tests several concepts at once. The trick is to slow down and apply the rules in a fixed order rather than trusting your intuition. Let me give you the checklist first.
Rule one: instance methods are resolved by the runtime type of the object (dynamic dispatch). Rule two: fields, static methods, and private methods are resolved by the declared type of the reference (static binding). Rule three: overloads are chosen at compile time from the static types of the arguments. Rule four: when several overloads apply, Java runs its selection in phases: first without boxing or varargs (identity, widening), then with boxing and unboxing, and only last with varargs; the most specific candidate wins within a phase.
Now the first snippet. Class P has a field name = "P", a method get() returning name, and a static method who(). Class C extends P, declares its own name = "C", overrides get(), and hides who(). We write P p = new C(). Then p.name prints P, because field access uses the reference type. p.get() prints C, because the overridden method runs on the C object, and inside C.get(), name refers to C's field. p.who() prints P.who, because static methods are bound to the reference type, and although calling static methods through an instance compiles, it is misleading. Finally, ((C) p).name prints C since after the cast the reference type is C. So the four lines are P, C, P.who, C.
The second snippet overloads f with int, long, Integer, Object, and int varargs. Passing a byte variable picks f(int), because in phase one byte widens to int and long, and int is more specific than long. Passing the literal 5 picks int for the same reason. Passing 5L picks long: exact match. Passing Integer.valueOf(5) picks Integer, because both Integer and Object apply by subtyping in phase one and Integer is more specific. Passing "s" picks Object, since String is not an Integer. Passing char 'a' picks int by widening. Calling f() with no arguments is only applicable via varargs, so it prints varargs.
Notice how frequently boxing, widening, and specificity interplay. A useful memory aid: widening beats boxing, boxing beats varargs. A related trap is that int widened to long is preferred over boxing into Integer, but you cannot widen and then box: passing an int to a method taking Long is a compile error, while passing it to one taking Object is fine, via boxing to Integer.
When you answer in an interview, speak your reasoning aloud. Say "the reference type is P, so the field lookup is static". Even if you slip on one line, showing the systematic method earns full marks in most rounds.
class P {
String name = "P";
String get() { return name; }
static String who() { return "P.who"; }
}
class C extends P {
String name = "C";
@Override String get() { return name; }
static String who() { return "C.who"; }
}
public class HidingQuiz {
public static void main(String[] args) {
P p = new C();
System.out.println(p.name);
System.out.println(p.get());
System.out.println(p.who());
System.out.println(((C) p).name);
}
}
The field is picked by reference type, the overridden method by object type, and the static method by class.
public class OverloadQuiz {
static void f(int x) { System.out.println("int"); }
static void f(long x) { System.out.println("long"); }
static void f(Integer x) { System.out.println("Integer"); }
static void f(Object x) { System.out.println("Object"); }
static void f(int... x) { System.out.println("varargs"); }
public static void main(String[] args) {
byte b = 1;
f(b);
f(5);
f(5L);
f(Integer.valueOf(5));
f("s");
f('a');
f();
}
}
field-hiding, static-hiding, overload-resolution, output-questions