How to directly initialize a HashMap (in a literal way)?

后端 未结 14 2382
野趣味
野趣味 2020-11-22 10:58

Is there some way of initializing a Java HashMap like this?:

Map test = 
    new HashMap{\"test\":\"test\",\"test\         


        
相关标签:
14条回答
  • 2020-11-22 11:35

    tl;dr

    Use Map.of… methods in Java 9 and later.

    Map< String , String > animalSounds =
        Map.of(
            "dog"  , "bark" ,   // key , value
            "cat"  , "meow" ,   // key , value
            "bird" , "chirp"    // key , value
        )
    ;
    

    Map.of

    Java 9 added a series of Map.of static methods to do just what you want: Instantiate an immutable Map using literal syntax.

    The map (a collection of entries) is immutable, so you cannot add or remove entries after instantiating. Also, the key and the value of each entry is immutable, cannot be changed. See the Javadoc for other rules, such as no NULLs allowed, no duplicate keys allowed, and the iteration order of mappings is arbitrary.

    Let's look at these methods, using some sample data for a map of day-of-week to a person who we expect will work on that day.

    Person alice = new Person( "Alice" );
    Person bob = new Person( "Bob" );
    Person carol = new Person( "Carol" );
    

    Map.of()

    Map.of creates an empty Map. Unmodifiable, so you cannot add entries. Here is an example of such a map, empty with no entries.

    Map < DayOfWeek, Person > dailyWorkerEmpty = Map.of();
    

    dailyWorkerEmpty.toString(): {}

    Map.of( … )

    Map.of( k , v , k , v , …) are several methods that take 1 to 10 key-value pairs. Here is an example of two entries.

    Map < DayOfWeek, Person > weekendWorker = 
            Map.of( 
                DayOfWeek.SATURDAY , alice ,     // key , value
                DayOfWeek.SUNDAY , bob           // key , value
            )
    ;
    

    weekendWorker.toString(): {SUNDAY=Person{ name='Bob' }, SATURDAY=Person{ name='Alice' }}

    Map.ofEntries( … )

    Map.ofEntries( Map.Entry , … ) takes any number of objects implementing the Map.Entry interface. Java bundles two classes implementing that interface, one mutable, the other immutable: AbstractMap.SimpleEntry, AbstractMap.SimpleImmutableEntry. But we need not specify a concrete class. We merely need to call Map.entry( k , v ) method, pass our key and our value, and we get back an object of a some class implementing Map.Entry interface.

    Map < DayOfWeek, Person > weekdayWorker = Map.ofEntries(
            Map.entry( DayOfWeek.MONDAY , alice ) ,            // Call to `Map.entry` method returns an object implementing `Map.Entry`. 
            Map.entry( DayOfWeek.TUESDAY , bob ) ,
            Map.entry( DayOfWeek.WEDNESDAY , bob ) ,
            Map.entry( DayOfWeek.THURSDAY , carol ) ,
            Map.entry( DayOfWeek.FRIDAY , carol )
    );
    

    weekdayWorker.toString(): {WEDNESDAY=Person{ name='Bob' }, TUESDAY=Person{ name='Bob' }, THURSDAY=Person{ name='Carol' }, FRIDAY=Person{ name='Carol' }, MONDAY=Person{ name='Alice' }}

    Map.copyOf

    Java 10 added the method Map.copyOf. Pass an existing map, get back an immutable copy of that map.

    Notes

    Notice that the iterator order of maps produced via Map.of are not guaranteed. The entries have an arbitrary order. Do not write code based on the order seen, as the documentation warns the order is subject to change.

    Note that all of these Map.of… methods return a Map of an unspecified class. The underlying concrete class may even vary from one version of Java to another. This anonymity enables Java to choose from various implementations, whatever optimally fits your particular data. For example, if your keys come from an enum, Java might use an EnumMap under the covers.

    0 讨论(0)
  • 2020-11-22 11:37

    You can use Streams In Java 8 (this is exmaple of Set):

    @Test
    public void whenInitializeUnmodifiableSetWithDoubleBrace_containsElements() {
        Set<String> countries = Stream.of("India", "USSR", "USA")
          .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));
    
        assertTrue(countries.contains("India"));
    }
    

    Ref: https://www.baeldung.com/java-double-brace-initialization

    0 讨论(0)
  • 2020-11-22 11:38

    If you allow 3rd party libs, you can use Guava's ImmutableMap to achieve literal-like brevity:

    Map<String, String> test = ImmutableMap.of("k1", "v1", "k2", "v2");
    

    This works for up to 5 key/value pairs, otherwise you can use its builder:

    Map<String, String> test = ImmutableMap.<String, String>builder()
        .put("k1", "v1")
        .put("k2", "v2")
        ...
        .build();
    


    • note that Guava's ImmutableMap implementation differs from Java's HashMap implementation (most notably it is immutable and does not permit null keys/values)
    • for more info, see Guava's user guide article on its immutable collection types
    0 讨论(0)
  • 2020-11-22 11:38

    You could possibly make your own Map.of (which is only available in Java 9 and higher) method easily in 2 easy ways

    Make it with a set amount of parameters

    Example

    public <K,V> Map<K,V> mapOf(K k1, V v1, K k2, V v2 /* perhaps more parameters */) {
        return new HashMap<K, V>() {{
          put(k1, v1);
          put(k2,  v2);
          // etc...
        }};
    }
    

    Make it using a List

    You can also make this using a list, instead of making a lot of methods for a certain set of parameters.

    Example

    public <K, V> Map<K, V> mapOf(List<K> keys, List<V> values) {
       if(keys.size() != values.size()) {
            throw new IndexOutOfBoundsException("amount of keys and values is not equal");
        }
    
        return new HashMap<K, V>() {{
            IntStream.range(0, keys.size()).forEach(index -> put(keys.get(index), values.get(index)));
        }};
    }
    

    Note It is not recommended to use this for everything as this makes an anonymous class every time you use this.

    0 讨论(0)
  • 2020-11-22 11:38

    Unfortunately, using varargs if the type of the keys and values are not the same is not very reasonable as you'd have to use Object... and lose type safety completely. If you always want to create e.g. a Map<String, String>, of course a toMap(String... args) would be possible though, but not very pretty as it would be easy to mix up keys and values, and an odd number of arguments would be invalid.

    You could create a sub-class of HashMap that has a chainable method like

    public class ChainableMap<K, V> extends HashMap<K, V> {
      public ChainableMap<K, V> set(K k, V v) {
        put(k, v);
        return this;
      }
    }
    

    and use it like new ChainableMap<String, Object>().set("a", 1).set("b", "foo")

    Another approach is to use the common builder pattern:

    public class MapBuilder<K, V> {
      private Map<K, V> mMap = new HashMap<>();
    
      public MapBuilder<K, V> put(K k, V v) {
        mMap.put(k, v);
        return this;
      }
    
      public Map<K, V> build() {
        return mMap;
      }
    }
    

    and use it like new MapBuilder<String, Object>().put("a", 1).put("b", "foo").build();

    However, the solution I've used now and then utilizes varargs and the Pair class:

    public class Maps {
      public static <K, V> Map<K, V> of(Pair<K, V>... pairs) {
        Map<K, V> = new HashMap<>();
    
        for (Pair<K, V> pair : pairs) {
          map.put(pair.first, pair.second);
        }
    
        return map;
      }
    }
    

    Map<String, Object> map = Maps.of(Pair.create("a", 1), Pair.create("b", "foo");

    The verbosity of Pair.create() bothers me a bit, but this works quite fine. If you don't mind static imports you could of course create a helper:

    public <K, V> Pair<K, V> p(K k, V v) {
      return Pair.create(k, v);
    }
    

    Map<String, Object> map = Maps.of(p("a", 1), p("b", "foo");

    (Instead of Pair one could imagine using Map.Entry, but since it's an interface it requires an implementing class and/or a helper factory method. It's also not immutable, and contains other logic not useful for this task.)

    0 讨论(0)
  • 2020-11-22 11:41

    All Versions

    In case you happen to need just a single entry: There is Collections.singletonMap("key", "value").

    For Java Version 9 or higher:

    Yes, this is possible now. In Java 9 a couple of factory methods have been added that simplify the creation of maps :

    // this works for up to 10 elements:
    Map<String, String> test1 = Map.of(
        "a", "b",
        "c", "d"
    );
    
    // this works for any number of elements:
    import static java.util.Map.entry;    
    Map<String, String> test2 = Map.ofEntries(
        entry("a", "b"),
        entry("c", "d")
    );
    

    In the example above both test and test2 will be the same, just with different ways of expressing the Map. The Map.of method is defined for up to ten elements in the map, while the Map.ofEntries method will have no such limit.

    Note that in this case the resulting map will be an immutable map. If you want the map to be mutable, you could copy it again, e.g. using mutableMap = new HashMap<>(Map.of("a", "b"));

    (See also JEP 269 and the Javadoc)

    For up to Java Version 8:

    No, you will have to add all the elements manually. You can use an initializer in an anonymous subclass to make the syntax a little bit shorter:

    Map<String, String> myMap = new HashMap<String, String>() {{
            put("a", "b");
            put("c", "d");
        }};
    

    However, the anonymous subclass might introduce unwanted behavior in some cases. This includes for example:

    • It generates an additional class which increases memory consumption, disk space consumption and startup-time
    • In case of a non-static method: It holds a reference to the object the creating method was called upon. That means the object of the outer class cannot be garbage collected while the created map object is still referenced, thus blocking additional memory

    Using a function for initialization will also enable you to generate a map in an initializer, but avoids nasty side-effects:

    Map<String, String> myMap = createMap();
    
    private static Map<String, String> createMap() {
        Map<String,String> myMap = new HashMap<String,String>();
        myMap.put("a", "b");
        myMap.put("c", "d");
        return myMap;
    }
    
    0 讨论(0)
提交回复
热议问题