1

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

  • Is there a specific reason you need the directions as a `Map`? Seems like any ordered collection (ie, `List`) would do. – E-Riz Jan 16 '23 at 20:35
  • By the way, the "double-brace" syntax for initializing a Map inline isn't necessarily bad. It does create an anonymous class every time it's called, but in many cases that's not going to make a real difference. Initializing values of an enum is one of those, since enum initialization is only performed once. As for "memory leaks," I'd be curious to hear a concrete case for that claim; I'm skeptical. – E-Riz Jan 16 '23 at 20:38
  • @E-Riz I understand a list would work for this mock example, but for my actual use I do need a LinkedHashMap, as it maps one type of object to another, and it needs to preserve insertion order – labradorEater Jan 16 '23 at 20:46
  • @E-Riz I didn't know that. I guess if it causes no issues it should work, but I'm still not totally comfortable with it. Here's a link I found claiming it's not totally safe, although a lot of it goes over my head: https://stackoverflow.com/a/27521360/21022183 – labradorEater Jan 16 '23 at 20:53
  • IMO, none of those points matter to your case, primarily because enums can't be instantiated - they're basically globally static pseudo-objects. – E-Riz Jan 16 '23 at 20:59
  • 2
    Reading the question and the comments, I'd think twice about using an enum at all. Without knowing your full context, `Recipe` seems like a full-fledged class. You can make it's constructor private and only expose final static instances publicly; that would probably achieve the same level of "protection." – E-Riz Jan 16 '23 at 21:01
  • @E-Riz I guess it's fine then. Thanks for clearing that up. I'll leave the question unanswered for a bit just in case, but I guess I found what I needed. – labradorEater Jan 16 '23 at 21:02
  • 1
    I think you're abusing `LinkedHashMap`. What you have here is a list of pairs - there's no reason for it to be a map. In particular, a recipe may have two steps involving the same ingredient (eg, saute onions, then add onions to the batter) and your map won't support that. – Dawood ibn Kareem Jan 16 '23 at 21:28
  • @DawoodibnKareem I know it's a bit later but I understand why I am using LinkedHashMap. This was just a mock example i cooked up to illustrate my problem easily. The code I'm actually trying to write is much more cluttered and I was simply curious about this one specific question outlined in the header – labradorEater Jan 17 '23 at 01:45
  • 1
    You could write your own method similar to `Map.of` that creates a `LinkedHashMap`, then call it for each element of your Enum. – Dawood ibn Kareem Jan 17 '23 at 01:58

2 Answers2

2

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.

E-Riz
  • 31,431
  • 9
  • 97
  • 134
  • The example I used was simply a mock example. The best storage method for the data, I believed, was a LinkedHashMap because I 1) want to preserve the order and 2) only need to access the Keys and their values, and no other info ; and I thought making an entire class to essentially just hold its own LinkedHashMap would be redundant. – labradorEater Jan 17 '23 at 01:54
  • But that's what your `enum` is, a holder for an ordered map. I don't understand the difference between that and "an entire class" in practical terms. Classes are cheap :-) (at least until you get to really obscene numbers of them - like the old saying, "A billion dollars here, a billion there, and pretty soon you're talking about *real* money). – E-Riz Jan 17 '23 at 14:47
0

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.

egeorge
  • 612
  • 3
  • 11
  • I noticed this may work, but only if I don't need to preserve an ordering to the map. Does Map.of() preserve the order of Key-Value pairs put into its entries? – labradorEater Jan 16 '23 at 20:50
  • That is an excellent question. I checked out the Java source, and `Map.of` returns an implementation of `ImmutableCollections.MapN` which is actually an Array-based structure. I have not written a test to confirm, but looking at the implementation, I would expect this to preserve order. – egeorge Jan 16 '23 at 21:16
  • 3
    @EricGeorge even if it does, that isn't part of the public contract of Map.of() and thus possible to change with any new Java release. Rely on that behavior at your own peril. – E-Riz Jan 16 '23 at 21:22
  • 1
    @E-Riz, you are completely correct. This makes my suggestion to use both `Map` and `Map.of` unsuitable for the problem. – egeorge Jan 16 '23 at 22:23
  • Thank you both for at least trying to tackle the LinkedHashMap problem. I'm simply looking into making a seperate EnumMap> instead. – labradorEater Jan 17 '23 at 01:51