javagenericsjava-11overload-resolutionstatic-polymorphism

Java static polymorphism (overloading) and inheritance between generics


Java 11 (may be irrelevant):

public static String toString(Object obj) {
    return ReflectionToStringBuilder.toString(obj, ToStringStyle.SHORT_PREFIX_STYLE);
}

public static String toString(Collection<Object> collection) {
    return collection.stream()
            .map(SaLogUtils::toString)
            .collect(Collectors.joining(", ", "[", "]"));
}

public static void main(String[] args) {
    List<Integer> list = List.of(Integer.valueOf(1));
    System.out.println(SaLogUtils.toString(list));
    System.out.println(SaLogUtils.toString(List.of(Integer.valueOf(1))));
}

Surprising output:

// from toString(Object)
ImmutableCollections.List12[e0=1,e1=<null>]
// from toString(Collection<Object>)
[Integer[value=1]]

Why does Java statically choose different methods?


Solution

  • When there are multiple overloads which could be invoked, Java chooses the most specific applicable method:

    The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error. In cases such as an explicitly typed lambda expression argument (§15.27.1) or a variable arity invocation (§15.12.2.4), some flexibility is allowed to adapt one signature to the other.


    toString(Collection<Object>) isn't applicable for a List<Integer>, because a List<Integer> isn't a List<Object>, so it's not a Collection<Object> either. As such, only the toString(Object) method is applicable, so that's the one that is invoked.


    toString(Collection<Object>) is applicable for List.of(someInteger) because that List.of is a polyexpression: it could be List<Integer>, it could be List<Object>, it could be List<Serializable>.

    Since both toString(Object) and toString(Collection<Object>) are applicable, it has to choose one or the other (or declare it ambiguous). The Collection overload is more specific because:

    This makes the toString(Collection<Object>) more specific, so this is the one that is chosen.