64

I've seen other questions about getting objects from Set's based on index value and I understand why that is not possible. But I haven't been able to find a good explanation for why a get by object is not allowed so thought I would ask.

HashSet is backed by a HashMap so getting an object from it should be pretty straightforward. As it is now, it appears I would have to iterate over each item in the HashSet and test for equality which seems unnecessary.

I could just use a Map but I have no need for a key:value pair, I just need a Set.

For example say I have Foo.java:

package example;

import java.io.Serializable;

public class Foo implements Serializable {

    String _id;
    String _description;

    public Foo(String id){
        this._id = id
    }

    public void setDescription(String description){
        this._description = description;
    }

    public String getDescription(){
        return this._description;
    }

    public boolean equals(Object obj) {
        //equals code, checks if id's are equal
    }

    public int hashCode() {
        //hash code calculation
    }

}

and Example.java:

package example;

import java.util.HashSet;

public class Example {

    public static void main(String[] args){
        HashSet<Foo> set = new HashSet<Foo>();

        Foo foo1 = new Foo("1");
        foo1.setDescription("Number 1");

        set.add(foo1);
        set.add(new Foo("2"));

        //I want to get the object stored in the Set, so I construct a object that is 'equal' to the one I want.
        Foo theFoo = set.get(new Foo("1")); //Is there a reason this is not allowed?
        System.out.println(theFoo.getDescription); //Should print Number 1
    }

}

Is it because the equals method is meant to test for "absolute" equality rather than "logical" equality (in which case contains(Object o) would be sufficient)?

FGreg
  • 14,110
  • 10
  • 68
  • 110
  • 1
    You have list if you want index. HashSet can not guarantee insertion order so no point in get method. What are you missing is implementing `equals` and use `contains()` which will iterate and find the object. – Amit Deshpande Dec 13 '12 at 15:57
  • 3
    Related: http://stackoverflow.com/questions/12670292/get-an-item-from-a-java-set – assylias Dec 13 '12 at 16:00
  • @assylias That is extremely close the scenario I am asking about. However, I don't like that the answer is to include a third party library. – FGreg Dec 13 '12 at 16:07
  • 1
    In HashSet it is almost there... `set.contains` delegates to `map.contains`, which does: `return getEntry(key) != null;` where `getEntry(key).getKey();` is what you are looking for... Not sure why it was not included... – assylias Dec 13 '12 at 16:10
  • 2
    Admittedly, HashMap does not provide such a method either... – assylias Dec 13 '12 at 16:11
  • Yes, I think `HashMap` with equal key:value is the way I have to go for now. Just seems like HashSet should have this functionality as well. – FGreg Dec 13 '12 at 16:28
  • I agree that we should be able to get by index. I am surprised it's not there even though the order cannot be guaranteed. I'm thinking off random operations but also possibilities we do know the index and just want to get that element in the set. A delegate or toArray still iterates over the set which might be problematic for very large sets. – Madmenyo Jul 17 '15 at 12:17

12 Answers12

52

Java Map/Collection Cheat Sheet

Will it contain key/value pair or values only?

1) If it contains pairs, the choice is a map. Is order important?

. 1-1) If yes, follow insertion order or sort by keys?

. . 1-1-1) If ordered, LinkedHashMap

. . 1-1-2) If sorted, TreeMap

. 1-2) If order is not important, HashMap

2) If it stores only values, the choice is a collection. Will it contain duplicates?

. 2-1) If yes, ArrayList

. 2-2) If it will not contain duplicates, is primary task searching for elements (contains/remove)?

. . 2-2-1) If no, ArrayList

. . 2-2-2) If yes, is order important?

. . . 2-2-2-1) If order is not important, HashSet

. . . 2-2-2-2) If yes, follow insertion order or sort by values?

. . . . 2-2-2-2-1) if ordered, LinkedHashSet

. . . . 2-2-2-2-2) if sorted, TreeSet

Rasshu
  • 1,764
  • 6
  • 22
  • 53
31

A Set is a Collection of objects which treats a.equals(b) == true as duplicates, so it doesn't make sense to try to get the same object you already have.

If you are trying to get(Object) from a collection, a Map is likely to be more appropriate.

What you should write is

Map<String, String> map = new LinkedHashMap<>();

map.put("1", "Number 1");
map.put("2", null);
String description = map.get("1");

if an object is not in the set (based on equals), add it, if it is in the set (based on equals) give me the set's instance of that object

In the unlikely event you need this you can use a Map.

Map<Bar, Bar> map = // LinkedHashMap or ConcurrentHashMap

Bar bar1 = new Bar(1);
map.put(bar1, bar1);

Bar bar1a = map.get(new Bar(1));
raj240
  • 646
  • 7
  • 17
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 42
    I can see why one could want something like: if an object is not in the set (based on equals), add it, if it is in the set (based on equals) give me the set's instance of that object. – assylias Dec 13 '12 at 15:57
  • 26
    Sort of... except it actually *could* be useful, if you're effectively canonicalizing. You wouldn't be getting the *same* object - you'd be getting an *equal* object. – Jon Skeet Dec 13 '12 at 15:57
  • 8
    What I was trying to present in the example was the idea that the object has an identifier (which controls it's equality to other `Foo` types) plus any number of other fields that do not contribute to it's identity. I want to be able to get the object from the Set (which includes the extra fields) by constructing an 'equal' `Foo` object. – FGreg Dec 13 '12 at 16:10
  • 2
    The whole point of a `Set` is that `equal` elements are interchangeable. It sounds like what you want is a `Map`. – Louis Wasserman Dec 13 '12 at 17:08
  • 2
    @FGreg Or even `Map` as the key is really a String. You shouldn't be confusing what you use to lookup with what you lookup. – Peter Lawrey Dec 13 '12 at 17:36
  • I guess the answer I'm looking for is that in terms of a Set, two `equal()` objects need to be identical; not just logically equivalent. That is more or less what your first sentence reads so I will give you the answer. – FGreg Jan 11 '13 at 15:01
  • Am I completely off base here... but a Set does NOT contain equal objects. A Set restricts the adding of duplicates to it, but it absolutely does NOT contain equal objects. That's the hard and fast rule of a Set is that it can't contain duplicate objects (based on their equals method). – Kevin M May 18 '15 at 15:56
  • 1
    It makes perfect sense to have a `get` method. Just because two objects are `true` when `equals()` is called, it doesn't mean there aren't differences. You may want to do certain tasks for objects that are `equal` but have differences in fields that do not make them `unequal`. – Hooli Sep 01 '16 at 10:40
  • @Hooli in that case, you should be using a Map where the keys have the fields which are used for comparison. – Peter Lawrey Sep 01 '16 at 13:19
  • @PeterLawrey: That's a terrible idea it's literally double the memory wasted just for the sake of indexing when `HashSet` already has an index it uses for `contains`. It would be much better if there were a `Set` implementation WITH a `get(Object o)` of its own – Hooli Sep 07 '16 at 17:39
  • 1
    @Hooli Set implementations wrap Map implementation where the elements are the keySet() and values are Boolean. Using a Set can use more than Map in some use cases. You should be using the data structure which naturally fits your problem, and worry about memory later because often your assumptions can be incorrect. – Peter Lawrey Sep 07 '16 at 18:19
5

Your last sentence is the answer.

get(Object o) would run through the HashSet looking for another object being equal to o (using equals(o) method). So it is indeed the same as contains(o), only not returning the same result.

xlecoustillier
  • 16,183
  • 14
  • 60
  • 85
5

If you want to know that new Foo("1"); object is already present in the set then you need to use contains method as:

boolean present =  set.contains(new Foo("1"));

The get kind of method i.e. set.get(new Foo("1")); is not supported because it doesn't make sense. You are already having the object i.e. new Foo("1") then what extra information you would be looking through get method.

Yogendra Singh
  • 33,927
  • 6
  • 63
  • 73
  • 12
    *You are already having the object i.e. new Foo("1")* => not exactly - he has another instance which happens to be equals but it still is a different object. – assylias Dec 13 '12 at 16:07
  • @assylias What I meant is: before calling get, `new Foo("1")` instance is created. Whether you write like, `Foo one = new Foo("1");set.get(one);` or `set.get(new Foo("1"));`, you have the `new Foo("1")` is created first. Now, when you have `new Foo("1")`, what extra is required other than knowing the presence? – Yogendra Singh Dec 13 '12 at 16:15
  • 2
    The extra fields on the original `Foo` object in the `Set` are what I'm after. Creating `new Foo("1")` equals the object in the Set but does not contain the extra information that the object in the Set contains. – FGreg Dec 13 '12 at 16:30
  • 1
    @FGreg: I got your requirement now. In this case, I think you need to iterate the set or use a HashMap against the key and your full object. This doesn't seem to be common requirement, hence it wasn't supported directly. – Yogendra Singh Dec 13 '12 at 16:33
1

Set, is mostly used to eliminate duplicates for simple objects like String or Integer. For a complicated object, I would first see if a primary key exists for the Object discussed. If we have a key, then using Map makes more sense. BTW, for the Java 8 one-liner mentioned above, I think when dealing with simple objects like Integer with autoboxing, it might be even simpler to use:

int i=testSet.stream().filter(o -> o.equals(1)).findFirst().get();

Also, I think pre-Java8 style won't be a bad choice either: just use set.contains() is more straightforward without stream():

if( set.contains(1) ) {...};
cconsta1
  • 737
  • 1
  • 6
  • 20
Wayne Fang
  • 11
  • 1
0

HashSet is a little bit simplier than HashMap. If you don't need the features of HashMap, why use it? If the method like getObject(ObjectType o) was implemented by Java we dont need to iterate over the set after calling contain() methode...

user07
  • 319
  • 2
  • 8
0

The reason why there is no get is simple:

If you need to get the object X from the set is because you need something from X and you dont have the object.

If you do not have the object then you need some means (key) to locate it. ..its name, a number what ever. Thats what maps are for right.

map.get( "key" ) -> X!

Sets do not have keys, you need yo traverse them to get the objects.

So, why not add a handy get( X ) -> X

That makes no sense right, because you have X already, purist will say.

But now look at it as non purist, and see if you really want this:

Say I make object Y, wich matches the equals of X, so that set.get(Y)->X. Volia, then I can access the data of X that I didn have. Say for example X has a method called get flag() and I want the result of that.

Now look at this code.

Y

X = map.get( Y );

So Y.equals( x ) true!

but..

Y.flag() == X.flag() = false. ( Were not they equals ?)

So, you see, if set allowed you to get the objects like that It surely is to break the basic semantic of the equals. Later you are going to live with little clones of X all claming that they are the same when they are not.

You need a map, to store stuff and use a key to retrieve it.

Alex Vaz
  • 496
  • 5
  • 8
0

A common use case of a get method on Set might be to implement an intern set. If that's what you're trying to achieve, consider using the Interner interface and Interners factory from Google Guava.

pburka
  • 1,434
  • 9
  • 12
0

if you only want know what are in the Hashset, you can use .toString(); method to display all Hashset Contents separated by comma.

Lins Louis
  • 2,393
  • 2
  • 25
  • 30
0

I've got the same problem as the thread author and I've got a real reason why a Set should have a get method: I overwrote equals of e.g. X, the content of the set Set and so the contained object is not necessarily the same as the checked one. In my scenario I'll remove semantic doubles in an other collection and enrich the "original" with some relations of the "double" so I need the "original" to be able to drop the double.

user5101998
  • 209
  • 3
  • 9
0

get(Object o) is useful when we have one information linked to other information just like key value pair found in HashMap .So using get() method on one information we can get the second information or vice-versa.

Now, if HashSet provides get(Object o) method you need to pass an object. So if you have the object to pass to the get(Object o) method that means you already have the object, then what is need of get(Object o) method.

Saheb
  • 51
  • 6
-1

As everyone mentioned before, there is no such method and for good reasons. That being said, if you wish to get a certain object from a HashSet in java 8 using a one-liner (almost), simply use streams. In your case, it would be something like:

Foo existing = set.stream().filter(o -> o.equals(new Foo("1"))).collect(Collectors.toList()).iterator().next();

Note that an exception will be thrown if the element doesn't exist so it is technically not a one-liner, though if the filter is properly implemented it should be faster than a traditional iteration over the collection elements.

Nolf
  • 113
  • 1
  • 10