Typed Subsets of Enums

Let's say I have the following enum type:

 public enum Country { CHINA, JAPAN, FRANCE, // ... and all other countries AUSTRIA, POLAND; } 

Now I would like to create a subset of this enumeration, conceptually like:

 public enum EuropeanUnion constraints Country { FRANCE, // ... and all other countries within the European Union AUSTRIA, POLAND; } public enum LandlockedCountries constraints Country { // ... all landlocked countries AUSTRIA; } 

I would like to create subsets of type enum so that I can write methods like

 Set<Person> getNationalMembersOfEuropeanParliament(EuropeanUnion euCountry); 

Using the EuropeanUnion subtype for the euCountry parameter protects the user of the API from accidentally passing in an invalid country, for example. non-EU JAPAN .

Are there any ways to "limit" the range of valid enum values ​​so that a static type system can be used?

+5
source share
3 answers

You might want to use EnumSet ; this will not give you new types, but allows you to work with sets of enumeration values ​​in a set-theoretic way:

 public enum Country { CHINA, JAPAN, ...; public static final EnumSet<Country> EUROPEAN_UNION = EnumSet.of(Country.FRANCE, Country.AUSTRIA, ...); } 

EnumSet implements Set , so you can use, for example. addAll() , removeAll() and retainAll() to create joins, differences, and intersections (just remember to copy the left row first).

+3
source

enum is just syntactic sugar for a private constructor class, the only instances of which are stored in public static final fields. Having defined this manually, you can get additional flexibility, for example, the ability to create subclasses and implement interfaces. So, one of the possible solutions, but each of them requires one class, so think carefully before deciding to do this:

 public interface Country { static final France FRANCE = France.INSTANCE; static final Norway NORWAY = Norway.INSTANCE; static final Sweden SWEDEN = Sweden.INSTANCE; static final Denmark DENMARK = Denmark.INSTANCE; ... } public interface EuropeanUnionCountry extends Country { static final France FRANCE = France.INSTANCE; static final Sweden SWEDEN = Sweden.INSTANCE; static final Denmark DENMARK = Denmark.INSTANCE; ... } public interface ScandinavianCountry extends Country { static final Norway NORWAY = Norway.INSTANCE; static final Sweden SWEDEN = Sweden.INSTANCE; static final Denmark DENMARK = Denmark.INSTANCE; } // In case you need to store information about each country public class CountryBase implements Country { protected CountryBase() { } } public class France extends CountryBase implements EuropeanUnionCountry { public static final France INSTANCE = new France(); private France() { } } public class Norway extends CountryBase implements ScandinavianCountry { public static final Norway INSTANCE = new Norway(); private Norway() { } } public class Sweden extends CountryBase implements ScandinavianCountry, EuropeanUnionCountry { public static final Sweden INSTANCE = new Sweden(); private Sweden() { } } public class Denmark extends CountryBase implements ScandinavianCountry, EuropeanUnionCountry { public static final Denmark INSTANCE = new Denmark(); private Denmark() { } } 

The advantage of this solution is that you now have complete type safety: Country.NORWAY can be passed to a method that accepts a Country or ScandinavianCountry , but not one that accepts a EuropeanUnionCountry . In addition, Country.NORWAY == ScandinavianCountry.NORWAY . Please note that it would be enough to list all the countries in the Country , but repeating the corresponding sub-interfaces can make it easier to keep track of which category belongs to.

You can do this using only classes, but then you are limited by a tree hierarchy, thereby excluding, for example, those that have both the European Union and Scandinavia (since they only partially overlap).

+1
source

So we can define two independent enumeration types

 enum Country{ GREECE ... enum EU{ GREECE ... 

The question is how to link EU.GREECE with Country.GREECE . We do not need a link at compile time; we are satisfied if at run time we can convert EU.GREECE to Country.GREECE

  private EU() { this.country = Country.valueOf(this.name()); } 

This is not a completely safe type, but at least it does not work fast at runtime - if any name in the EU is not in the country, we will get an error at an early stage when the EU class is initialized.

Now, to further justify this project, we can argue that EU.GREECE should not be the same object as Country.GREECE, since EU.GREECE may contain more metadata and actions specific to the EU.

0
source

All Articles