I'm learning about Java enums and I was wondering what is the best approach to check multiple enums for a matching value in order to call a specific method. I have defined two separate enums below that are used by getValue method's colName parameter to determine what method to execute. So the enum drives the method call. There has to be a more efficient way to do this than what I have below. Any suggestions?
I want to avoid having to do the below (pseudo code):
if(colName.equalsIgnoreCase("ATTRIBUTEONE") ||
colName.equalsIgnoreCase("ATTRIBUTETWO") ||
colName.equalsIgnoreCase("ATTRIBUTETWO")){
callAsStringMethod();
} else if(colName.equalsIgnoreCase("ATTRIBUTEFOUR")){
callAsIntegerMethod();
}
My Attempt using enum:
public class RowHelper implements IRowHelper
public static enum StringAttributes {
ATTRIBUTEONE,
ATTRIBUTETWO,
ATTRIBUTETHREE;
}
public static enum IntegerAttributes {
ATTRIBUTEFOUR,
ATTRIBUTEFIVE,
ATTRIBUTESIX,
ATTRIBUTESEVEN;
}
@Override
public String getValue(String colName) throws Exception{
boolean colFound=false;
Object retValue = null;
for (EConstants.StringAttributes attribute : EConstants.StringAttributes.values()) {
if(colName.toUpperCase().equals(attribute)){
retValue = callAsStringMethod();
colFound=true;
}
}
for (EConstants.IntegerAttributes attribute : EConstants.IntegerAttributes.values()) {
if(colName.toUpperCase().equals(attribute)){
retValue = callAsIntegerMethod();
colFound=true;
}
}
if(!colFound)
throw new Exception("column not found");
if(retValue instanceof String )
return (String) retValue;
else
return retValue.toString();
}
}
Try this:
public String getValue(String colName) throws Exception {
final String name = colName != null ? colName.trim().toUpperCase() : "";
try {
EConstants.StringAttributes.valueOf(name);
return callAsStringMethod().toString();
} catch (Exception e1) {
try {
EConstants.IntegerAttributes.valueOf(name);
return callAsIntegerMethod().toString();
} catch (Exception e2) {
throw new Exception("column not found");
}
}
}
The method's now returning the appropriate value, according to the latest edit of the question.
EDIT :
According to Kirk Woll and Louis Wasserman's benchmark, looping through values
is significantly faster than doing a try/catch
. So here's a simplified version of the original code, expect it to be a bit faster:
public String getValue(String colName) throws Exception {
final String name = colName != null ? colName.trim().toUpperCase() : "";
for (EConstants.StringAttributes attribute : EConstants.StringAttributes.values())
if (name.equals(attribute))
return callAsStringMethod().toString();
for (EConstants.IntegerAttributes attribute : EConstants.IntegerAttributes.values())
if (name.equals(attribute))
return callAsIntegerMethod().toString();
throw new Exception("column not found");
}
Enum.valueOf
-- and I'll be fair, the performance of exceptions has actually improved in recent Java releases - Louis Wasserman 2012-04-05 18:49
values()
method. (and, if necessary, throwing that in a map - Kirk Woll 2012-04-05 18:49
Enum.valueOf
gets to use a JVM-global cache of values. I'd like to see benchmarks before I go whipping up alternatives - Louis Wasserman 2012-04-05 18:52
valueOf
when it finds it, I'm complaining about the performance of the exception when it does not - Kirk Woll 2012-04-05 18:52
values
is significantly faster than doing a try/catch - Louis Wasserman 2012-04-05 19:13
Well, this is a weird design ._. Anyway, you can use enum, but I would something like:
public interface RowAttribute {
String getValue(IRowHelper rowHelper);
}
public class StringRowAttribute implements RowAttribute {
@Override
public String getValue(IRowHelper rowHelper) {
return rowHelper.callAsStringMethod();
}
}
public class IntegerRowAttribute implements RowAttribute {
@Override
public String getValue(IRowHelper rowHelper) {
return rowHelper.callAsIntegerMethod().toString();
}
}
public class RowHelper implements IRowHelper {
private static final RowAttribute INTEGER_ATTRIBUTE = new IntegerRowAttribute();
private static final RowAttribute STRING_ATTRIBUTE = new StringRowAttribute();
private static enum Attribute {
ATTRIBUTEONE(INTEGER_ATTRIBUTE),
ATTRIBUTETWO(INTEGER_ATTRIBUTE),
ATTRIBUTETHREE(INTEGER_ATTRIBUTE);
ATTRIBUTEFOUR(STRING_ATTRIBUTE),
ATTRIBUTEFIVE(STRING_ATTRIBUTE),
ATTRIBUTESIX(STRING_ATTRIBUTE),
ATTRIBUTESEVEN(STRING_ATTRIBUTE);
private final RowAttribute attribute;
private Attribute(RowAttribute attribute) {
this.attribute = attribute;
}
public RowAttribute getAttributeResolver() {
return this.attribute;
}
}
@Override
public String getValue(String colName) throws Exception {
final String name = colName != null ? colName.trim() : "";
for (Attribute attribute : Attribute.values()) {
if (attribute.name().equalsIgnoreCase(name)) {
return attribute.getAttributeResolver().getValue(this);
}
}
throw new Exception(String.format("Attribute for column %s not found", colName));
}
}
Then you don't need to create more than one enum and use its power to iterate through the possible values. You would only need to make the methods callAsStringMethod/callAsIntegerMethod public. Another way is to insert the implementations inside RowHelper. Something like this:
public class RowHelper implements IRowHelper {
public interface RowAttribute {
String getValue();
}
private static final RowAttribute INTEGER_ATTRIBUTE = new RowAttribute() {
@Override
public String getValue() {
return callAsIntegerMethod().toString();
}
};
private static final RowAttribute STRING_ATTRIBUTE = new RowAttribute() {
@Override
public String getValue() {
return callAsStringMethod();
}
};
...
@Override
public String getValue(String colName) throws Exception {
...
if (attribute.name().equalsIgnoreCase(name)) {
return attribute.getAttributeResolver().getValue();
}
...
}
}
Anyway, I don't understand in your method how you get the attribute value really without passing as parameter the colName to it.
callAsXXXMethod()
knows which column he needs to get the attribute. Anyway, if you want to use enum, its better to aim to using only one and specialize different behaviours throught specialized classes or even at using abstract method/override method in the enum - Caesar Ralf 2012-04-05 21:00
The most efficient way to do this with multiple enums is, frankly, to make them the same enum. There isn't really a better way.
That said, instead of the loop you have, you can use Enum.valueOf(EnumClass.class, name)
to find the enum value of that type with the specified name, rather than looping like you're doing.