3

Is it possible to declare and initialize a Hashtable or Map, so we don't have to declare and initialize on two different steps?

// declare first
Hashtable<String, Integer> numbers = new Hashtable<String, Integer>(3);

// then initialize
numbers.put("one", 1);
numbers.put("two", 2);
numbers.put("three", 3);

In ruby and javascript, for example, there are literals to define those:

numbers = {
  one: 1,
  two: 2,
  three: 3,
}
eloyesp
  • 3,135
  • 1
  • 32
  • 47
  • Possibly related: [builder for HashMap](https://stackoverflow.com/q/7345241) specifically (self promotion) https://stackoverflow.com/a/56383519 – Pshemo Aug 02 '22 at 18:01
  • This sounds like a false saving? You're using a strongly typed language, why would you want to slip in something that lets you bypasses that strong typing? While "it's more writing", that's basically a nonsense argument: you know how to copy paste, you have a decent code editor or IDE that lets you optimize typing in myriad ways, it really doesn't take much longer to write the declare + init code compared to the typeless ruby or JS code. – Mike 'Pomax' Kamermans Aug 02 '22 at 18:03
  • Groovy lets you do things like this. You could work out the language diffs in your build script and resulting class can still end up in the same package. Separates your source files but it works. Otherwise, I agree with @Mike'Pomax'Kamermans. See also: https://www.baeldung.com/groovy-maps – dan Aug 02 '22 at 18:10
  • For constants (`private static final SomeField`), I'll usually make use of a `static` initializer block, in tandem with `Collections#unmodifiableMap`. There is double-brace initialization (shown below), but this comes at a cost (also shown below in @Pshemo 's comments). The initializer block will allow you to create the `Map`, modify it as you like, and _then_ set the field to the value you want. – Rogue Aug 02 '22 at 18:12
  • @Mike'Pomax'Kamermans Scala, Kotlin, Groovy, Rust, C#, and D all have ways of initializing maps on one line from arguments. It has absolutely nothing to do with type safety or forfeiting guarantees and everything to do with cognitive load. A single line initializing constant data is parsed (by a human brain) as a single initializer. Ten lines to initialize one hashmap have to be parsed as an entire function, and it takes a minute to recognize that "oh, it's just really long initialization code". – Silvio Mayolo Aug 02 '22 at 18:52
  • Always search Stack Overflow thoroughly before posting. – Basil Bourque Aug 02 '22 at 19:33
  • @BasilBourque I've searched for more than one hour before posting, I was almost sure that the question was already asked, but sadly I could not found it... it seems that I were missing some keywords. – eloyesp Aug 02 '22 at 22:34

2 Answers2

6

For Java 9 and beyond, you can make use of Map#of, as well as Map#ofEntries with Map#entry:

Map<String, String> example = Map.ofEntries(
    Map.entry("1", "one"),
    Map.entry("2", "two"),
    Map.entry("3", "three"),
    //...
);

For shorter literals (10 or less entries), you can use Map#of directly (albeit with a slightly confusing syntax of Key1, Value1, Key2, Value2, ...):

Map<String, String> example = Map.of("1", "one", "2", "two");

Prior to Java 9, this would typically only be needed for initializing constant Map objects, for which a static initializer block is also useful:

private static final Map<String, String> MY_CONSTANT;

static {
    Map<String, String> setup = new HashMap<>();
    setup.put("1", "one");
    setup.put("2", "two");
    setup.put("3", "three");
    MY_CONSTANT = Collections.unmodifiableMap(setup);
}

This will allow you to create a constant Map while allowing you to modify the map before it is set.

As pointed out by @Pshemo, there are numerous reasons to avoid double-brace initialization, however the notable exception to the "rule" is typically in constants much like the above example (as numerous anonymous classes would not be made). However, it is still possible to work around the need for double-brace initialization even this case, so it is overall best to avoid if possible. The only case I can think of this not being possible is within the context of a constant within an enum class, where the double-brace init may truly be the only true way to make this work.

Rogue
  • 11,105
  • 5
  • 45
  • 71
1

Firstly, you shouldn't use Hashtable class - it's legacy. If you need a general purpose implementation of the Map interface, use HashMap instead.

In case when you need to initialize a general purpose Map with a few entries, you can use one of the overloaded versions of Map.of() method which is available starting with Java 9. It allows providing up to 10 key-value pairs, if you need more you can use Map.ofEntries(), which takes a varargs of map entries. You can create a map entry using another Java 9 method Map.entry().

Example:

public static final Map<String, Integer> numbers = 
    Map.of("one", 1, "two", 2, "three", 3);

Note these maps are

1. Immutable, therefore it would be the right choice only if the data never change (or might change rarely, i.e. in case of very frequent reads and infrequent writes, and every write would require re-initializing the map).

2 So-called unmodifiable maps like MapN (which is an internal implementation used by Map.ofEntries() intended to store an arbitrary number of key-value pairs) are optimized in terms of Memory consumption, their underlying array occupies exactly as match space as match there are key-value pairs and no more, and retrieving a value from an unmodifiable map using get() might even perform slightly better (see the quote below) in comparison to a HashMap.

For more information you can refer to official documentation - Creating Immutable Lists, Sets, and Maps:

Use Cases

The common use case for the immutable methods is a collection that is initialized from known values, and that never changes. Also consider using these methods if your data changes infrequently.

For optimal performance, the immutable collections store a data set that never changes. However, you may be able to take advantage of the performance and space-saving benefits even if your data is subject to change. These collections may provide better performance than the mutable collections, even if your data changes occasionally.

If you have a large number of values, you may consider storing them in a HashMap. If you are constantly adding and removing entries, then this is a good choice. But, if you have a set of values that never change, or rarely change, and you read from that set a lot, then the immutable Map is a more efficient choice. If the data set is read frequently, and the values change only rarely, then you may find that the overall speed is faster, even when you include the performance impact of destroying and rebuilding an immutable Map when a value changes.

Also note that depending on the type of keys and the actual data (specifically on the number of collisions), an unmodifiable map might perform worse than a HashMap because contrary to HashMap unmodifiable map is incapable to store and reuse hashes of the keys and hence might perform more comparisons of keys using equals method. And in case of large number of collisions, unmodifiable map has no means to mitigate performance degradation, unlike HashMap which since would try to arrange nodes into a tree when the number of nodes in a bucket exceeds certain threshold.

Spiking of other alternatives doubly-brace initialization, which creates an instance of anonymous inner class, is indisputably a discouraged practice.

Static and instance initializer blocks have no downsides from the technical point of view, they allow you to keep the variables final if you need that. But some might argue that from the perspective of clean coding, initializer blocks are not very great.

Another possibility you might consider is to initialize a map inline using Stream API (for instance, when map-data can be expressed as a sequence that is easy to generate).

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46