How to know which element matched in java-8 anymatch?

Viewed 4276

Assuming I have an enum

enum Country {
    China, 
    USA, 
    Others
}

Say I have a list1 = ["China", "Shanghai", "Beijing"] and a check for isChina, if true, then return Country.China.

list2 = ["USA", "Dallas", "Seattle"] and a method checks for isUSA, if true then return Country.USA.

If USA or China is missing from the list, then return Country.Others.

Assumption: A list will always contain only 1 country followed by cities in that country.

[Edit] Do not assume, country would be first element in the array.

While I find it extremely easy to implement in Java-7, I am not sure of the most elegant way to do this using streams

for (String str: list) {
   if (str.equals("China")) {
       return Country.China  
   } 
   if (str.equals("USA")) {
       return Country.USA; 
   }
}
return Country.Other;

I am looking for a clean implementation using Streams.

6 Answers

Use a filter operation for the conditions and findFirst to get the first match, then transform to the respective Country.China or Country.USA otherwise return Country.Others if there was not match.

 return list.stream()
            .filter(str -> "China".equals(str) || "USA".equals(str))
            .findFirst()
            .map(s -> "China".equals(s) ? Country.China : Country.USA)
            .orElse(Country.Others);

Alternatively:

return Stream.of(Country.values()) // or Arrays.stream(Country.values())
             .filter(o -> list.contains(o.toString()))
             .findFirst().orElse(Country.Others);

The first solution will always check for "China" before checking for "USA" which is the exact equivalent of your imperative approach, However, the latter doesn't always respect that order, rather it depends on the order of the enum constants declarations in Country.

If the string matches the enum token then it's pretty simple:

return Arrays.stream(Country.values())
    .filter(c -> c.name().equals(list.get(0))
    .findAny().orElse(Country.Others);

This is assuming the first element of the list is the name as you specified.

A simpler solution using the ternary operators would be :

Country searchCountry(List<String> list) {
    return list.contains("China") ? Country.China : 
            list.contains("USA") ? Country.USA : Country.Others;
}
Country findCountry(List<String> countries) {
  return Arrays.stream(Country.values())
    .filter(country -> countries.contains(country.name()))
    .findAny()
    .orElse(Country.Others);
}

As you said, the Country is always followed by cities in said country, which means that the first element must be the country. From that we can define an easy helper method inside the enum, and we're done:

public static Country valueOf(String value){
    for(Country country : values()){
        if(country.name().equals(value)){
            return country;
        }
    }
    return Country.Others;
}

you then can just call that mehtod with:

Country country = Country.valueOf(list1.get(0));

No need to use Java8.

If this assumption holds:

A list will always contain only 1 country followed by cities in that country.

Then, it would be enough to compare the first element of the list and return the enum element that matches, otherwise fallback to Country.Others.

So, according to the problem statement, there's no need to iterate the list, hence no need to use streams at all.

Your Java 7 code is not the best either, because it iterates the list. If you want a one-liner, the ternary operator would do it:

return "China".equals(list.get(0)) ? Country.China :
       "USA".equals(list.get(0))   ? Country.USA   :
                                     Contry.Others;

I would even factor out list.get(0):

String first = list.get(0);
return "China".equals(first) ? Country.China :
       "USA".equals(first)   ? Country.USA   :
                               Contry.Others;
Related