-1

I have stumbled into this by accident. Here is the short story. In some code-base there are a bunch of enums that follow the same pattern, here is one that is greatly simplified:

enum Letters {
    A("a"),
    Z("z");

    private String value;

    Letters(String value) {
        this.value = value;
    }

    private static final Map<String, Letters> MAP;

    public String getValue() {
        return value;
    }

    static {
        MAP = Arrays.stream(values())
                    .collect(Collectors.collectingAndThen(
                        Collectors.toMap(
                            Letters::getValue,
                            Function.identity()
                        ),
                        ImmutableMap::copyOf
                    ));
    }

    public static Optional<Letters> fromValue(String value) {
        return Optional.ofNullable(MAP.get(value));
    }
}

This is a pattern across tens of Enums, literally, and no this can't be changed. I thought about changing this code duplication, since it's a lot of it.

My idea was to start with an enum interface, that all will implement:

interface GetValueInterface {
    String getValue();
}

And some common code that will handle this MAP creation (fromValue would be handled there too, but let's put that aside). So, something like this:

 static class EnumsCommon {

    public static <T extends Enum<T> & GetValueInterface> Map<String, T> getAsMap(Class<T> clazz) {
        T[] values = clazz.getEnumConstants();

        return Arrays.stream(values)
                     .collect(Collectors.collectingAndThen(
                         Collectors.toMap(
                             T::getValue,
                             Function.identity()
                         ),
                         ImmutableMap::copyOf
                     ));
    }
}

So the code looks like this now:

enum Letters implements GetValueInterface {

    // everything else stays the same
    static {
        MAP = EnumsCommon.getAsMap(Letters.class);
    }
}

This compiles just fine, but when I run it under java-8, there is an exception thrown:

Caused by: java.lang.invoke.LambdaConversionException: Invalid receiver type class java.lang.Enum; not a subtype of implementation type interface GetValueInterface

Running the same code under java-11 works just fine...

Naman
  • 27,789
  • 26
  • 218
  • 353
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 3
    Possibly a duplicate of [LambdaConversionException with generics: JVM bug?](https://stackoverflow.com/questions/27031244/lambdaconversionexception-with-generics-jvm-bug) or [BootstrapMethodError caused by LambdaConversionException caused by using MethodHandle::invokeExact as a method reference](https://stackoverflow.com/questions/27394032/bootstrapmethoderror-caused-by-lambdaconversionexception-caused-by-using-methodh)? Well explained by Holger in both the references. – Naman Dec 21 '18 at 01:20
  • Why `T::getValue` instead of just `GetValueInterface::getValue`? – fps Dec 21 '18 at 12:58
  • @FedericoPeraltaSchaffner I initially tried that also, with the same result though – Eugene Dec 21 '18 at 13:01
  • OK, seems it was a type inference bug that has been fixed. The solution (as you've shown in your self answer) is to not use a method reference and provide an argument with an explicit type to the lambda expression. – fps Dec 21 '18 at 13:05
  • @FedericoPeraltaSchaffner I don't think it was a type inference bug btw, but more of what gets erased and how, it should have been erased to `GetValueInterface`, instead it is to an enum, if there would be two interfaces, simply swapping them would resolve it too, it seems – Eugene Dec 21 '18 at 13:07

1 Answers1

2

After preparing this question for 20 minutes, I did a google search and found the issue immediately :| this one

The fix is easy, just use a lambda expression:

(T t) -> t.getValue() // instead of T::getValue
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • If you still want to use a method reference, you have to use `(Function)GetValueInterface::getValue`. It will be accepted by the `toMap` collector, due to the PECS rule, but has no relationship to `T` nor `Enum` anymore. That’s a bit like in [this Q&A](https://stackoverflow.com/q/33929304/2711488). By the way, you may consider using a [`ClassValue`](https://docs.oracle.com/javase/8/docs/api/?java/lang/ClassValue.html) instance in the interface for caching, instead of having a field in every implementation class. – Holger Dec 21 '18 at 17:01
  • @Holger unfortunately I did not understand the `ClassValue` proposal, it's actually first time I heard about it – Eugene Dec 21 '18 at 19:04