Search code examples
lambdajava-8javactype-inferenceecj

Invalid method reference / ambiguous reference (javac / ecj behaviour difference)


The following code compiles and runs correctly when using the Eclipse Compiler for Java.

package org.sandbox;

public final class ExceptionUtils
{
    private ExceptionUtils(){}

    @FunctionalInterface
    public interface Runnable
    {
        void run() throws Exception;
    }

    @FunctionalInterface
    public interface Callable<T>
    {
        T call() throws Exception;
    }

    public static void uncheck( final Runnable r )
    {
        try
        {
            r.run();
        }
        catch( final Exception e )
        {
            throw new RuntimeException( e );
        }
    }

    public static <T> T uncheck( final Callable<T> c )
    {
        try
        {
            return c.call();
        }
        catch( final Exception e )
        {
            throw new RuntimeException( e );
        }

    }
}

...

package org.sandbox;

import static org.sandbox.ExceptionUtils.uncheck;

public class Foo
{
    private String bar;

    public String getBar()
    {
        return bar;
    }

    public void setBar( final String bar )
    {
        this.bar = bar;
    }

    @Override
    public Foo clone()
    {
        return (Foo)uncheck( super::clone );
    }
}

When compiled using javac, the following errors are emitted:

org\sandbox\Foo.java:22: error: reference to uncheck is ambiguous
        return (Foo)uncheck( super::clone );
                    ^
  both method <T>uncheck(Callable<T>) in ExceptionUtils and method uncheck(Runnable) in ExceptionUtils match
  where T is a type-variable:
    T extends Object declared in method <T>uncheck(Callable<T>)
org\sandbox\Foo.java:22: error: incompatible types: cannot infer type-variable(s) T
        return (Foo)uncheck( super::clone );
                           ^
    (argument mismatch; invalid method reference
      clone() has protected access in Object)
  where T is a type-variable:
    T extends Object declared in method <T>uncheck(Callable<T>)
2 errors

It appears there are two issues here

  • a protected method cannot be used as a method reference
  • it's impossible to select the correct uncheck(...) method based solely on the return type (ie. either T or void)

The overall question is "why is there a behaviour difference?", but perhaps it can be broken down into:

  • Is javac being too strict with the method reference restriction, or is the Eclipse compiler being lax here?
  • Is javac correctly determining that the method resolution is ambiguous, or does it simply lack the smarts of the Eclipse compiler to correctly determine which method should be selected?

Solution

  • This is a known bug in javac compiler (see JDK-8139836) which is finally fixed in OpenJDK 1.9ea-b89. You may download the Java9 early access build and see that it compiles your code normally. This bug affects method references only. You can replace it with lambda expression. It's not much longer and compiles fine in both ECJ and javac:

    return (Foo)uncheck( () -> super.clone() );