2

i have an enum implementing a supplier, e.g.:

public enum QUERY_FIELD implements Supplier<String> {

    PRODUCT_TYPE("ProductType"),
    MIN_NUMBER_OF_PARTS("MinNumberOfParts"),
    MAX_NUMBER_OF_PARTS("MaxNumberOfParts");

    private final String id;

    QUERY_FIELD(final String id) {
        this.id = id;
    }

    @Override
    public final String get() {
        return id;
    }
}

i have different utility methods which can find the enum i search for depending on the query

public static <T extends Enum<T> & Supplier<String>> Optional<T> findById(final Class<T> enumClass, final String id) {

    return Arrays.stream(enumClass.getEnumConstants()).filter(p -> id.equalsIgnoreCase(p.get())).findFirst();
}

public static <T extends Enum<T> & Supplier<? extends Number>> Optional<T> findById(final Class<T> enumClass, final Number id) {

    return Arrays.stream(enumClass.getEnumConstants()).filter(p -> id.equals(p.get())).findFirst();
}

now i want to adapt this idea to create a utility method that just returns the list of all values depending on the suppliers type.

i tried it with:

public static <T extends Enum<T> & Supplier<? extends String>> List<String> getValueList(final Class<T> enumClass) {
    return Arrays.stream(enumClass.getEnumConstants()).map(Supplier::get).collect(Collectors.toList());
}

or

public static <U, T extends Enum<T> & Supplier<? extends U>> List<U> getValueList(final Class<T> enumClass) {
    return Arrays.stream(enumClass.getEnumConstants()).map(Supplier::get).collect(Collectors.toList());
}

which both compile but do not work, how should i build this method?

Xtroce
  • 1,749
  • 3
  • 19
  • 43
  • 4
    What do you mean with "do not work"? What output did you expect and what are you getting instead? – Jesper Oct 03 '17 at 11:46
  • Why don't you just name your enum instances `ProductType`, `MinNumberOfParts` etc, then you can use `values()` and `.name()` etc – Bohemian Oct 03 '17 at 12:46
  • Just added and answer that I believe fixes your problem. In order to find the reason for it I had to copy your code (with some mods) and get the run-time stack trace. Then I just needed to google the exception class and error message to find the solution. Saying this just to point out how important is to include in your question/post the error message that you are getting rather that just say it doesn't work. – Valentin Ruano Oct 03 '17 at 17:30

1 Answers1

3

I guess that the problem you are experiencing in the runtime issue described in Enum, interfaces and (Java 8) lambdas: code compiles but fails at runtime; is this expected? and is cause by a known bug in Java-8 https://bugs.openjdk.java.net/browse/JDK-8141508.

The problem is with the intersection bound Enum<T> & Supplier<? extends String>. It seems that the compiler generates code where the second element in the intersection is lost and so in run-time it is as if you tried to use for the lambda an object of a class that is not guaranteed to implement Supplier but just Enum.

Since getEnumConstant is available for all Class<X> regardless wether X is an enum or not you could simply discard that part from the param-type bound and leave it as Supplier<String>:

import java.util.function.*;
import java.util.*;
import java.util.stream.*;

enum Test implements Supplier<String> {
   A, B, C;

   public String get() { return name(); }
}

class Main {

    public static <T extends Supplier<String>> List<String> getValueList(final Class<T> enumClass) {
        return Arrays.stream(enumClass.getEnumConstants())
                     .map(Supplier::get)
                     .collect(Collectors.toList());
    }

    public static final void main(String[] args) {
        System.out.println(getValueList(Test.class).stream()
                  .collect(Collectors.joining(",")));
    }
}

However the downside of this is that the compiler won't fail if someone provide a class that is not an enum.... in order to prevent that you can keep the intersection bound and add an extra map that explicitly cast the enum constants into suppliers:

    public static <T extends Enum<T> & Supplier<String>> List<String> getValueList(final Class<T> enumClass) {
            return Arrays.stream(enumClass.getEnumConstants())
                         .map(x -> (Supplier<String>) x) // not needed to compile but needed for run-time.
                         .map(Supplier::get)
                         .collect(Collectors.toList());
    }

UPDATE

There is a even better solution for the second alternative. You can do the cast just one outside the stream, that should save you compute:

    public static <T extends Enum<T> & Supplier<String>> List<String> getValueList(final Class<T> enumClass) {
            final Class<Supplier<String>> asSupplierClass = enumClass;
            return Arrays.stream(asSupplierClass.getEnumConstants())
                         .map(Supplier::get)
                         .collect(Collectors.toList());
    }

I have not tested it, let me know whether it works.

Valentin Ruano
  • 2,726
  • 19
  • 29