2

I want to have an enum that provides a function that includes a constructor of a new class. Before Java8 I used to write this code:

enum Fruit1 {
    APPLE, BANANA;

    public Tree getTree() {
        switch (this) {
            case APPLE:
                return new AppleTree();
            case BANANA:
                return new BananaTree();
            default:
                throw new IllegalStateException();
        }
    }
}

Now, I can use lambda expressions. The advantage is, when adding a new enum entry, you cannot forget adding sth to the switch statement. But I dont like that the code looks more complicated to me.

enum Fruit2 {
    APPLE(AppleTree::new), BANANA(BananaTree::new);

    Supplier<Tree> treeSupplier;

    Fruit2(Supplier<Tree> treeSupplier) {
        this.treeSupplier = treeSupplier;
    }

    public Tree getTree() {
        return treeSupplier.get();
    }
}

Is it just a matter of taste or is there more to it? Can I do it in a nicer way using lambda expressions?

This is strongly related to other questions comparing lambda expressions to abstract methods, but here I would like to focus on the switch expression, that I am still quite comfortable with (in comparison to lambda expressions).

Community
  • 1
  • 1
user954923
  • 61
  • 1
  • 5
  • If the code looks more complicated to you, why would you want to use it? I find it simpler and nicer. But define what you consider nicer. – Tunaki Oct 06 '16 at 08:33
  • 2
    Possible duplicate of [Is there any reason to override methods in enums in Java 8](http://stackoverflow.com/questions/28277562/is-there-any-reason-to-override-methods-in-enums-in-java-8) – sprinter Oct 06 '16 at 09:30
  • The lambda expression variant is free of conditionals, but of course, a hotspot optimizer should be able to optimize the `switch` statement variant to a similar outcome, if it ever becomes performance relevant. Then, the only difference is the necessity of writing `default: throw new IllegalStateException();` in the `switch` statement to make the compiler happy. The lambda variant does not need that… – Holger Oct 06 '16 at 14:14

2 Answers2

3

Your second snippet looks fine. If you feel otherwise, you can do something similar by bringing the implementation of your method at the enum constant level:

enum Fruit1 {
  APPLE {
    public Tree getTree() { return new AppleTree(); }
  }, BANANA {
    public Tree getTree() { return new BananaTree(); }
 };

  public abstract Tree getTree();
}
assylias
  • 321,522
  • 82
  • 660
  • 783
  • This is a great solution -- I've found that often using functions rather than functional types gives you the option of doing either. With this solution, if you still needed a supplier you could create one using method references, like `myFruit::getTree` or `APPLE::getTree`. – Hank D Oct 06 '16 at 16:33
1

One thing you should ask yourself is whether it is a good idea to have such a tight coupling between the Fruit enumeration and the Tree class hierarchy at all. Consider the alternative:

public enum Fruit { APPLE, BANANA }

public class TreeFactory {
    static EnumMap<Fruit,Supplier<Tree>> SUPPLIERS=new EnumMap<>(Fruit.class);
    static {
        SUPPLIERS.put(Fruit.APPLE,  AppleTree::new);
        SUPPLIERS.put(Fruit.BANANA, BananaTree::new);
        assert SUPPLIERS.keySet().containsAll(EnumSet.allOf(Fruit.class));
    }
    public static Tree getTree(Fruit f) {
        return SUPPLIERS.getOrDefault(
            Objects.requireNonNull(f), ()->{throw new AssertionError();}).get();
    }
}

The enum specialized collections are efficient as the hard-coded alternatives, like the switch statement or storing data in the enum constants themselves. But this solution works without the need for the Fruit type to know anything about the Tree types.

Holger
  • 285,553
  • 42
  • 434
  • 765