Difference between using Map and HashMap as declared type
Categories:
Map vs. HashMap: Understanding Declared Types in Java

Explore the critical differences and implications of declaring a variable as Map versus HashMap in Java, focusing on flexibility, API access, and best practices.
In Java, when working with collections, you often encounter situations where you need to choose between declaring a variable with an interface type (like Map) or a concrete class type (like HashMap). While both can hold a HashMap object, the choice of the declared type has significant implications for your code's flexibility, maintainability, and the methods you can directly access. This article delves into these differences, providing clarity on when to use each approach and why.
Understanding the Map Interface
The java.util.Map is an interface that represents a mapping from keys to values. It defines the fundamental contract for how key-value pairs are stored and retrieved, including methods like put(), get(), containsKey(), keySet(), values(), and entrySet(). It does not, however, specify any details about the underlying data structure or its performance characteristics. When you declare a variable as Map, you are essentially stating that you only care about the general contract of a map, not the specific implementation details.
import java.util.Map;
import java.util.HashMap;
public class MapDeclaration {
public static void main(String[] args) {
// Declaring with the Map interface type
Map<String, Integer> counts = new HashMap<>();
counts.put("apple", 5);
counts.put("banana", 2);
System.out.println("Map declared counts: " + counts);
}
}
Declaring a variable using the Map interface type.
Understanding the HashMap Class
The java.util.HashMap is a concrete class that implements the Map interface. It uses a hash table for storage, providing constant-time performance for basic operations (get and put), assuming the hash function disperses elements properly among the buckets. HashMap offers specific implementation details and, in some cases, additional methods or constructors beyond what the Map interface defines (though these are rare for HashMap itself, they are more common in other concrete implementations like LinkedHashMap or TreeMap). When you declare a variable as HashMap, you are explicitly stating that you intend to use the specific features and performance characteristics of a hash map.
import java.util.HashMap;
public class HashMapDeclaration {
public static void main(String[] args) {
// Declaring with the HashMap concrete class type
HashMap<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 88);
System.out.println("HashMap declared scores: " + scores);
}
}
Declaring a variable using the HashMap concrete class type.
Key Differences and Best Practices
The primary difference lies in the level of abstraction and flexibility. Declaring a variable as Map promotes programming to an interface, which is a core principle of good object-oriented design. This approach offers several advantages:
- Flexibility: If you decide later to change the underlying implementation (e.g., from
HashMaptoTreeMaporLinkedHashMap) for different performance characteristics or ordering guarantees, you only need to change the instantiation line (new HashMap<>()tonew TreeMap<>()). The rest of your code that interacts with themapvariable remains unchanged, as long as it only uses methods defined in theMapinterface. - Reduced Coupling: Your code becomes less coupled to a specific implementation. This makes it easier to swap out components, test different implementations, and generally makes your code more robust to changes.
- API Restriction: By declaring as
Map, you restrict yourself to using only the methods defined in theMapinterface. This can be a good thing, as it prevents you from accidentally relying on implementation-specific details that might not be present in otherMapimplementations.
Conversely, declaring a variable as HashMap means you are tightly coupled to that specific implementation. While this might seem simpler for small, isolated cases, it can lead to rigidity and make future refactoring more difficult. You should only declare a variable as HashMap if you specifically need methods or constructors that are unique to HashMap and not part of the Map interface (which is rare for HashMap itself, but more relevant for other specific implementations like LinkedHashMap's removeEldestEntry method).

Decision flow for choosing between Map and HashMap.
Map interface unless you have a compelling reason to use a specific HashMap method not available in the Map interface.Practical Example: Changing Implementations
Consider a scenario where you initially use HashMap but later realize you need the elements to be ordered by insertion. If you declared your variable as Map, the change is minimal. If you declared it as HashMap, you might need to refactor more code.
Using Map Interface
import java.util.Map; import java.util.HashMap; import java.util.LinkedHashMap;
public class FlexibleMap { public static void main(String[] args) { // Initial declaration using Map interface, instantiated as HashMap Map<String, String> userSettings = new HashMap<>(); userSettings.put("theme", "dark"); userSettings.put("language", "en"); System.out.println("Initial settings (HashMap): " + userSettings);
// Later, change to LinkedHashMap for insertion order guarantee
// Only the instantiation line changes, variable type remains Map
userSettings = new LinkedHashMap<>();
userSettings.put("theme", "dark");
userSettings.put("language", "en");
userSettings.put("notifications", "on");
System.out.println("Updated settings (LinkedHashMap): " + userSettings);
}
}
Using HashMap Class
import java.util.HashMap; import java.util.LinkedHashMap;
public class RigidHashMap { public static void main(String[] args) { // Initial declaration using HashMap class HashMap<String, String> userSettings = new HashMap<>(); userSettings.put("theme", "dark"); userSettings.put("language", "en"); System.out.println("Initial settings (HashMap): " + userSettings);
// If you later need LinkedHashMap, you'd have to change the variable type
// This might require more widespread changes if 'userSettings' was passed around
// HashMap<String, String> userSettings = new LinkedHashMap<>(); // This would be a type mismatch if not re-declared
// To change, you'd need to re-declare or cast, which is less clean.
// For demonstration, let's show the impact:
LinkedHashMap<String, String> orderedSettings = new LinkedHashMap<>();
orderedSettings.put("theme", "dark");
orderedSettings.put("language", "en");
orderedSettings.put("notifications", "on");
System.out.println("New ordered settings (LinkedHashMap): " + orderedSettings);
}
}
HashMap is a common choice, remember that other Map implementations like TreeMap (sorted by key) and LinkedHashMap (insertion-ordered) offer different behaviors. Choosing Map as the declared type allows you to switch between these with minimal code changes.In conclusion, the choice between Map and HashMap as a declared type boils down to a fundamental principle of software engineering: programming to an interface. By favoring Map, you create more flexible, maintainable, and robust code that is less susceptible to changes in underlying implementation details. Reserve HashMap as the declared type only when you have a specific, compelling reason to leverage its unique, non-interface-defined characteristics.