Base Method Invocation on enum

Go To StackoverFlow.com

3

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();
            }
        }
2012-04-05 18:31
by c12


1

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

}
2012-04-05 18:38
by Óscar López
Catching exceptions as part of an ordinary control-flow is a performance killer - Kirk Woll 2012-04-05 18:43
@KirkWoll not necessarily. We don't know if the method is going to be invoked only once, or a million of times. You know what they say about premature optimization.. - Óscar López 2012-04-05 18:45
this is not controversial. Exceptions are for exceptional conditions, not for control flow. Get yourself a copy of Effective Java - Kirk Woll 2012-04-05 18:46
@Oscar Lopez - I added some additional code to show more clearly what the return from the methods is - c12 2012-04-05 18:46
@c12 and I updated my code accordingl - Óscar López 2012-04-05 18:47
Exceptions are for exceptional conditions, but there might not be an alternative here, given that you want to use 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
@Louis, there are many alternatives, not least of which is simply handling the lookup yourself by leveraging the 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
@Louis, also, I'm not sure what you expect out of exception performance, but between a method that throws an exception and the same method that does not, you'll see a 50:1 difference in performance. That's something you should always be aware of. And I'm not criticizing the performance of 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
I have hard numbers on recent versions of Java that indicate not a 50x performance difference, but a 1.1x difference. (10% slower. Yeah.) That's why I want benchmarks for this particular case. I'll admit that surprised the heck out of me when I did that benchmark -- I thought that exceptions were uberslow all the time, no exceptions -- but the numbers said I was wrong - Louis Wasserman 2012-04-05 18:54
@Louis: http://pastebin.com/AtGA9pC - Kirk Woll 2012-04-05 18:55
let us continue this discussion in chatLouis Wasserman 2012-04-05 18:56
Okay. We have hard numbers on the performance for this particular use case, and looping through values is significantly faster than doing a try/catch - Louis Wasserman 2012-04-05 19:13
apart from performance, I think readability should be a factor as well. Using try/catch for control flow keeps me awake at nigh - Omnipresent 2012-04-05 19:17
OK guys, you got me here. I updated my answer, thanks for all the comments - Óscar López 2012-04-05 19:21
Still this is not the best way to use enum, as you are splitting the same thing (attributes) in different enums. Everytime you get a new type of attribute you will need a new for. I posted a different way to do this that only uses one enum - Caesar Ralf 2012-04-05 20:46


1

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.

2012-04-05 20:06
by Caesar Ralf
In my example the colName parameter must be one of the values in the enum. So it will always be ATTRIBUTEONE, ATTRIBUTETWO...etc - c12 2012-04-05 20:56
That I understood, but I didn't understand how 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


0

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.

2012-04-05 18:32
by Louis Wasserman
...and if you still needed to segregate the two subsets of enums somehow, that could be done with a private field that is provided in the enum constructor - Kevin Welker 2012-04-05 19:27
Ads