Home Tutorials Training Consulting Books Company Contact us


Get more...

This tutorial explains the usage of Maps and HashMap with Java.

1. Using Maps

1.1. Map and HashMap

The Map interface defines an object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value. The HashMap class is an efficient implementation of the Map interface.

1.2. Example Program

To see the following code snippets in action, insert them into the main method where put test code here is stated.

package com.vogella.java.collections.map;

import java.util.HashMap;
import java.util.Map;

public class MapTester {
    public static void main(String[] args) {
        // put test code here
    }
}

1.3. Initialize a HashMap in Java

The following code demonstrates how to initialize a HashMap in Java.

1.3.1. Initialize an Empty Map

In the following code, an empty map with keys and values of type String is initialized.

Map<String, String> map = Map.of();

1.3.2. Initialize an Unmodifiable Map with Map.of for Small Maps

In the following example, a map with several entries is initialized. This factory method supports a maximum of 10 key-value pairs and requires at least Java 9.

Map<String, String> map = Map.of("key1", "value1", "key2", "value2");

1.3.3. Initialize an Unmodifiable Map via the Map.ofEntries()

Map<String, String> map = Map.ofEntries(
    Map.entry("key1", "value1"),
    Map.entry("key2", "value2"),
    // more entries can be added here
    Map.entry("key100", "value100")
);

1.3.4. Initialize a Map via the New Operator

The following code creates a map using the new operator and adds multiple entries to it via the put method.

Map<String, String> map = new HashMap<>();
map.put("Android", "Mobile");
map.put("Eclipse IDE", "Java");
map.put("Eclipse RCP", "Java");
map.put("Git", "Version control system");

1.4. Remove an Entry from a Map

You can remove an entry from a map using the remove method.

Map<String, String> map = Map.of("Android", "Mobile OS", "Flutter", "Development environment");
map.remove("Android");

1.5. Process the Map

To process every element in a map, you can use the forEach method, which takes a lambda as a parameter.

map.forEach((k, v) -> System.out.printf("%s %s%n", k, v));

1.6. Convert Keys in a Map to an Array or a List

You can convert the keys or values to an array or a list. The following code demonstrates this process.

Map<String, String> map = new HashMap<>();
map.put("Android", "Mobile");
map.put("Eclipse IDE", "Java");
map.put("Eclipse RCP", "Java");
map.put("Git", "Version control system");

// Convert keys to array
String[] keys = map.keySet().toArray(new String[0]);
for (String key : keys) {
    System.out.println(key);
}

// Convert keys to list
List<String> list = new ArrayList<>(map.keySet());
for (String key : list) {
    System.out.println(key);
}

1.7. Getting the Current Value or a Default for a Map

You can use the getOrDefault() method to return the value for a key or a default value if the key is not present in the map.

Map<String, Integer> map = new HashMap<>();
map.put("Android", 1 + map.getOrDefault("Android", 0));

// Write to command line
map.forEach((k, v) -> System.out.printf("%s %s%n", k, v));

1.8. Compute If Absent

The computeIfAbsent method calculates and adds an entry to the map if it is not already present.

Map<String, Integer> map = new HashMap<>();

Integer calculatedValue = map.computeIfAbsent("Java", it -> 0);
System.out.println(calculatedValue);

map.keySet().forEach(key -> System.out.println(key + " " + map.get(key)));

2. Sorting Maps in Java

Sorting maps in Java can be achieved in several ways, depending on the requirements for ordering keys or values. This chapter explores the methods to sort maps, focusing on the TreeMap for key sorting and using streams for sorting by values.

2.1. Sorting by Keys

The simplest way to sort a map by keys is to use TreeMap, which maintains the natural ordering of its keys or can be constructed with a custom comparator.

2.1.1. Using TreeMap

A TreeMap automatically sorts the keys in their natural order or according to a specified comparator.

import java.util.Map;
import java.util.TreeMap;

public class SortMapByKeys {
    public static void main(String[] args) {
        Map<String, Integer> map = new TreeMap<>();
        map.put("Banana", 3);
        map.put("Apple", 5);
        map.put("Orange", 2);

        // Display the sorted map
        map.forEach((k, v) -> System.out.println(k + ": " + v));
    }
}

2.2. Sorting by Values

To sort a map by its values, you can use Java Streams to create a sorted representation of the entries.

2.2.1. Example of Sorting by Values

Here’s how to sort a HashMap by values using streams:

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class SortMapByValues {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("Banana", 3);
        map.put("Apple", 5);
        map.put("Orange", 2);

        // Sort by values
        Map<String, Integer> sortedByValues = map.entrySet()
            .stream()
            .sorted(Map.Entry.comparingByValue())
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (e1, e2) -> e1,
                LinkedHashMap::new // Maintain insertion order
            ));

        // Display the sorted map
        sortedByValues.forEach((k, v) -> System.out.println(k + ": " + v));
    }
}

2.3. Custom Sorting

You can also implement custom sorting logic using comparators. For instance, to sort a map by values in descending order:

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class CustomSortMapByValues {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("Banana", 3);
        map.put("Apple", 5);
        map.put("Orange", 2);

        // Sort by values in descending order
        Map<String, Integer> sortedByValuesDescending = map.entrySet()
            .stream()
            .sorted(Map.Entry.comparingByValue((v1, v2) -> v2.compareTo(v1))) // Custom comparator
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (e1, e2) -> e1,
                LinkedHashMap::new // Maintain insertion order
            ));

        // Display the sorted map
        sortedByValuesDescending.forEach((k, v) -> System.out.println(k + ": " + v));
    }
}

2.4. Conclusion

Sorting maps can be easily accomplished using TreeMap for key-based sorting or Java Streams for value-based sorting. By employing custom comparators, you can tailor the sorting behavior to suit your application’s requirements. Understanding these methods enhances your ability to manipulate and present data effectively in Java.

3. Using ConcurrentHashMap

In multi-threaded environments, handling data shared among threads can lead to synchronization issues. The ConcurrentHashMap class is designed to allow concurrent access without locking the entire map, making it a suitable choice for high-performance applications.

3.1. Overview of ConcurrentHashMap

The ConcurrentHashMap is part of the Java Collections Framework and provides an efficient way to manage key-value pairs in a multi-threaded context. Unlike regular maps, ConcurrentHashMap allows multiple threads to read and write to the map concurrently without causing data inconsistencies.

3.2. Creating a ConcurrentHashMap

You can create a ConcurrentHashMap similarly to other map implementations. Here’s an example:

import java.util.concurrent.ConcurrentHashMap;

ConcurrentHashMap<String, String> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put("key1", "value1");
concurrentMap.put("key2", "value2");

3.3. Key Features of ConcurrentHashMap

  1. Concurrency Level:

    • ConcurrentHashMap allows you to specify a concurrency level during initialization, which defines how many threads can access the map concurrently.

  2. Non-blocking Reads:

    • Read operations (e.g., get) do not block other threads, making them very efficient.

  3. Segmented Locks:

    • Instead of locking the entire map, ConcurrentHashMap divides the map into segments. Each segment can be locked independently, allowing better concurrent performance.

  4. Atomic Operations:

    • The map provides atomic methods like putIfAbsent, remove, and replace, ensuring thread safety for these operations.

3.4. Example Usage of ConcurrentHashMap

Here’s a simple example demonstrating the use of ConcurrentHashMap in a multi-threaded application:

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();

        // Create and start multiple threads that modify the map
        Runnable task = () -> {
            for (int i = 0; i < 5; i++) {
                concurrentMap.put(Thread.currentThread().getName() + i, i);
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        // Wait for threads to finish
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // Display the contents of the map
        concurrentMap.forEach((k, v) -> System.out.println(k + ": " + v));
    }
}

3.5. Synchronization Issues

When using ConcurrentHashMap, it is essential to understand that:

  • Concurrent Modifications: Multiple threads can modify the map at the same time, but if your logic depends on checking a value before updating it, you may need to use atomic operations or additional synchronization.

  • Iterating Over the Map: If you need to iterate over the map while it may be modified by other threads, use the forEach method or keySet in a way that doesn’t assume a consistent view of the map.

3.6. Best Practices

  1. Use Atomic Operations:

    • Leverage atomic methods provided by ConcurrentHashMap to perform updates safely without explicit locking.

  2. Avoid External Synchronization:

    • Since ConcurrentHashMap handles its own synchronization, avoid wrapping it with synchronized blocks unless absolutely necessary.

  3. Profile Your Application:

    • Always profile and test your application under concurrent loads to ensure the map performs as expected.

By following these guidelines and understanding the capabilities of ConcurrentHashMap, you can effectively manage shared data in multi-threaded applications without compromising performance or data integrity. == Common Use Cases for Maps in Java

Maps are versatile data structures that provide efficient key-value pair storage. This chapter explores several common use cases for maps in Java, illustrating their practicality in various scenarios.

3.7. 1. Frequency Counting

One of the most common use cases for maps is counting the occurrences of items, such as words in a text or characters in a string. This can be efficiently implemented using a HashMap.

3.7.1. Example: Counting Word Frequencies

import java.util.HashMap;
import java.util.Map;

public class WordFrequencyCounter {
    public static void main(String[] args) {
        String text = "apple banana apple orange banana apple";
        Map<String, Integer> frequencyMap = new HashMap<>();

        for (String word : text.split(" ")) {
            frequencyMap.put(word, frequencyMap.getOrDefault(word, 0) + 1);
        }

        frequencyMap.forEach((k, v) -> System.out.println(k + ": " + v));
    }
}

3.8. 2. Caching Data

Maps are often used for caching data to improve performance in applications. By storing previously retrieved results in a map, you can avoid expensive computations or database calls.

3.8.1. Example: Simple Cache Implementation

import java.util.HashMap;
import java.util.Map;

public class SimpleCache {
    private Map<String, String> cache = new HashMap<>();

    public String getData(String key) {
        // Check if the data is already in the cache
        return cache.getOrDefault(key, fetchData(key));
    }

    private String fetchData(String key) {
        // Simulate data fetching (e.g., from a database)
        String data = "Data for " + key;
        cache.put(key, data);
        return data;
    }

    public static void main(String[] args) {
        SimpleCache cache = new SimpleCache();
        System.out.println(cache.getData("item1")); // Fetches data
        System.out.println(cache.getData("item1")); // Returns cached data
    }
}

3.9. 3. Configuration Settings

Maps can efficiently store configuration settings for applications. You can easily retrieve and modify settings based on keys.

3.9.1. Example: Application Configuration

import java.util.HashMap;
import java.util.Map;

public class AppConfig {
    private Map<String, String> config = new HashMap<>();

    public AppConfig() {
        // Initialize configuration settings
        config.put("url", "https://example.com");
        config.put("timeout", "30");
    }

    public String getConfig(String key) {
        return config.get(key);
    }

    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        System.out.println("URL: " + appConfig.getConfig("url"));
        System.out.println("Timeout: " + appConfig.getConfig("timeout"));
    }
}

3.10. 4. Grouping Data

Maps can be used to group related data together, such as associating a list of values with a single key.

3.10.1. Example: Grouping Students by Class

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class GroupStudents {
    public static void main(String[] args) {
        Map<String, List<String>> classMap = new HashMap<>();

        // Adding students to classes
        classMap.computeIfAbsent("Class A", k -> new ArrayList<>()).add("Alice");
        classMap.computeIfAbsent("Class A", k -> new ArrayList<>()).add("Bob");
        classMap.computeIfAbsent("Class B", k -> new ArrayList<>()).add("Charlie");

        // Displaying students in each class
        classMap.forEach((k, v) -> System.out.println(k + ": " + v));
    }
}

3.11. 5. Storing Relationships

Maps can represent relationships between entities, such as users and their friends or products and their categories.

3.11.1. Example: User Friendships

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class UserFriendships {
    private Map<String, Set<String>> friendships = new HashMap<>();

    public void addFriend(String user, String friend) {
        friendships.computeIfAbsent(user, k -> new HashSet<>()).add(friend);
        friendships.computeIfAbsent(friend, k -> new HashSet<>()).add(user); // Bidirectional friendship
    }

    public Set<String> getFriends(String user) {
        return friendships.getOrDefault(user, new HashSet<>());
    }

    public static void main(String[] args) {
        UserFriendships uf = new UserFriendships();
        uf.addFriend("Alice", "Bob");
        uf.addFriend("Alice", "Charlie");

        System.out.println("Alice's friends: " + uf.getFriends("Alice"));
    }
}

3.12. Conclusion

Maps provide a powerful and flexible way to manage data in Java. By understanding these common use cases, you can leverage maps to solve various programming challenges efficiently and effectively.

If you need more assistance we offer Online Training and Onsite training as well as consulting