tags:

views:

978

answers:

7

I want to store certain objects in a HashMap. The problem is, usually you just use a single object as a key. (You can, for example, use a String.) What I want to do it to use multiple object. For example, a Class and a String. Is there a simple and clean way to implement that?

+4  A: 

I tend to use a list

map.put(Arrays.asList(keyClass, keyString), value)
ILMTitan
Isn't the hashcode of Array.asList specific to that list?
e5
No, the hashcode of any `List` (or at least any `AbstractList`, which `Arrays.asList` is) is determined by the hashcodes of its elements. I just looked it up because I had the same thought.
Michael Myers
Easy to do, but it has the drawback of not documenting what belongs in the list. Was it class, string; or the string first? or the classname and the string?
Jens Schauder
True, but there are ways you can handle that, such as creating a static getHashList method that will do that for you. Also, I would certainly recommend Pierre's Key Class for longer lived maps.
ILMTitan
Another problem is that the implementations of hashCode and equals in the list are expensive compared with a well implemented custom class.
Stephen C
+2  A: 

You could create a holder class that contains the class and string that you want as the keys.

public class Key {

    public MyClass key_class;
    public String key_string;

    public Key(){
        key_class = new MyClass();
        key_string = "";
    }

}

Probably not the best solution, but a possibility.

Berek Bryan
Any class used as a key needs to override equals() and hashCode() properly.
notnoop
I like the approach, but adding a hashcode and equals method is mandantory for the use case. I also would make it an immutable.
Jens Schauder
@msaeed and @Jens Schauder, thanks for the pointers.
Berek Bryan
+2  A: 

The easiest way that I know of is to make a wrapper class and override hashmap and equals. For instance:

public class KeyClass {

    private String element1;
    private String element2;

    //boilerplate code here

    public boolean equals(Object obj) {
        if (obj instanceof KeyClass) {
            return element1.equals(((KeyClass)obj).element1) &&
                element2.equals(((KeyClass)obj).element2);
        }
        return false;
    }

    public int hashcode() {
        return (element1 + element2).hashcode();
    }
}

OF course, I would recommend using a StringBuilder and whatever else, but this way you have overridden the equals and hashcode, thereby allowing a hash and equality check on your multiple keys.

Also, I would recommend making the objects immutable (not editable) for safety's sake, but that is purely preference.

aperkins
+1  A: 

Do you mean that the object will be keyed by two keys, or rather a key which consists of two things.

If you want the first case. That is, an objected keyed by two keys, say a class or an object, you need to use two maps.

Map<Key1, value>

Map<Key2, value>

In the second case you need a map of maps, so:

Map<Key1, Map<Key2, value>>
e5
+3  A: 

You key must implement the hashCode and equals. If it is a SortedMap, it must also implements the Comparable interface

public class MyKey implements Comparable<MyKey>
{
private Integer i;
private String s;
public MyKey(Integer i,String s)
{
this.i=i;
this.s=s;
}

public Integer getI() { return i;}
public String getS() { return s;}

@Override
public int hashcode()
{
return i.hashcode()+31*s.hashcode();
}

@Override
public boolean equals(Object o)
{
if(o==this) return true;
if(o==null || !(o instanceof MyKey)) return false;
MyKey cp= MyKey.class.cast(o);
return i.equals(cp.i) && s.equals(cp.s);
    }

   public int compareTo(MyKey cp)
     {
     if(cp==this) return 0;
     int i= i.compareTo(cp.i);
     if(i!=0) return i;
     return s.compareTo(cp.s);
     }


 @Override
    public String toString()
       {
       return "("+i+";"+s+")";
       }

    }

public Map<MyKey,String> map= new HashMap<MyKey,String>();
map.put(new MyKey(1,"Hello"),"world");
Pierre
+1  A: 

Apache Commons Collections has a multikey map which might do the trick for you:

http://commons.apache.org/collections/api-release/org/apache/commons/collections/keyvalue/MultiKey.html

It looks like it will handle up to 5 "keys".

A: 

There are a few places where people suggest creating a "Key" class containing the others, I totally agree. Just thought I'd add a helpful hint.

If you use eclipse or netbeans, they have a nice option--you can tell Eclipse to create equals and hashcode methods based on one or more members. So you just select the member (or members) you want to retrieve by and NB creates most of the code you'd need to write for you.

of course when I just want to retrieve by one object, I often just delegate the hashcode and equals methods to that object (delegating equals might be problematic because it would mean that one of your "Key holder" classes would be equal to the object that is it's key, but that's pretty easily fixed (and wouldn't usually effect anything anyway)

so off the top of my head:

class KeyHolder {
    public final String key;
    public final Object storeMe;

    public KeyHolder(String key, Object storeMe) {
        this.key=key;
        this.storeMe=storeMe;
    }

    public equals(Object o) {
        return (o instanceof KeyHolder && ((KeyHolder)o).key.equals(key));
    }

    public hashcode() {
        return key.hashCode();
    }
}

That's all there is to it, and eclipse will do the last two for you if you ask it to.

By the way, I know that I have public members, a public final member is exactly the same thing as having a getter--not really a terrible idea. I'm starting to use this pattern on small utility classes like this a lot more lately. If the member wasn't final, it would be worse because it would be like having a setter (Something I try to avoid these days).

Bill K