According to Why define a Java object using interface (e.g. Map) rather than implementation (HashMap), when creating objects, I should declare the most abstract type, eg : Map hashMap=new HashMap(). However, I don't think it is a good idea, because it seems suffers from the same problem as primitive obsession. Let's consider the following case about primitive obsession:
Case 1:
public class Case{
public static void methodForAgeOnly(int age){
}
public static int methodThatReturnsTemperature(){
int temperature=someTemperatureOutside();
return temperature;
}
public static void main(String[] args){
int age=30;
int temperature=20;
age=temperature; //problem 1
methodForAgeOnly(temperature); //problem 2
age=methodThatReturnsTemperature(); //problem 3
}
}
Clearly, we don't want problems 1-3 to occur (swapping age and temperature), which is a problem that may occur with only primitives. So we could enclose age and temperature into a class :
Case 2:
public class Case{
public static void methodForAgeOnly(Age age){
}
public static Temperature methodThatReturnsTemperature(){
Temperature temperature=someTemperatureOutside();
return temperature;
}
public static void main(String[] args){
Age age=30;
Temperature temperature=20;
age=temperature; //problem 1
methodForAgeOnly(temperature); //problem 2
age=methodThatReturnsTemperature(); //problem 3
}
}
so that the code won't compile if we swap age and temperature.
Now, consider case 3, which declares the most abstract type:
public class Case{
public static void methodForUsernameMapOnly(Map<String,String> usernameMap){
}
public static Map<String,String> methodThatReturnsSessionIdMap(){
Map<String,String> sessionIdMap=someSessionIdMapOutside();
return sessionIdMap;
}
public static void main(String[] args){
Map<String,String> usernameMap=new HashMap<String,String>();
Map<String,String> sessionIdMap=new TreeMap<String,String>();
usernameMap=sessionIdMap; //problem 1
methodForUsernameMapOnly(sessionIdMap); //problem 2
usernameMap=methodThatReturnsSessionIdMap(); //problem 3
}
}
which allows us to swap usernameMap and sessionIdMap. Doesn't it suffer from the same problem as case 1? So I think declaring the exactly using type instead of the abstract type is a better habit:
Case 4:
public class Case{
public static void methodForUsernameMapOnly(HashMap<String,String> usernameMap){
}
public static TreeMap<String,String> methodThatReturnsSessionIdMap(){
TreeMap<String,String> sessionIdMap=someSessionIdMapOutside();
return sessionIdMap;
}
public static void main(String[] args){
HashMap<String,String> usernameMap=new HashMap<String,String>();
TreeMap<String,String> sessionIdMap=new TreeMap<String,String>();
usernameMap=sessionIdMap; //problem 1
methodForUsernameMapOnly(sessionIdMap); //problem 2
usernameMap=methodThatReturnsSessionIdMap(); //problem 3
}
}
which prevents us from mixing usernameMap with sessionIdMap by compile error. While I agree declaring the most abstract type allows us to change less code when we want to replace HashMap with TreeMap, it reduces type safety, which I think type safety is more important than easy to change because the change may never happen. Is the argument above true?
Note: I think
HashMap<String,String> usernameMap=new HashMap<String,String>();
is still programming to interface because according to https://softwareengineering.stackexchange.com/a/402035/432039, HashMap seems already an interface, just it is not the most abstract interface.
MultiMap
extends fromTreeMap
... so not a solution either. This problem has very little to do with primitive obsession. It's rather about Interface segregation.