Reputation: 27832
Let's say I have the following enum
type:
public enum Country {
CHINA,
JAPAN,
FRANCE,
// ... and all other countries
AUSTRIA,
POLAND;
}
Now I would to create a subset of this enum, 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'd like to create subsets of an enum
type so that I can write methods such as
Set<Person> getNationalMembersOfEuropeanParliament(EuropeanUnion euCountry);
Using the subtype EuropeanUnion
for parameter euCountry
protects the API user from accidentally passing in an invalid country, e.g. the non-EU JAPAN
.
Are there any ways of "constraining" the range of valid enum values so that one can benefit from the static type system?
Upvotes: 11
Views: 5123
Reputation: 19682
Well, so we can define two independent enum types
enum Country{
GREECE
...
enum EU{
GREECE
...
The question is how to link EU.GREECE
to Country.GREECE
. We don't need the link at compile time; we are satisfied if at runtime we can convert
EU.GREECE
to Country.GREECE
private EU()
{
this.country = Country.valueOf(this.name());
}
This is not totally type safe, but at least it fails fast at runtime - if any name in EU is not in Country, we'll get an error early on when EU class is initialized.
Now, to further justify this design, we can argue that EU.GREECE should not be the same object as Country.GREECE anyway, because EU.GREECE may contain more metadata and actions that are specific to EU.
Upvotes: 0
Reputation: 37950
enum
is just syntactic sugar for a class with a private constructor whose only instances are stored in public static final
fields. By defining this manually instead, you can gain additional flexibility, such as the ability to create subclasses and implement interfaces. So here's one possible solution, but it requires one class per country, so think carefully before you decide 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 takes Country
or ScandinavianCountry
, but not to one that takes EuropeanUnionCountry
.
Also, Country.NORWAY == ScandinavianCountry.NORWAY
. Note that it would have been enough to list all the countries in Country
, but repeating the appropriate ones in the subinterfaces might make it easier to keep track of which belong to which category.
You can do this using only classes as well, but then you're limited to a tree-based hierarchy, thus precluding e.g. having both European Union and Scandinavia (since they only partly overlap).
Upvotes: 4
Reputation: 37950
You might want to consider using an EnumSet
; this won't give you new types, but it lets you work with collections of enum 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 e.g. addAll()
, removeAll()
and retainAll()
to produce unions, differences, and intersections (just remember to copy the left-hand-side set first).
Upvotes: 5