2

I have the following problem. I have a package with different enumeration classes in java.

What is the correct approach to avoid repeting myself when adding new methods that are common to all enums?

My problem:

channel.java

    public enum Channel {
           channel1, channel2;
    }

color.java

    public enum color{
           color1, color2;
    }

The method which should be used in both:

    public static boolean contains(String channel) {
       for (Channel c : Channel.values()) {
          if (c.name().equals(channel)) {
              return true;
          }
      }
    return false;
    }

Note that the loop has a reference to the enum itself. So, this will require copy and paste the method in all enums that I want to use it. Any suggestion?

Thanks Victor

Laurentiu L.
  • 6,566
  • 1
  • 34
  • 60
vcaldas
  • 25
  • 8
  • The first question is why you need such a method in the first place. But apart from that, in Java 8 you can implement this using an interface and a default method. Whether it's a good idea to do so is another question. – biziclop May 20 '15 at 09:21
  • Why does your Color enum need this method if it has nothing to do with Color? – Oli May 20 '15 at 09:26
  • @biziclop I was aware of that Java8 part but I'm limited to Java 6 to this particular case. – vcaldas May 20 '15 at 09:29
  • @Oli This is just a example name. I have several different enum types. The idea is that I have a set of parameters and they are usually compared a lot in the execution. Also, some of the won't change - that's why I choosed enums. Do you have a better suggestion for that case? – vcaldas May 20 '15 at 09:30

4 Answers4

4

You can't do it quite the way you want to unfortunately. What you can do is to have some sort of util class that has a general mehod on it.

For example...

public class EnumUtils {

    public static boolean contains(Enum[] enumValues, String nameToCheck) {

        for(Enum each : enumValues) {
            if(each.name().equals(nameToCheck)) {
                return true;
            }
        }
        return false;
    }
}

Then you could use it like this...

System.out.println(EnumUtils.contains(Channel.values(), "channel1")); // TRUE
System.out.println(EnumUtils.contains(Color.values(), "octarine")); // FALSE

Caveat - In more complex systems, these sorts of static util classes are sometimes a bit of a "code-smell" but I think in your case it's fine.

For Java6:

     change each.name() => each.toString()
vcaldas
  • 25
  • 8
Phil Anderson
  • 3,146
  • 13
  • 24
  • I think that will do the job and I would like to vote this up, but I cant. Thanks for the caveat! I am fairly new to all this. – vcaldas May 20 '15 at 09:47
  • 1
    A more type-safe variation is to pass the class object for the enum and then use `Class.getEnumConstants()` to enumerate the constants within the method. – biziclop May 20 '15 at 09:47
  • @biziclop Re: Using the class object... Nice! I didn't think of that. – Phil Anderson May 20 '15 at 09:50
2

I suggest to use an util-method like @Phil Anderson mentioned. I would only change it to a general pattern:

public static <T extends Enum<T>> void some_method(Class<T> clazz, String name) {
    try {
        T foundEnum = Enum.valueOf(clazz, name);
        // name matches to one of enum values, do something with it
    } catch (IllegalArgumentException e) {
        // name doesn't matches to any of enum values
    }
}

which in case of contains semantic could look like this:

public static <T extends Enum<T>> boolean contains(Class<T> clazz, String name) {
    try {
        Enum.valueOf(clazz, name);
    } catch (IllegalArgumentException e) {
        return false;
    }
    return true;
}

Updated:

As @phil-anderson mentioned, from performance point of view this method have certain disadvantages, because generation and throwing of exception is pretty slow (see How slow are Java exceptions?). But this is only a case, if method is invoked with an incorrect name value.

So, in this case you could use this pattern:

public static <T extends Enum<T>> void some_method(Class<T> clazz, String name) {
    for (T each : clazz.getEnumConstants()) {
        if (each.name().equals(name)) {
            // name matches to one of enum values, do something with it
        }
    }
    // name doesn't matches to any of enum values
}

Moreover, if performance plays an important role, especially if enum consists of large number of values, it is not efficient to iterate over (maybe) all of them. The solution could be using a lazy hash map for enums and get the value by a hashcode. For example:

@SuppressWarnings("unchecked")
public static <T extends Enum<T>> void some_method(Class<T> clazz, String name) {
    Map<String, Enum<?>> enumMap = enumsMap.get(clazz);
    if (enumMap == null) {
        enumMap = new HashMap<String, Enum<?>>();
        for (T t : clazz.getEnumConstants()) {
            enumMap.put(t.name(), t);
        }
        enumsMap.put(clazz, enumMap);
    }
    T t = (T) enumMap.get(name);
    if (t != null) {
        // name matches to one of enum values, do something with it
    } else {
        // name doesn't matches to any of enum values
    }
}
Community
  • 1
  • 1
olexd
  • 1,360
  • 3
  • 13
  • 26
  • For some reason this is not giving the expected results. I'm not famiiliar with this structure so may be my bad. But If I try, for example: contains(Channel.class, "channel1"); I get that nothing is detected. – vcaldas May 20 '15 at 10:11
  • If you place contains-method in `EnumUtil` class, then `System.out.println(EnumUtil.contains(Channel.class, "channel1"));` returns `true` as expected. – olexd May 20 '15 at 10:19
  • Was a case-sensitive problem. Cheers. – vcaldas May 20 '15 at 11:14
  • I considered the valueof approach, but it's using exceptions for flow control which is not recommended (see Effective Java). That said, it was a pretty marginal call. – Phil Anderson May 22 '15 at 12:33
1

You can use an interface with a static method to do this:

public interface CanContainChannel {
    static boolean contains(String channel) {
        for (Channel c : Channel.values()) {
           if (c.name().equals(channel)) {
               return true;
           }
        }
        return false;
    }
}

And you both your enums can implement this interface to gain this method, although I'm not sure why you would want a method like this on your Color enum.

EDIT:

On clarification of the question I this question will help you: Iterate enum values using java generics

Community
  • 1
  • 1
Oli
  • 919
  • 8
  • 16
  • I think the idea is that each enum has its own version of the method, referring to itself. Plus there's not much point implementing an interface when all it has is a static method that doesn't even refer to the interface. – biziclop May 20 '15 at 09:24
  • @biziclop I did think using default methods would be a possibility, but I'm unsure why a Color enum would want an instance method such as this. Although, you could make the case it doesn't need a static method like it either. – Oli May 20 '15 at 09:25
  • @biziclop And in his example the method he wanted on both was static – Oli May 20 '15 at 09:26
  • 1
    I think what OP wants is almost what `Enum.valueOf()` does. – biziclop May 20 '15 at 09:27
  • @Oli Thanks for the help. But the point is, in this case, the enum in case is Channel. But, if in the enum Color, the loop should go around (Color c: Color.values()). Maybe I'm just trying a complex solution for a simple problem. – vcaldas May 20 '15 at 09:29
  • @biziclop I think you may well be right, but valueOf() throws if the enumerated value doesn't exist so if he genuinely wants to check if it exists or not, then he'd have to use exceptions as flow control, which is a big no-no in Java. – Phil Anderson May 20 '15 at 09:40
  • @PhilAnderson I know, it's a rather wonky method in that respect. But I just used it as a way to explain what I think OP wanted. – biziclop May 20 '15 at 09:43
0

With reflection one can fetch the enum constants.

enum E {
    i("unu"), ii("du"), iii("tri"), iv("kvar");

    public final String name;

    E(String name) {
        this.name = name;
    }
}

public static void main(String[] args) {
    // Normally in Java 8 for a concrete enum:
    final String sought = "tri";
    boolean found = Stream.of(E.values()).anyMatch((c) -> c.name.equals(sought));

    // A generic function:
    System.out.println("okay:  "
        + find(E.class, (c) -> c.name.equals(sought)));
    System.out.println("fails: "
        + find(E.class, (c) -> c.name.equals("prtl")));
}

public static <T extends Enum<?>> boolean find(Class<T> clazz,
        Predicate<T> predicate) {
    return Stream.of(clazz.getEnumConstants()).anyMatch(predicate);
}

As you want to access a field, it is easier to pass the entire predicate.

Under java 7 one would do it generally more tight (without Stream) and need an interface for getName() etcetera.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138