0

I need to check a list of Strings to see if they exist in an enum. I'm aware of this SO question, but here I need to record the discrepancies, to show them to the user. The following works, but is there a better way?

import okhttp3.Protocol;  // the enum

        List<String> foo = List.of("HTTP_1_0", "NOT_HTTP_1_1", "BOGUS");
        List<String> invalid = new ArrayList<>(List.of());
        foo.forEach(f -> {
            if (!Stream.of(Protocol.values()).anyMatch(v -> v.name().equals(f))) {
                invalid.add(f);
            }});
        System.out.println(invalid); // [NOT_HTTP_1_1, BOGUS]

mellow-yellow
  • 1,670
  • 1
  • 18
  • 38
  • There is a EnumUtils.isValidEnum among answers in the SO question you mentioned. Pretty short validation. – Danny Briskin Mar 09 '23 at 02:48
  • Most alternatives would be pretty similar to what you’ve done. One thing you can do to speed it up is keep a static Set of the valid names: `private static final Set VALID_PROTOCOL_NAMES = EnumSet.allOf(Protocol.class).stream().map(Enum::name).collect(Collectors.toUnmodifiableSet());` Then your check is simply `List invalid = new ArrayList<>(foo); invalid.removeAll(VALID_PROTOCOL_NAMES);` – VGR Mar 09 '23 at 03:11

2 Answers2

2

tl;dr

For:

enum Animal { DOG , CAT }

… we can filter out matches from list of possible enum names:

List < String > names = Arrays.stream( Animal.values() ).map( Animal :: name ).toList() ;
List < String > possibleNames = List.of( "DOG" , "CAT" , "BIRD" );

List < String > invalid = new ArrayList <>( possibleNames );
invalid.removeAll( names );

When run:

names.toString() = [DOG, CAT]
possibleNames.toString() = [DOG, CAT, BIRD]
invalid.toString() = [BIRD]

Details

Your code looks good enough. But I suppose we could make the code briefer, and optimize a bit.

Let's use enum Animal { DOG , CAT } as an example.

Keep enum names for repeated use

Get a list of the enum names, a list of strings.

Keep this list of names around if you will be doing multiple match attempts.

We sort for (a) perusing by humans, and (b) possible optimization discussed below.

List < String > names = Arrays.stream( Animal.values() ).map( Animal :: name ).sorted().toList() ;

Now search for matches.

boolean hit = names.contains( someName ) ;
boolean isInvalid = ( ! names.contains( someName ) ) ;

Optimizing for performance

If you had a very long list, and made many searches, you could optimize with a binary search. This would require the names to be in sorted order, thus our .sorted() call in code above.

In milder situations, I would not bother with the binary search. Likely a premature optimization.

boolean hit = ( Collections.binarySearch( names , someName ) >= 0 ) ;
boolean isInvalid = ( Collections.binarySearch( names , someName ) < 0 ) ;

Multiple matches

For a list of names to be matched, use List :: removeAll.

First, make a copy of the possible names. Then, from that copy remove the known names. Any remaining are invalid.

List < String > invalid = new ArrayList <>( possibleNames );
invalid.removeAll( names );

Apache Commons Lang

The Apache Commons Lang library offers two methods:

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
1

Since your use case is presenting a small selection of data to a user, who won't notice the fraction of a millisecond one solution takes over another, you won't find anything materially better than what you've shown. If it works, and it's easy for the next developer to understand what you did, then you did it right.

If you have a significant volume of comparisons to the same enum, you could convert it to a Set for O(1) comparisons and filter by whatever's not in the set.

static Set<String> cachedDOW=new HashSet<>();
Arrays.stream(DayOfWeek.values()).forEach(v->dow.add(v.toString()));

List<String> foo = Arrays.asList("SUNDAY", "HUMPDAY", "SATURDAY");
System.out.println(foo.stream().filter(a->!cachedDOW.contains(a)).collect(Collectors.toList()));
phatfingers
  • 9,770
  • 3
  • 30
  • 44