An HTTP query can be parametrized to a list of pairs like name=value
that can be encapsulated in general as Map<String, String>
since an HTTP GET query is string pairs. I could need something for any combination of objects and primitives. A varargs method for example update(String s1...)
could be written in general as update(Map<String, String>)
where the key is the name and the value is the data.
Therefore I wonder if it could be good to use a definition as Map<K, V>
instead of writing a custom object in this case wrapping the specific 3 parameters to a class in their specific types which can't be reused whereas a Map<K, V>
can be used to encapsulate any 2-tuples when we must write a pair like Integer i
or int i
or int i=0
.
But I think it will only work for classes and not for primitive types, so the primitive types must be boxed in their objects so I must also box an int
to an Integer
for this to also work with primitive types.
Is there a better way than Map<K, V>
to encapsulate method parameters when we must select a list of unknown length and unknown types where the types could be primitives or objects?
So instead of being specific about types and their values in the method, can I use a Map<K, V>
instead of a list of named pairs such as String s, int i, Object o...
Best Answer
Abstract data types are not good things in Java for passing around complex objects or parameters.
Type safety
Lets compare two bits of code
vs
In the second example, we have to use
Object
as the value because its the only object thatString
andInteger
share in common. This means that all objects further down need to be cast. This has the distinct possibility of throwing aClassCastException
(javadoc) anywhere or everywhere in your code... unless you add a ton of boiler plate code to prevent that (guard conditions forinstanceof
all over).Whats worse, is that those exceptions (if you don't check everything - and the paths you follow if you do) are runtime errors. The errors of your types won't be found until you run the program rather than when you compile it. Errors found when you compile are easier to fix than ones you discover at runtime. For that matter, the static analysis tools and inspections that most IDEs give you will catch them for you as you type them (and throw up horrendous warnings about trying to do it with the abstract data type).
Overloads
Passing all of the arguments as a single ADT means you have a single method. In the example above, what if you had:
as different ways to call the function. With passing one ADT, you've got just
There is no way to differentiate the overloads. This leads to code that will look like:
The complexity of the function will go through the roof (unless you start extracting those to other methods that are somehow indicating what type they're dealing with and my head is starting to hurt thinking about the hungarian notation you're going to be sticking into the method names).
This further gets problematic when you have different types that are valid arguments.
Typo safety
Long ago, I was a perl programmer - back in the days before OOP found its way into perl (
bless
its reference). The only way to pass around complex objects was with%hashes
, and@arrays
. And you had to pull out the keys of the hash. There were numerous bugs that could only be caught at runtime (and the joys of autovivication made that challenging at times).A simple typo in a key would mean some parameter was there wasn't found (when it was there) or was just created in the wrong spot and passed to another method.
These are not fun bugs to have to find. No bugs are really fun, but these bugs just smack you in the face because they are completely preventable if you had just used a proper parameter list and work with the language and the compiler.
Calling Packaging
So far, I've talked about the dangers in the method itself. What about work that the callee has to do.
And we're done with the simple parameter list.
Now, you want to debug this? What are you passing into foo? You've got to look back through the code and track it. Besides being a significantly larger block of code to do any method call of this type, its also obscufcating what is going into this.
Refactors
foo
now takesDouble
s rather thanInteger
s. You change the method signature and fix all the compile time errors. You've found them all and it compiles correctly (see type safety above).However, the map version works just as well with
Integer
as it does withDouble
. You can't find out if you've fixed the refactoring or not.Why are they in a
Map
together?This is more a philosophical question. Why are these objects in a map together? What common attribute do they share? If they really are part of some other data structure
make them into a proper data structure that sticks together. If they aren't things that are honestly related to each other, well... don't.
Putting things together in some structure makes them related to each other in our mind, even if they aren't. If they aren't this adds significant mental gymnastics in order to keep what things are related to each other and what ones aren't apart.