I need to create an enum with about 300 values and have the ability to get its value by id (int). I currently have this:
public enum Country {
DE(1), US(2), UK(3);
private int id;
private static Map<Integer, Country> idToCountry = new HashMap<>();
static {
for (Country c : Country.values()) {
idToCountry.put(c.id, c);
}
}
Country(int id) {
this.id = id;
}
public static Country getById(int id) {
return idToCountry.get(id);
}
}
That enum is going to be used a lot, so I'm wondering if this is the best possible solution performance-wise.
I've read http://docs.oracle.com/javase/1.5.0/docs/guide/language/enums.html over and over again, but couldn't find the part that describes at what time the
static {
}
block is called, and if it is guaranted that this will be called only once. So - is it?
Numeric enums are number-based enums i.e. they store string values as numbers. Enums are always assigned numeric values when they are stored. The first value always takes the numeric value of 0, while the other values in the enum are incremented by 1.
Get the value of an EnumTo get the value of enum we can simply typecast it to its type. In the first example, the default type is int so we have to typecast it to int. Also, we can get the string value of that enum by using the ToString() method as below.
In case if the first country id is 0
and ids are incremented by 1
, you may use the next approach:
Enum.values()
returns elements in the same order as they declared in enum. But it should be cached, as it creates new array every time it is invoked.Please, see the code below:
enum Country {
A, B, C, D, E;
private static final Country[] values = Country.values();
public static Country getById(int id) {
return values[id];
}
}
UPDATE: To get Country's id, ordinal()
method should be used. And to make getting id code clearer, the next method can be added to the enum:
public int getId() {
return ordinal();
}
Static initializer blocks are called once when the class is initialized. It's not guaranteed to be called once, but it will be unless you're doing something exotic with class loaders.
So, your approach is probably fine from a performance perspective. The only changes I'd propose would be to make your fields final
.
An alternative way to represent the mapping could be to store elements in an array (or a list):
Country[] countries = new Countries[maxId + 1];
for (Country country : Country.values()) {
countries[country.id] = country;
}
You could then look them up by element index:
System.out.println(countries[1]); // DE.
This avoids the performance penalty of having to box the id
in order to call idToCountry.get(Integer)
.
This of course requires you to have non-negative IDs (and ideally the IDs would be reasonably contiguous, to avoid having to store large runs of null
between countries).
First you don't need to have a static block to create the map. You can just add your code to constructor where each component adds itself to your map. Enum is ALWAYS a sigleton so your constructor is guaranteed to be called only once (per a enum value) Also you don't need to even have ID as Enum has method public final int ordinal() that returns its zero-based sequential number in the enum. In your case ordinals would be 0 for DE, 1 forUS and 2 UK.
Here is an example:
public enum Country {
DE, US, UK;
private static Map<Integer, Country> idToCountry = new HashMap<>();
Country() {
idToCountry.put(this.ordinal(), this);
}
public static Country getById(int id) {
return idToCountry.get(id);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With