Refactoring - generating a List of Strings from an Enum

Viewed 110

Method getContinent() expects a String as an argument which can have three possible values: "Africa", "Asia" and "Europe".

Depending on the given value I get, I have to match the appropriate enum and get a list of all its values (as String).

There are repetitions in my code. Is there a better way to do it? Is it a good idea to place this in each of the enum's?:

Stream.of(CountryAsia.values())
      .map(country -> country.getName())
      .collect(Collectors.toList());

My code

Main class:

public class Main {
    public static void main(String[] args) {
        String continent = getContinent() //one of three possible values: "Africa" or "Asia" or "Europe"
        List<String> list = null;

        switch(continent) {
            case "Africa":
                list = Stream.of(CountryAfrica.values())
                        .map(country -> country.getName())
                        .collect(Collectors.toList());
                break;
            case "Asia":
                list = Stream.of(CountryAsia.values())
                        .map(country -> country.getName())
                        .collect(Collectors.toList());
                break;
            case "Europe":
                list = Stream.of(CountryEurope.values())
                        .map(country -> country.getName())
                        .collect(Collectors.toList());
                break;
        }

        System.out.println(list);
        //next instructions..
    }
}

CountryEurope enum:

public enum CountryEurope {
    DENMARK("Denmark"),
    ESTONIA("Estonia"),
    ICELAND("Iceland");

    CountryEurope(String name) {
        this.name = name;
    }

    private final String name;

    public String getName() {
        return name;
    }
}

CountryAsia enum:

public enum CountryAsia {
    AFGHANISTAN("Afghanistan"),
    KAZAKHSTAN("Kazakhstan"),
    UNITED_ARAB_EMIRATES("United Arab Emirates"),
    UZBEKISTAN("Uzbekistan");

    CountryAsia(String name) {
        this.name = name;
    }

    private final String name;

    public String getName() {
        return name;
    }
}

CountryAfrica class:

public enum CountryAfrica {
    ALGIERIA("Algeria"),
    WESTERN_SAHARA("Western Sahara"),
    EGYPT("Egypt");

    CountryAfrica(String name) {
        this.name = name;
    }

    private final String name;

    public String getName() {
        return name;
    }
}
2 Answers

You can make use EnumSet.allOf() in order to get the names of your enum constants.

EnumSet.allOf() expects a Class<T> of enum as an argument and returns an EnumSet of its elements (which is special implementation of the Set designed exclusively for enums).

Then we can transform a Set of enum constants into a list of strings either by using a stream or a plain for loop.

And if you are using Java 14 +, you cane utilize switch expressions. Otherwise, replace the repeating stream statement with a method call.

In order to access the method getName() with every element of these enums regardless of its type, we can define an interface which all of them will implement. So that it'll serve as an entry-point to all country-enums.

public interface CountryData {
    String getName();
}

Also, it could contain some other methods related to the domain-specific information that could be useful in your application, like country area or locale tax rates (all this behavior will be accessible though the CountryData interface).

Combining all mentioned above will give a very concise and expressive code:

public static List<String> getCountries(String continent) {
    return switch(continent) {
        case "Africa" -> getEnumNames(CountryAfrica.class);
        case "Asia" -> getEnumNames(CountryAsia.class);
        case "Europe" -> getEnumNames(CountryEurope.class);
        default -> Collections.emptyList();
    };
}

public static <T extends Enum<T> & CountryData> List<String> getEnumNames(Class<T> enumClas) {
    return EnumSet.allOf(enumClas).stream()
            .map(CountryData::getName)
            .collect(Collectors.toList());
}

Method above declares a bounded type parameter <T extends Enum<T> & CountryData>, which implies any type T which is subtype of both Enum class and interface CountryData.

A type variable with multiple bounds is a subtype of all the types listed in the bound. If one of the bounds is a class, it must be specified first.

for information on the syntax of generic methods, take a look at this tutorial

main()

public static void main(String[] args) {
    System.out.println(getCountries("Europe"));
    System.out.println(getCountries("Africa"));
    System.out.println(getCountries("Asia"));
}

Output

[Denmark, Estonia, Iceland]
[Algeria, Western Sahara, Egypt]
[Afghanistan, Kazakhstan, United Arab Emirates, Uzbekistan]

Is it a good idea to place this in each of the enum's?

No, it's not because:

  • the code it will not eliminate the duplication, the same lines will be scattered across multiple files;
  • this code ins't specific to any of your enums therefore it must reside in one place (in a class that is meant to process this list).

Looks like you try to store some collection of data inside enum that is a bit wrong IMHO. I would prefer to provide a map where the key is "continent" and the value is a collection of countries. Map<String, Set>.

Then, you don't need to use the switch, you can use Map#get().

Related