Making an enum with stored LinkedHashMap values

218 views Asked by At

I'm somewhat of a beginner to java, although I understand the basics. I believed this was the best implementation for my problem, but obviously I may be wrong. This is a mock example I made, and I'm not interested in looking for different implementations. I simply mention I'm not sure if it's the best implementation in the case that it's impossible. Regardless:

Here I have an enum, inside of which I want a map (specifically a LinkedHashMap) as one of the enum object's stored values

enum Recipe {

    PANCAKES(true, new LinkedHashMap<>() ),
    SANDWICH(true, new LinkedHashMap<>() ),
    STEW(false, new LinkedHashMap<>() );

    private final boolean tasty;
    private final LinkedHashMap<String, String> directions;

    // getter for directions

    Recipe(boolean tasty, LinkedHashMap<String, String> directions) {
        this.tasty = tasty
        this.directions = directions;
    }
}

However, I haven't found a way to Initialize and Populate a Map of any size in a single line

(as this would be needed for an enum)

For example, I thought this looked fine

PANCAKES(true, new LinkedHashMap<>(){{
                       put("Pancake Mix","Pour");
                       put("Water","Mix with");
                       put("Pan","Put mixture onto");
               }};)

Until I read that this is dangerous and can cause a memory leak. Plus, it isn't the best looking code.

I also found the method:

Map.put(entry(), entry()... entry())

Which can be turned into a LinkedHashMap by passing it through its constructor:

PANCAKES(true, new LinkedHashMap<>(Map.put(entry(), ...)) );

Although I haven't found a way to ensure the insertion order is preserved, since as far as I'm aware Maps don't preserve insertion order.

Of course, there's always the option to store the LinkedHashMaps in a different place outside of the enum and simply put those in manually, but I feel like this would give me a headache managing, as I intend to add to this enum in the future.

Is there any other way to accomplish this?

to clarify, I don't literally need the code to occupy a single line, I just want the LinkedHashMap initialization and population to be written in the same place, rather than storing these things outside of the enum

2

There are 2 answers

5
egeorge On

First off, you don't really need to declare the map as a concrete implementation. If you just use Map then you will have a lot more choices.

enum Recipe {

    PANCAKES(true, Map.empty()),
    SANDWICH(true, Map.empty()),
    STEW(false, Map.empty());

    private final boolean tasty;
    private final Map<String, String> directions;

    // getter for directions

    Recipe(boolean tasty, Map<String, String> directions) {
        this.tasty = tasty
        this.directions = directions;
    }
}

Then, assuming you don't have more than 10 directions, you can use this form:

PANCAKES(true, Map.of(
    "Pancake Mix","Pour",
    "Water","Mix with",
    "Pan","Put mixture onto"))

Map.of creates an immutable map, which is probably what you want for this kind of application, and should not have memory leakage issues.

2
E-Riz On

Without more context, I'd say that Recipe is kind of a square peg to try to fit into the round hole of enum. In other words, in the absence of some other requirement or context that suggests an enum is best, I'd probably make it a class and expose public static final instances that can be used like enum values.

For example:

public class Recipe {

    public static final Recipe PANCAKES =
            new Recipe(true,
                        new Step("Pancake Mix","Pour"),
                        new Step("Water","Mix with"),
                        new Step("Pan","Put mixture onto")
                        );

    public static final Recipe SANDWHICH =
            new Recipe(true
                        // ...steps...
                    );

    // ...more Recipes ...


    @Getter
    public static class Step {
        private final String target;
        private final String action;

        private Step(String target, String action ) {
            this.target = target;
            this.action = action;
        }

    }

    private final boolean tasty;
    private final LinkedHashMap<String, Step> directions;

    private Recipe(boolean tasty, Step... steps) {
        this.tasty = tasty;
        this.directions = new LinkedHashMap<>();
        for (Step aStep : steps) {
            directions.put(aStep.getTarget(), aStep);
        }
    }
}

You could also do this as anenum, where the values would be declared like this:

    PANCAKES(true,
            new Step("Pancake Mix","Pour"),
            new Step("Water","Mix with"),
            new Step("Pan","Put mixture onto")
            ),

    SANDWHICH(true
                // ...steps...
                );

but like I said, this feels like a proper class as opposed to an enum.