In my java coding, I often end up with several Map<String,Map<String,foo>>
or Map<String,List<String>>
and then I have trouble remembering which String is which key. I comment the declaration with //Map<capabiltyId,Map<groupId,foo>>
or //Map<groupId,List<capabilityId>
, but it's not the greatest solution. If String wasn't final, I would make new classes CapabilityId extends String
and GroupId extends String
, but I can't. Is there a better way to keep track of which thing is the key and maybe have the compiler enforce it?
views:
153answers:
7Wrap strings in wrapper-classes if you want:
class GroupId implements Comparable {
private String groupId;
public GroupId (String groupId) {
this.groupId = groupId;
}
...
}
Map<GroupId, List<CapabilityId>> m = ...
Create an ID
class which you can subclass, and which consists of a String
field and implementations of equals()
and hashCode()
which use that field.
Instead of having CapabilityId
extend String
, CapabilityId
could include a String
field called "id"; then your Map
could be defined as Map<CapabilityId, Map<GroupId, Foo>>
, and you could get at the individual ID fields through a getId()
on your key classes.
I'm not sure I would do this myself, but if I did, this is probably what I'd do.
You could limit the clutter by having an abstract GenericId
class with an id field and getId()
method, and have CapabilityId
and GroupId
inherit from it.
Instead of Map<String,List<String>>
you should use Multimap from Google Guava / Google Collection
Adding to the other answers:
Wrap it.
It is not just a solution to your problem but a good idea in general, i.e. avoid simple parameters. Your code will gain readability, sanity and maintainability. You can add all kinds of nice properties to it, e.g. declare it @Immutable. As you found out it this way is better to remember and to control. You own the class and can do whatever you like with it.
I would put it all in single class and make use of sensible field/method/argument names.
public class GroupCapabilities {
private Map<String, Map<String, Group>> groupCapabilities;
public void addGroup(String capabilityId, Group group) {
Map<String, Group> groups = groupCapabilities.get(capabilityId);
if (groups = null) {
groups = new HashMap<String, Group>();
groupCapabilities.put(capabilityId, group);
}
groups.put(group.getId(), group);
}
public Map<String, Group> getGroups(String capabilityId) {
return groupCapabilities.get(capabilityId);
}
public Group getGroup(String capabilityId, String groupId) {
Map<String, Group> groups = groupCapabilities.get(capabilityId);
return (groups != null) ? groups.get(groupId) : null;
}
// Etc..
}
This way the you can see at method/argument names what it expects/returns.
There are a number of ways to go on this one (some already mentioned):
- As @Roman, wrap the general purpose type in a more specific type, which gives stronger typing. Strong typing good, IMO.
- As @nanda, use a more specific collection type. The Java library is a little poor in this area. It depends about how you feel about dependencies.
- As @BalusC, move all the icky stuff into an icky class. Doesn't really remove the problem, but it does contain it (like in Ghostbusters).
Map<String,Map<String,foo>>
looks very much like you have a composite key, i.e. a key that comprises two parts. So, introduce an immutable composite key class, that is a value object representing the two component value objects.