Quiz Yourself: Creating and Invoking Overloaded Methods (Intermediate)

Overloaded methods must be valid, and all ambiguities must be resolved.

April 28, 2020 | Download a PDF of this article
More quiz questions available here

If you have worked on our quiz questions in the past, you know none of them is easy. They model the difficult questions from certification examinations. The “intermediate” and “advanced” designations refer to the exams rather than to the questions, although in almost all cases, “advanced” questions will be harder. We write questions for the certification exams, and we intend that the same rules apply: Take words at their face value and trust that the questions are not intended to deceive you but to straightforwardly test your knowledge of the ins and outs of the language.

The objective in this Java SE quiz is to create and invoke overloaded methods.

Given this class:


public class Logger {
    static void log(Object o)   { /* code */ }  // line n1
    static void log(Long[] lwa) { /* code */ }  // line n2
    
    public static void main(String[] args) {
        log(new Integer[]{});
        long[] arr = null;
        log(arr);
        log(null);
    }
}

Assuming any changes are done independently, which two statements are true? Choose two.

A. The code compiles successfully.
B. The code compiles if you add a new method: static void log(Integer[] iwa) { /* code */ }
C. The code compiles if you comment out line n1.
D. The code compiles if you comment out line n2.
 

Answer. Option A suggests the code compiles as it stands. For this to be true, the two overloaded methods must be valid overloads, and all the invocations must unambiguously resolve to one or the other of those overloads. Of course, there must be no other syntactic problems.

Regarding “other syntactic problems,” the code is good. For the question of the two overloaded methods, they do differ by the type sequence in the argument list. One is a single Object, and the other is an array. That’s sufficient difference to allow the overloads to coexist. The rules on this are spread across two locations.

The Java Language Specification section 8.4.9, “Overloading,” states that “If two methods of a class [...] have the same name but signatures that are not override-equivalent, then the method name is said to be overloaded.” The Java Language Specification section 8.4.2, “Method Signature,” describes override equivalence.

This description is less direct but may be paraphrased as follows: If the type sequences, after type-erasure of generic type parameters, are the same, then the methods are override-equivalent. In this case, there are no generics involved, and the types (Object and array-of-Long-wrapper) are definitely different, so the overloads are themselves valid.

The second point is that of unambiguous invocations of the overloaded methods. In this respect, too, the code is also valid, although a couple of details might not be entirely expected. Let’s look at which call invokes which method. For this, refer to the Java Language Specification section 15.12.2, “Compile-Time Step 2: Determine Method Signature,” which discusses overload resolution.

First, the invocation log(new Integer[]{}) cannot match the method that takes an array of Long, but instead it matches the Object argument type. You might think that because an int can be assigned to a long, this call might invoke the Long[] argument method. However, this is not so for two reasons. You should doubt this invocation because an Integer cannot be assigned to a Long, because they are sibling classes and no parent/child class relationship exists. However, the real reason is that although Java can make widening conversions of primitives, it cannot change the content types of arrays in this way.

Now, Java does not perform autoboxing for arrays. So in the second invocation, that of log(arr), once again the Object parameter method is selected.

Next, let’s consider the call to log(null). This is something of a special case and might be a surprise. The compiler searches the overloaded methods for those that accept the caller’s argument type (null in this case). Then, if several are found, the compiler looks for the one that is “most specific.” In this case, both methods fit (null can be any type except a primitive). However, the Long[] version is more specific and will be preferred over the Object argument. Remember that Object is the supertype of everything, so it is the most general form that can exist.

The bottom line, however, is that option A is correct; the code does compile as it stands.

Option B seeks to add a new method that takes an argument of type Integer[]. By itself, this is fine, and the call log(new Integer[]{}); now has an exactly matched target. However, the line log(null) will fail to compile because it now matches two target methods, both of which are equally specific. (Both are more specific than Object, but neither has any advantage over the other.) So, the compiler cannot choose between them, and it refuses to compile the resulting code.

It’s important to remember that overloaded methods can be legal declarations and yet cause calls to be ambiguous, and those calls will fail to compile. This can be particularly surprising when adding one new method makes a previously valid method call break. Therefore, option B is incorrect.

Option C suggests removing the log(Object o) method. If this is done, there is no valid target for the calls to log with the long[] and Integer[] arguments. Because those have no place to go, the change causes the code to fail to compile. From this, we can see that option C is incorrect.

Option D suggests commenting out the log(Long[] lwa) implementation. If this is done, all three invocations will target the log(Object o) method because any nonprimitive, and the null literal, can be assigned to java.lang.Object. In view of this, the code will compile after this change, and option D is correct.

The selection of an overloaded method is always done by the compiler based on the type of the arguments. The value of the argument, which might be null, is not significant. So, the following two invocations would reliably choose the same target method, even though a call to log(null) would perhaps invoke a different target.


long[] arr1 = null;
log(arr1);
long[] arr2 = new long[]{1L};
log(arr2);

The correct answers are options A and D.

Simon Roberts

Simon Roberts joined Sun Microsystems in time to teach Sun’s first Java classes in the UK. He created the Sun Certified Java Programmer and Sun Certified Java Developer exams. He wrote several Java certification guides and is currently a freelance educator who publishes recorded and live video training through Pearson InformIT (available direct and through the O’Reilly Safari Books Online service). He remains involved with Oracle’s Java certification projects.

Mikalai Zaikin

Mikalai Zaikin is a lead Java developer at IBA IT Park in Minsk, Belarus. During his career, he has helped Oracle with development of Java certification exams, and he has been a technical reviewer of several Java certification books, including three editions of the famous Sun Certified Programmer for Java study guides by Kathy Sierra and Bert Bates.

Share this Page