Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enums instead of usual classes in Java

While working on a project, I was presented with a task to design a set of classes that implement an interface defining a simple action. Usually these classes would do their job in particular sequence, all at once, but the possibility to call a method from only one of them was also a requirement.

Taking into account all the above and also considering that: - each class would have quite basic logic - extending another class was not required - it might be convenient to have all classes in a single file - editing source file when needed is not an issue

I came up with the following solution (actual class was not so contrived, but the example below is sufficient to give you some basic idea):

public enum Bestiary {
DOG(1) {
    @Override
    void makeNoise(Loudspeaker ls) {
        ls.shoutOutLoud("I am alpha dog");
    }
},

CAT(2) {
    @Override
    void makeNoise(Loudspeaker ls) {
        ls.shoutOutLoud("I am beta cat");
    }
},

RAT(3) {
    List<String> foods = new ArrayList<>();
    {
        foods.add("gods");
        foods.add("dogs");
        foods.add("cats");
        foods.add("other rats");
    }

    @Override
    void makeNoise(Loudspeaker ls) {
        StringBuilder cry = new StringBuilder("I am THE rat; usually I eat ");
        for (int i = 0; i < foods.size(); i++) {
            cry.append(foods.get(i));
            if (i != (foods.size() - 1)) {
                cry.append(", ");
            }
        }
        ls.shoutOutLoud(cry.toString());
    }
},

THE_THING(4) {
    String name = "r2d2";

    @Override
    void makeNoise(Loudspeaker ls) {
        ls.shoutOutLoud(calculateHash(name));

    }

    private String calculateHash(String smth) {
        return String.valueOf(smth.hashCode());
    }
};

private int id;

public int getId() {
    return id;
}

Bestiary(int id) {
    this.id = id;
}

abstract void makeNoise(Loudspeaker ls); // all enum elements will need to implement this - kind of like implementing an interface (which was also an option); note that we pass some arbitrary object and call methods on it
}

And code which calls this class might look like:

public final class Loudspeaker {
private static Loudspeaker loudspeaker = new Loudspeaker();

public static void shoutOutLoud(String cry) {
    System.out.println(cry);
}

static class Noizemakers {
    public static void makeSomeNoise() {
        for (Bestiary creature: Bestiary.values()) {
            System.out.println(creature + " with id " + creature.getId() +  " says: ");
            creature.makeNoise(loudspeaker);
        }
    }
}

public static void main(String[] args) {
    Noizemakers.makeSomeNoise();
    Bestiary.CAT.makeNoise(loudspeaker);
}
}

During a code review my suggestion was mocked as the one that is "too hacky, exploites the fact that enums have class body and methods, and have a bad code smell in overall". While transforming it into a separate interface, a bunch of usual Java classes etc. is a matter of few minutes, I was not really quite satisfied with this explanation. Are there any guidelines saying that you should use enums exclusively in their basic form, similarly to other languages? What real drawbacks does this approach have? How about Joshua Bloch's suggestion to write singletons as enums - in this case such an enum will have to be a full-fledged class, right?

like image 600
Alexey Danilov Avatar asked Jun 06 '13 19:06

Alexey Danilov


People also ask

Why we should not use enum in Java?

With an enum it can get more complicated due to having separate values for name and toString, and those values possibly being used in conditional logic.

Can enum be a class Java?

In Java, enum types are considered to be a special type of class. It was introduced with the release of Java 5. An enum class can include methods and fields just like regular classes. When we create an enum class, the compiler will create instances (objects) of each enum constants.

In which cases should we consider using enums Java?

You should always use enums when a variable (especially a method parameter) can only take one out of a small set of possible values. Examples would be things like type constants (contract status: "permanent", "temp", "apprentice"), or flags ("execute now", "defer execution").

What is the difference between enum and class in Java?

An enum can, just like a class , have attributes and methods. The only difference is that enum constants are public , static and final (unchangeable - cannot be overridden). An enum cannot be used to create objects, and it cannot extend other classes (but it can implement interfaces).


2 Answers

One could use an enum anywhere you have a shallow class hierarchy, with trade offs between extensibility (classes have more, enums have less) and conciseness (if the functionality is straightforward, enums are probably clearer). It's not the right thing to do all the time, but it's certainly ok to do some of the time, just be aware of the differences, some of which I list below.

To my mind, the situation you're dealing with seems to me to be exactly the kind of thing the language designers were working to support by allowing enums to have methods. It doesn't seem to me that you're subverting the intention of that language feature in the least.

As an example from my work, I often use enums with methods as a way of implementing a variety of stateless strategies, but have also used them for other things, including being a kind of extensible form of Class.

Answers to your specific questions:

What real drawbacks does this approach have?

Compared to the interface + concrete classes approach:

  • Methods defined in a specific enum value can't be called from outside that value. eg, if you defined a method for RAT called squeak(), no one can call it.
  • No mutable state, because each enum value is effectively a singleton.
  • Your enum class file can get excessively long if the number of types increases dramatically, or the code for each type increases.
  • Can't subclass enum values, they are effectively final
  • No doubt some others...

Are there any guidelines saying that you should use enums exclusively in their basic form, similarly to other languages?

None that I've ever seen.

How about Joshua Bloch's suggestion to write singletons as enums - in this case such an enum will have to be a full-fledged class, right?

Following the logic of your questioners, yes. So it becomes a question of whether you'd rather listen to them, or to Josh Bloch.

like image 161
sharakan Avatar answered Oct 03 '22 00:10

sharakan


You should only use an enum where there is no (or little) possibility of the addition of a new element. This is not to say that you shouldn't give an enum class-like functions. For example:

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7);

    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
}

There are multiple reasons for this:

  • Semantics/intended purpose. It just doesn't make sense to have enums for non-enumerations, by the very definition of the word.
  • Compatibility. What if I want to add a bird to your bestiary? You'd have to amend the enum. Easy enough, but what if you have some users using an older version of the enum and others using a later version? This makes for lots of compatibility issues.

One (suboptimal) solution if you must use an enum would be:

interface Animal {
    void makeNoise();
}

enum Bestiary implements Animal {
    // the rest of the stuff here
}

Then, any method currently accepting a Bestiary could be easily switched to accept an Animal. However, if you do this, it's better anyway to just have:

interface Animal {
    void makeNoise();
}
public class Dog implements Animal {...}
public class Cat implements Animal {...}
public class Rat implements Animal {...}
public class Thing implements Animal {...}
like image 42
wchargin Avatar answered Oct 03 '22 02:10

wchargin