Check enum valueOf in Java Optional mapping pipeline

Viewed 545

I have the following method and at here I first get name of exportProductType and then get the value of that name. If name or the valueOf(name) is null, then throw exception.

public static ProductType getExportProductType(ExportProductType exportProductType) {
    return Optional.ofNullable(exportProductType)
            .map(ExportProductType::name)
            .map(ProductType::valueOf)
            .orElseThrow(() -> new ProductTypeNotFoundException());
}

Everything is ok and works without any problem. However, when I pass exportProductType enum value that is not available in ProductType enums, I cannot catch it and it throws another exception "No enum constant...". So, I think I need to check if ProductType::valueOf is null before .map(ProductType::valueOf). How should I check it? Using a boolean or filter? Or another way that is proper for stream() in Java?

I think something like that after .map(ExportProductType::name):

.filter(result -> ProductType::valueOf(result) != null)


Update: I found a proper way as shown below:

EnumUtils.isValidEnum(ProductType.class, exportProductType.name());

I just need to use it in stream(). So, is it possible to make the same check in a stream()? Is this ok just below the .map(ExportProductType::name) line?

.filter(x -> EnumUtils.isValidEnum(ProductType.class, x))

2 Answers

As mentioned in the comments, the IllegalArgumentException by valueOf would have to be handled, so for example, you can instead define your own value lookup method that would return an empty Optional instead of throwing an exception:

enum ProductType {
    ...
    
    static Optional<ProductType> nullableValueOf(String name) {
        try {
            return Optional.of(ProductType.valueOf(name));
        } catch (IllegalArgumentException ex) {
            return Optional.empty();
        }
   }
}

public static ProductType getExportProductType(ExportProductType exportProductType) throws ProductTypeNotFoundException {
    return Optional.ofNullable(exportProductType)
                   .map(ExportProductType::name)
                   .flatMap(ProductType::nullableValueOf)
                   .orElseThrow(() -> new ProductTypeNotFoundException());
}

EDIT:

While you're at using Optional already, it would be cleaner to also use it as the return type:

public static Optional<ProductType> getExportProductType(ExportProductType exportProductType) throws ProductTypeNotFoundException {
    return Optional.ofNullable(exportProductType)
                   .map(ExportProductType::name)
                   .flatMap(ProductType::nullableValueOf);
}

Libraries like Vavr implement functional helpers like:

static <A, T> Function<A, Optional<T>> Try(Function<A, T> f) {
    return a -> {
        try {
            return Optional.of(f.apply(a));
        } catch (Exception ex) {
            return Optional.empty();
        }
    };
}

wrapping the exception into a pure functional type. With this, you can map, filter, etc... invalid states:

enum EnumX { Foo, Bar }

Stream
        .of("Foo", "Bar", "Par")
        .map(Try(EnumX::valueOf))
        .forEach(System.out::println);

Stream
        .of("Foo", "Bar", "Par")
        .map(Try(EnumX::valueOf))
        .filter(Optional::isPresent)
        .map(Optional::get)
        .forEach(System.out::println);

with output

Optional[Foo]
Optional[Bar]
Optional.empty
Foo
Bar

NOTE: full running example code here

import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

public class EnumStreamCheck {

    static <A, T> Function<A, Optional<T>> Try(Function<A, T> f) {
        return a -> {
            try {
                return Optional.of(f.apply(a));
            } catch (Exception ex) {
                return Optional.empty();
            }
        };
    }

    public static void main(String... args) {

        Stream
                .of("Foo", "Bar", "Par")
                .map(Try(EnumX::valueOf))
                .forEach(System.out::println);

        Stream
                .of("Foo", "Bar", "Par")
                .map(Try(EnumX::valueOf))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .forEach(System.out::println);
    }

    enum EnumX {
        Foo, Bar
    }
}
Related