8

I used to think about java method references as a syntactic sugar which was introduced as an addition to lambda expressions. But apparently it is not the case.

In the example below method reference, unlike lambda expression, produces an error.
Could someone please explain that weird behavior?

import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

    public static void main(String[] args) {
        System.out.println(getMapUsingLanmdaApproach(MyEnum.class)); // works as expected: {1=A, 2=B}
        System.out.println(getMapUsingMethodReferenceApproach(MyEnum.class));   // throws java.lang.BootstrapMethodError
    }

    static <K, V extends Enum<?> & HasProperty<K>> Map<K, V> getMapUsingLanmdaApproach(Class<V> aClass) {
        return Stream.of(aClass.getEnumConstants())
                .collect(Collectors.toMap(e -> e.getProperty(), Function.identity()));
    }

    static <K, V extends Enum<?> & HasProperty<K>> Map<K, V> getMapUsingMethodReferenceApproach(Class<V> aClass) {
        return Stream.of(aClass.getEnumConstants())
                .collect(Collectors.toMap(HasProperty::getProperty, Function.identity()));
    }
}

enum MyEnum implements HasProperty<Integer> {
    A(1),
    B(2);

    private final Integer property;

    MyEnum(Integer property) {
        this.property = property;
    }

    @Override
    public Integer getProperty() {
        return property;
    }
}

@FunctionalInterface
interface HasProperty<K> {
    K getProperty();
}

Result:

{1=A, 2=B}
Exception in thread "main" java.lang.BootstrapMethodError: call site initialization exception
    at java.lang.invoke.CallSite.makeSite(CallSite.java:341)
    at java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:307)
    at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:297)
    at Main.getMapUsingMethodReferenceApproach(Main.java:19)
    at Main.main(Main.java:10)
Caused by: java.lang.invoke.LambdaConversionException: Invalid receiver type class java.lang.Enum; not a subtype of implementation type interface HasProperty
    at java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:233)
    at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:303)
    at java.lang.invoke.CallSite.makeSite(CallSite.java:302)
    ... 4 more

I was running the example on the next java versions:
1.8.0_101-b13
1.8.0_131-b11

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
Artem Petrov
  • 772
  • 4
  • 17
  • What if you declare a variable `Function func = HasProperty::getProperty;`? – Andy Turner Jul 03 '17 at 11:44
  • Actually, you'd need to declare it as `Function, K> func = HasProperty::getProperty;`. – Andy Turner Jul 03 '17 at 11:48
  • If I extract reference function into a variable (as was proposed by @AndyTurner: Function, K> func = HasProperty::getProperty) it actually works. But still it looks for me as some kind of magic. I don't understand mechanisms that are behind this behavior. – Artem Petrov Jul 03 '17 at 12:28
  • 2
    I think this will be buried in a deep dark corner of JLS. BTW, this question is an excellent example of a [mcve]. I wish more new folks could right questions like this! – Andy Turner Jul 03 '17 at 12:32

0 Answers0