Paul Tomblin
Paul Tomblin

Reputation: 182802

Better type safety in Java collections

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?

Upvotes: 5

Views: 770

Answers (7)

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147164

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.

Upvotes: 1

BalusC
BalusC

Reputation: 1109132

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.

Upvotes: 2

openCage
openCage

Reputation: 2775

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.

Upvotes: 0

nanda
nanda

Reputation: 24788

Instead of Map<String,List<String>> you should use Multimap from Google Guava / Google Collection

http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/collect/Multimap.html

Upvotes: 0

Jim Kiley
Jim Kiley

Reputation: 3652

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.

Upvotes: 7

Michael Borgwardt
Michael Borgwardt

Reputation: 346377

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.

Upvotes: 3

Roman
Roman

Reputation: 66196

Wrap 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 = ...

Upvotes: 9

Related Questions