Thetsu
Thetsu

Reputation: 375

How to get the enum type by its attribute?

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

Answers (11)

dreamcrash
dreamcrash

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

Efthymis
Efthymis

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

PonomarevMM
PonomarevMM

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

Fabienr_75
Fabienr_75

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

Details:

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; 
    }
}

Helpers

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");

Benefits

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.

Disadvantages

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.


Performance test

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

Philipp Paland
Philipp Paland

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

Mr. Polywhirl
Mr. Polywhirl

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"]

Switch

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());
    }
}

Loop

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

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());
    }
}

Generic Approach

EnumUtils.java

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

AlexR
AlexR

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

Joachim Sauer
Joachim Sauer

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

G_H
G_H

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

JB Nizet
JB Nizet

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

Michael Borgwardt
Michael Borgwardt

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

Related Questions