Reputation: 375
I have written an enum class and I want to either get the attribute by type or get the type by attribute, but it seems impossible.
public enum AreaCode {
area1(7927),
area2(7928),
area3(7929);
private final int ac;
AreaCode(int ac) {
this.ac = ac;
}
int areaCode(){
return ac;
}
AreaCode area(int n) {
switch (n) {
case 7927: return AreaCode.area1;
case 7928: return AreaCode.area2;
case 7929: return AreaCode.area3;
}
}
}
The code above will not compile. How to make area(int n)
work?
Upvotes: 19
Views: 38434
Reputation: 51423
This way you make it clear on the method signature that you might or not find the enum.
public static Optional<AreaCode> getAreaCode(int area_code){
return Arrays.stream(AreaCode.values()).filter(e -> e.ac == area_code).findFirst();
}
Upvotes: 1
Reputation: 1326
You should include a default clause in your switch statement, because the compiler does not understand what to do when n is not 7927, 7928 or 7929.
Upvotes: 0
Reputation: 462
You can use the next construction
public enum AreaCode {
area1(7927),
area2(7928),
area3(7929);
private static final Map<Integer, AreaCode> idMap = new HashMap<Integer, AreaCode>();
static {
for (AreaCode areaCode : AreaCode.values()) {
idMap.put(areaCode.id, areaCode);
}
}
private Integer id;
private AreaCode(Integer id) {
this.id = id;
}
public static AreaCode getById(Integer id) {
return idMap.get(id);
}
}
Upvotes: 2
Reputation: 559
I have written a helper to avoid to pollute enum code and which lets you get Enum of any type by attribute. You won't have anymore to declare specific methods on each enum type.
In your case, you can use it like following (your getter must be public):
// Java 8
AreaCode area = FunctionalEnumHelper.getEnum(AreaCode.class, AreaCode::areaCode, 7927); // value is area1
// Java 6
AreaCode area = EnumHelper.getEnum(AreaCode.class, 7927); // value is area1
Given the following enum :
public enum Move {
FORWARD("F"),
RIGHT("R"),
LEFT("L");
private String label;
private Move(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}
With Java 8: uses functional programming
import java.util.function.Function;
/**
* Helper to get an {@link Enum} of any Type by attribute or method
*
*/
public final class FunctionalEnumHelper {
// Constructors
//-------------------------------------------------
/**
* Private constructor
* A helper should not be instantiated in order to force static calls
*/
private FunctionalEnumHelper() {}
// Static methods
//-------------------------------------------------
/**
* Get the enum of type <code>E</code> associated to the attribute
* @param enumType
* @param method
* @param expectedValue
* @return
*/
public static <E extends Enum<E>, R> E getEnum(final Class<E> enumType, final Function<E, R> method, final R expectedValue) {
E enumVariable = null;
E[] values = enumType.getEnumConstants();
if(values != null) {
for(E e : values) {
if(e != null) {
Object value = method.apply(e);
if(value == null && expectedValue == null || value != null && value.equals(expectedValue)) {
enumVariable = e;
break;
}
}
}
}
return enumVariable;
}
/* Functional style */
public static <E extends Enum<E>, R> E getEnum(final Class<E> enumType, final Function<E, R> method, final R expectedValue) {
return Arrays.stream(enumType.getEnumConstants())
.filter(e -> {
final Object value = method.apply(e);
return value == null && expectedValue == null || value != null && value.equals(expectedValue);
})
.findAny()
.orElse(null);
}
}
Use:
Move move = FunctionalEnumHelper.getEnum(Move.class, Move::getLabel, "F") // value is Move.FORWARD
With Java 6: uses the reflection API
import java.lang.annotation.ElementType;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper to get an {@link Enum} of any Type by attribute or method
*
*/
public final class EnumHelper {
private static final Logger logger = LoggerFactory.getLogger(EnumHelper.class);
// Constructors
//-------------------------------------------------
/**
* Private constructor
* A helper should not be instantiated in order to force static calls
*/
private EnumHelper() {}
// Static methods
//-------------------------------------------------
/**
* Get the enum of type <code>E</code> which first attribute has value {@link obj}.
* @param enumType
* @param value
* @return
*/
public static <E extends Enum<E>> E getEnum(final Class<E> enumType, final Object value) {
return getEnum(enumType, null, value);
}
/**
* Get the enum of type <code>E</code> which attribute {@link attributeName} has value {@link obj}.
* @param enumType
* @param value
* @return
*/
public static <E extends Enum<E>> E getEnum(final Class<E> enumType, final String attributeName, final Object value) {
return getEnum(enumType, ElementType.FIELD, attributeName, value);
}
/**
* Get the enum of type <code>E</code> associated to the attribute
* @param enumType
* @param elementType
* @param name
* @param value
* @return
*/
public static <E extends Enum<E>> E getEnum(final Class<E> enumType, final ElementType elementType, final String name, final Object value) {
E enumVariable = null;
E[] enumObjs = enumType.getEnumConstants();
if(enumObjs != null) {
ReflectionData reflectionData = new ReflectionData();
for(E enumObj : enumObjs) {
if(enumObj != null) {
Object val = getValue(reflectionData, elementType, name, enumObj);
if(val == null && value == null || val != null && val.equals(value)) {
enumVariable = enumObj;
break;
}
}
}
}
return enumVariable;
}
private static Object getValue(final ReflectionData reflectionData, final ElementType type, final String name, final Object obj) {
Object value = null;
final String parameter = name != null ? name.trim() : null;
switch(type) {
case FIELD : value = getFieldValue(reflectionData, obj, parameter); break;
case METHOD : value = callMethod(reflectionData, obj, parameter); break;
default : throw new IllegalArgumentException("The elementType '" + type.toString() + "' is not allowed!");
}
return value;
}
/**
* Get the attribute value
* @param reflectionData
* @param obj
* @param fieldName
* @return
*/
private static Object getFieldValue(final ReflectionData reflectionData, final Object obj, final String fieldName) {
Object value = null;
try {
Field field = reflectionData.getField();
if(field == null) {
field = computeField(obj, fieldName);
reflectionData.setField(field);
}
boolean accessible = field.isAccessible();
field.setAccessible(true);
value = field.get(obj);
field.setAccessible(accessible);
}
catch (NoSuchFieldException | SecurityException e) {
logger.error("No attribute {} : {}", fieldName, e.getMessage());
}
catch (IllegalArgumentException | IllegalAccessException e) {
logger.error(e.getMessage());
}
return value;
}
private static Field computeField(final Object obj, final String fieldName) throws NoSuchFieldException {
Field field = null;
if(fieldName != null) {
field = obj.getClass().getDeclaredField(fieldName);
}
else {
Field[] fields = obj.getClass().getDeclaredFields();
if(fields != null) {
int position = obj.getClass().getEnumConstants().length;
field = fields[position];
}
}
return field;
}
/**
* Call the method
* @param reflectionData
* @param obj
* @param methodName
* @return
*/
private static Object callMethod(final ReflectionData reflectionData, final Object obj, final String methodName) {
Object returnValue = null;
try {
Method method = reflectionData.getMethod();
if(method == null) {
method = obj.getClass().getMethod(methodName);
reflectionData.setMethod(method);
}
returnValue = method.invoke(obj);
}
catch (SecurityException | NoSuchMethodException e) {
logger.error("No method {} : {}", methodName, e.getMessage());
}
catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
logger.error("Error when calling method {} on class {} : {}", methodName, obj.getClass(), e.getMessage());
}
return returnValue;
}
private static class ReflectionData {
private Field field;
private Method method;
public Field getField() {
return field;
}
public Method getMethod() {
return method;
}
public void setField(final Field field) {
this.field = field;
}
public void setMethod(final Method method) {
this.method = method;
}
}
}
Use:
// Basic use
Move move = EnumHelper.getEnum(Move.class, "F"); // value is Move.FORWARD
You can also specify which attribute you want to use (by default it is the first attribute of the Enum)
// Get by attribute
Move move = EnumHelper.getEnum(Move.class, "label", "F");
// Get by method
Move move = EnumHelper.getEnum(Move.class, ElementType.METHOD, "getLabel", "F");
The code is centralised and you don't need to code the same handling into each enumeration. Don't reinvent the wheel ! It is easy to use, it increases the productivity and enums remain cleans.
Execution time: The complexity is O(n), so not as well as accessing to a static hashmap declared in an enumeration Type (which is O(1)). Otherwise, because it uses the reflection API (java 6) or Functional (java 8), performance are slower than with a standard code fragment. It is, by magnitude, more expensive.
However it is possible to add a cache system (EhCache, map..).
Safety: This helper can throw exceptions at runtime if you call it with bad arguments, while standard code would have produced errors during the compilation.
The reflection API is slow, so it is not production friendly ! Don't use it in a production environment with time constraints.
Just add a static method Move::getMove for test comparison:
public enum Move {
FORWARD("F"),
RIGHT("R"),
LEFT("L");
private String label;
private Move(final String label) {
this.label = label;
}
public String getLabel() {
return label;
}
// Only used by regular test
public static Move getMove(final String label) {
Move move = null;
for(Move curr : Move.values()) {
if(curr.label.equals(label)) {
move = curr;
break;
}
}
return move;
}
}
Now we can compare performance of each solution:
public class Main {
public static void main(final String[] args) {
int nbrIterations = 1000000;
doTrivial(nbrIterations);
doRegular(nbrIterations);
doFunctional(nbrIterations);
doReflection(nbrIterations);
}
private static void doTrivial(final int nbrIterations) {
long start = System.currentTimeMillis();
for (int i=0; i<nbrIterations; ++i) {
Move.valueOf("FORWARD");
Move.valueOf("RIGHT");
Move.valueOf("LEFT");
}
System.out.println("Trivial: " + (System.currentTimeMillis() - start) + "ms");
}
private static void doRegular(final int nbrIterations) {
long start = System.currentTimeMillis();
for (int i=0; i<nbrIterations; ++i) {
Move.getMove("F");
Move.getMove("R");
Move.getMove("L");
}
System.out.println("Regular: " + (System.currentTimeMillis() - start) + "ms");
}
private static void doFunctional(final int nbrIterations) {
long start = System.currentTimeMillis();
for (int i=0; i<nbrIterations; ++i) {
FunctionalEnumHelper.getEnum(Move.class, Move::getLabel, "F");
FunctionalEnumHelper.getEnum(Move.class, Move::getLabel, "R");
FunctionalEnumHelper.getEnum(Move.class, Move::getLabel, "L");
}
System.out.println("Functional: " + (System.currentTimeMillis() - start) + "ms");
}
private static void doReflection(final int nbrIterations) {
long start = System.currentTimeMillis();
for (int i=0; i<nbrIterations; ++i) {
EnumHelper.getEnum(Move.class, "F");
EnumHelper.getEnum(Move.class, "R");
EnumHelper.getEnum(Move.class, "L");
}
System.out.println("Reflection (argument): " + (System.currentTimeMillis() - start) + "ms");
long start2 = System.currentTimeMillis();
for (int i=0; i<nbrIterations; ++i) {
EnumHelper.getEnum(Move.class, ElementType.METHOD, "getLabel", "F");
EnumHelper.getEnum(Move.class, ElementType.METHOD, "getLabel", "R");
EnumHelper.getEnum(Move.class, ElementType.METHOD, "getLabel", "L");
}
System.out.println("Reflection (method): " + (System.currentTimeMillis() - start2) + "ms");
}
}
The results are: Trivial: 28ms | Regular: 53ms | Functional: 171ms | Reflection (argument): 890ms | Reflection (method): 800ms
This benchmark shows that the functional solution is a little more expensive than the regular solution (with ugly code in enums..), but it remains acceptable. The solution with reflection is beautiful to read but it is not suitable to an environment with time constraint.
Upvotes: 4
Reputation: 341
Here is another way (using Guava and Java 8) to create a Map for a lookup:
import com.google.common.collect.Maps;
public enum AreaCode {
area1(7927),
area2(7928),
area3(7929);
private final int ac;
private final static Map<Integer, AreaCode> AREA_BY_CODE =
Maps.uniqueIndex(EnumSet.allOf(AreaCode.class), AreaCode::areaCode);
AreaCode(int ac) {
this.ac = ac;
}
public static AreaCode area(int n) {
return AREA_BY_CODE.get(n);
}
int areaCode() {
return ac;
}
}
Upvotes: 4
Reputation: 48600
As they say, there is more than one way to skin a cat. First off, enum values should be uppercase (words delimited by underscores) as they are constant values and should be treated as such by Java naming conventions. At the very least, they should begin with a capital letter as all class names should.
public enum AreaCode {
AREA_1(7927),
AREA_2(7928),
AREA_3(7929);
private int areaCode;
private AreaCode(int areaCode) {
this.areaCode = areaCode;
}
public int getAreaCode() {
return areaCode;
}
}
Now, there are three ways to retrieve an enum by an instance variable. A switch statement, a loop with an equality condition, and a lookup map. The last scenario may add more memory to your program, but if you need to lookup a lot of enums quickly, this will help you do it at a constant rate O(1) time.
Each of the enum classes below are act identical, but each one does something different internally. By adding the following main()
method to any of these classes, you will get the same result.
public static void main(String[] args) {
System.out.println(retrieveByAreaCode(7928));
}
The example above will print:
AreaCode[name="AREA_2", value="7928"]
Lookup is O(1) (constant time), but you need to hard-code each case (not very dynamic).
public enum AreaCode {
AREA_1(7927),
AREA_2(7928),
AREA_3(7929);
private int areaCode;
private AreaCode(int areaCode) {
this.areaCode = areaCode;
}
public int getAreaCode() {
return areaCode;
}
public static AreaCode retrieveByAreaCode(int n) {
switch (n) {
case 7927:
return AreaCode.AREA_1;
case 7928:
return AreaCode.AREA_2;
case 7929:
return AreaCode.AREA_3;
default:
return null;
}
}
@Override
public String toString() {
return String.format("%s[name=\"%s\", value=\"%d\"]",
this.getClass().getName(), this.name(), this.getAreaCode());
}
}
Lookup is O(n) (linear time), so you need to loop over each value until you find a match, but you do need to hard-code each case (dynamic).
public enum AreaCode {
AREA_1(7927),
AREA_2(7928),
AREA_3(7929);
private int areaCode;
private AreaCode(int areaCode) {
this.areaCode = areaCode;
}
public int getAreaCode() {
return areaCode;
}
public static AreaCode retrieveByAreaCode(int n) {
for (AreaCode areaCode : AreaCode.values()) {
if (areaCode.getAreaCode() == n) {
return areaCode;
}
}
return null;
}
@Override
public String toString() {
return String.format("%s[name=\"%s\", value=\"%d\"]",
this.getClass().getName(), this.name(), this.getAreaCode());
}
}
Lookup is O(1) (constant time), and you do not need to hard-code each value (dynamic), but you need to store the map which takes up memory.
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public enum AreaCode {
AREA_1(7927),
AREA_2(7928),
AREA_3(7929);
private static final Map<Integer, AreaCode> LOOKUP_MAP;
private int areaCode;
static {
LOOKUP_MAP = new HashMap<Integer, AreaCode>();
for (AreaCode areaCode : AreaCode.values()) {
LOOKUP_MAP.put(areaCode.getAreaCode(), areaCode);
}
LOOKUP_MAP = Collections.unmodifiableMap(LOOKUP_MAP);
}
private AreaCode(int areaCode) {
this.areaCode = areaCode;
}
public int getAreaCode() {
return areaCode;
}
public static AreaCode retrieveByAreaCode(int n) {
return LOOKUP_MAP.get(n);
}
@Override
public String toString() {
return String.format("%s[name=\"%s\", value=\"%d\"]",
this.getClass().getName(), this.name(), this.getAreaCode());
}
}
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class EnumUtils {
public static interface EnumProperty<T extends Enum<T>, U> {
U getValue(T type);
}
public static <T extends Enum<T>, U> Map<U, T> createLookup(Class<T> enumTypeClass, EnumProperty<T, U> prop) {
Map<U, T> lookup = new HashMap<U, T>();
for (T type : enumTypeClass.getEnumConstants()) {
lookup.put(prop.getValue(type), type);
}
return Collections.unmodifiableMap(lookup);
}
}
import java.util.Map;
public enum AreaCode {
AREA_1(7927),
AREA_2(7928),
AREA_3(7929);
private static final EnumUtils.EnumProperty<AreaCode, Integer> ENUM_PROP;
private static final Map<Integer, AreaCode> LOOKUP_MAP;
static {
ENUM_PROP = new EnumUtils.EnumProperty<AreaCode, Integer>() {
@Override
public Integer getValue(AreaCode code) {
return code.getAreaCode();
}
};
LOOKUP_MAP = EnumUtils.createLookup(AreaCode.class, ENUM_PROP);
}
private int areaCode;
private AreaCode(int areaCode) {
this.areaCode = areaCode;
}
public int getAreaCode() {
return areaCode;
}
public static AreaCode retrieveByAreaCode(int n) {
return LOOKUP_MAP.get(n);
}
@Override
public String toString() {
return String.format("%s[name=\"%s\", value=\"%d\"]",
this.getClass().getName(), this.name(), this.getAreaCode());
}
}
Upvotes: 26
Reputation: 115328
I suggest to add static map that maps the integers to area codes and then just use this map.
public enum AreaCode {
area1(7927), area2(7928), area3(7929);
private final int ac;
private static Map<Integer, AreaCode> id2code = new HashMap<Integer, AreaCode>();
AreaCode(int ac) {
this.ac = ac;
id2code.put(ac, this);
}
int areaCode(){
return ac;
}
AreaCode area(int n){
return id2code.get(n);
}
}
}
Upvotes: 4
Reputation: 308021
Apart from the issues pointed at by the other posters, I'd rewrite the method to avoid duplicating the information (keep it DRY!):
public static AreaCode area(int n) {
for (AreaCode c : values()) {
if (c.ac == n) {
return c;
}
}
// either throw the IAE or return null, your choice.
throw new IllegalArgumentException(String.valueOf(n));
}
Upvotes: 43
Reputation: 11999
The reason it doesn't compile is that there's a missing return statement. You only return something for the cases that are recognized. I'd advise you to add a default case that returns something indicating the area code isn't known. Either an enum constant with name unknown
or null could do the job.
Upvotes: 2
Reputation: 691715
The method should be static, and should return something in every case. Make it return null in the default case, or make it throw an IllegalArgumentException (or some other exception) : it's up to you.
Note: reading the compiler error message should guide you.
Upvotes: 1
Reputation: 346270
All you need to do is add a default case so the method always returns something or throws an exception:
AreaCode area(int n){
switch (n) {
case 7927: return AreaCode.area1;
case 7928: return AreaCode.area2;
case 7929: return AreaCode.area3;
default: return null;
}
}
or perhaps better
AreaCode area(int n){
switch (n) {
case 7927: return AreaCode.area1;
case 7928: return AreaCode.area2;
case 7929: return AreaCode.area3;
default: throw new IllegalArgumentException(String.valueOf(n));
}
}
Upvotes: 16