Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Compiler Confusion With overloaded methods

Tags:

java

java-8

guava

While upgrading an app to Java 8 I ran into a weird issue with google guava's newArrayList in a couple of places.

Take a look at this example:

import com.google.common.collect.UnmodifiableIterator;

import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.BasicAttribute;
import java.util.ArrayList;

import static com.google.common.collect.Iterators.forEnumeration;
import static com.google.common.collect.Lists.newArrayList;

public class NewArrayListIssue {
    public static void main(String[] args) throws NamingException {

        UnmodifiableIterator<?> elements = forEnumeration(getEnumeration().getAll());
        System.out.println("declarefirst = " + newArrayList(elements)); // calls newArrayList(Iterator<? extends E> elements)

        ArrayList directCopy = newArrayList(forEnumeration(getEnumeration().getAll()));
        System.out.println("useDirectly = " + directCopy); //calls newArrayList(E... elements)
    }

    public static Attribute getEnumeration(){
        return new BasicAttribute("foo",1);
    }
}

In the first example when I get the UnmodifiableIterator first into its own variable and then call newArrayList I get what I expect, which is the Iterators values copied into a new List.

In the second example where the forEnumeration goes directly into the newArrayList method I get back a List with a containing the iterator (which contains the value).

According to Intellij it thinks that both method calls should be to newArrayList(Iterator<? extends E> elements) but I found when debugging that the second call actually goes to newArrayList(E... elements).

It only happens when I compile with the Oracle JDK8 targeted to Java8. If I target to 7 it works fine.

like image 412
ryber Avatar asked Jan 22 '15 00:01

ryber


2 Answers

The problem is that the compiler thinks that newArrayList(Iterator<? extends E>) is not applicable (maybe because of this bug) and then silently chooses the generic varargs method which is always applicable (which shows the danger of such overload), when you don’t use a specific element type for your result list.

The bug appears with wildcard types, i.e. in your code it’s Attribute.getAll() returning a NamingEnumeration<?> hence the result of forEnumeration is UnmodifiableIterator<?> which the compiler refuses to assign to Iterable<? extends E>, the parameter type of newArrayList. If you cast the return value of the inner call to Enumeration the problem disappears as it does when you cast the return value of the outer call to Iterator.

I don’t see a simple short-term solution for this problem. After all, I don’t understand why you didn’t use List<?> directCopy=Collections.list(getEnumeration().getAll()); in the first place…

Note that if you want to find all occurrences of this problem, you may simply use a patched version of guava in which newArrayList(E...) has been removed and check all compiler-errors (assuming you don’t have many case where you really want to call this overload). After rewriting the call sites, you may turn back to the original guava.

like image 181
Holger Avatar answered Nov 10 '22 22:11

Holger


I've seen this happen with overloaded methods and generic types. In this case the more generic version of newArrayList() is being chosen when the parameter is not explicitly typed.

I don't have a technical explanation for you, but I'd recommend you force usage of the desired method overload by casting:

ArrayList directCopy = newArrayList((Iterator)forEnumeration(getEnumeration().getAll()));
like image 34
gknicker Avatar answered Nov 10 '22 21:11

gknicker