views:

1123

answers:

11

I understand that only one instance of any object according to .equals() is allowed in a Set and that you shouldn't "need to" get an object from the Set if you already have an equivalent object, but I would still like to have a .get() method that returns the actual instance of the object in the Set (or null) given an equivalent object as a parameter.

Any ideas/theories as to why it was designed like this?

I usually have to hack around this by using a Map and making the key and the value same, or something like that.

EDIT: I don't think people understand my question so far. I want the exact object instance that is already in the set, not a possibly different object instance where .equals() returns true.

As to why I would want this behavior, typically .equals() does not take into account all the properties of the object. I want to provide some dummy lookup object and get back the actual object instance in the Set.

+1  A: 

Well, if you've already "got" the thing from the set, you don't need to get() it, do you? ;-)

Your approach of using a Map is The Right Thing, I think. It sounds like you're trying to "canonicalize" objects via their equals() method, which I've always accomplished using a Map as you suggest.

andersoj
I agree: For the Set it doesn't make sense to return a different object, since they are equal() anyway and that implies that they can be used interchangeably anyway. Semantically you'd want a Map from an Object to it's canonicalized ("interned" for Strings, for example) instance. The only un-usual think here would be that the key and value are the same object.
Joachim Sauer
+1  A: 

I think you've answered your own question: it is redundant.

Set provides Set#contains (Object o) which provides the equivalent identity test of your desired Set#get(Object o) and returns a boolean, as would be expected.

+8  A: 

The problem is: Set is not for "getting" objects, is for adding and test for presence. I understand what are you looking for, I had a similar situation and ended using a map of the same object in key and value.

GClaramunt
+1  A: 

"I want the exact object instance that is already in the set, not a possibly different object instance where .equals() returns true."

This doesn't make sense. Say you do:

Set<Foo> s = new Set<Foo>();
s.Add(new Foo(...));
...
Foo newFoo = ...;

You now do:

s.contains(newFoo)

If you want that to only be true if an object in the set is == newFoo, implement Foo's equals and hashCode with object identity. Or, if you're trying to map multiple equal objects to a canonical original, then a Map may be the right choice.

Matthew Flaschen
A: 

I think the expectation is that equals truely represent some equality, not simply that the two objects have the same primary key, for example. And if equals represented two really equal objects, then a get would be redundant. The use case you want suggests a Map, and perhaps a different value for the key, something that represents a primary key, rather than the whole object, and then properly implement equals and hashcode accordingly.

Yishai
not true, at least not for String, as we know. (OK, not a real difference if they have the same content)
Carlos Heuberger
+6  A: 

While the purity argument does make the method get(Object) suspect, the underlying intent is not moot.

There are various class and interface families that slightly redefine equals(Object). One need look no further than the collections interfaces. For example, an ArrayList and a LinkedList can be equal; their respective contents merely need to be the same and in the same order.

Consequently, there are very good reasons for finding the matching element in a set. Perhaps a clearer way of indicating intent is to have a method like

public interface Collection<E> extends ... {
  ...
  public E findMatch(Object o) throws UnsupportedOperationException;
  ...
}

Note that this API has value broader that within Set.

As to the question itself, I don't have any theory as to why such an operation was omitted. I will say that the minimal spanning set argument does not hold, because many operations defined in the collections APIs are motivated by convenience and efficiency.

Dilum Ranatunga
Finally an answer that doesn't dismiss the question out of hand. +1
Konrad Rudolph
+1  A: 

I'm not sure if you're looking for an explanation of why Sets behave this way, or for a simple solution to the problem it poses. Other answers dealt with the former, so here's a suggestion for the latter.

You can iterate over the Set's elements and test each one of them for equality using the equals() method. It's easy to implement and hardly error-prone. Obviously if you're not sure if the element is in the set or not, check with the contains() method beforehand.

This isn't efficient compared to, for example, HashSet's contains() method, which does "find" the stored element, but won't return it. If your sets may contain many elements it might even be a reason to use a "heavier" workaround like the map implementation you mentioned. However, if it's that important for you (and I do see the benefit of having this ability), it's probably worth it.

Oz
Yes, of course I can iterate over all the elements, but then the complexity of finding the object instance becomes linear, which is very bad, especially if the backing set is a HashSet. You are right, I was looking for an explanation of why Sets behave that way, not a workaround.
GreenieMeanie
A: 

Functional Java has an implementation of a persistent Set (backed by a red/black tree) that incidentally includes a split method that seems to do kind of what you want. It returns a triplet of:

  1. The set of all elements that appear before the found object.
  2. An object of type Option that is either empty or contains the found object if it exists in the set.
  3. The set of all elements that appear after the found object.

You would do something like this:

MyElementType found = hayStack.split(needle)._2().orSome(hay);
Apocalisp
+2  A: 

So I understand that you may have two equal objects but they are not the same instance.

Such as

Integer a = new Integer(3);
Integer b = new Integer(3);

In which case a.equals(b) because they refer to the same intrinsic value but a != b because they are two different objects.

There are other implementations of Set, such as IdentitySet, which do a different comparison between items.

However, I think that you are trying to apply a different philosophy to Java. If your objects are equal (a.equals(b)) although a and b have a different state or meaning, there is something wrong here. You may want to split that class into two or more semantic classes which implement a common interface - or maybe reconsider .equals and .hashCode.

If you have Joshua Bloch's Effective Java, have a look at the chapters called "Obey the general contract when overriding equals" and "Minimize mutability".

Sorin Mocanu
A: 

Just use the Map solution... a TreeSet and a HashSet also do it since they are backed up by a TreeMap and a HashMap, so there is no penalty in doing so (actualy it should be a minimal gain).

You may also extend your favorite Set to add the get() method.

[]]

Carlos Heuberger
A: 

I think your only solution, given some Set implementation, is to iterate over its elements to find one that is equals() -- then you have the actual object in the Set that matched.

K target = ...;
Set<K> set = ...;
for (K element : set) {
  if (target.equals(element)) {
    return element;
  }
}
Sean Owen