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?

3
  • 4
    What do you mean with "do not work"? What output did you expect and what are you getting instead? Commented Oct 3, 2017 at 11:46
  • Why don't you just name your enum instances ProductType, MinNumberOfParts etc, then you can use values() and .name() etc Commented Oct 3, 2017 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. Commented Oct 3, 2017 at 17:30

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.