开发者

Better type safety in Java collections

开发者 https://www.devze.com 2022-12-24 07:21 出处:网络
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 t

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 //M开发者_高级运维ap<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?


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


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.


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.


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.


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


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消