432

Why doesn't Set provide an operation to get an element that equals another element?

Set<Foo> set = ...;
...
Foo foo = new Foo(1, 2, 3);
Foo bar = set.get(foo);   // get the Foo element from the Set that equals foo

I can ask whether the Set contains an element equal to bar, so why can't I get that element? :(

To clarify, the equals method is overridden, but it only checks one of the fields, not all. So two Foo objects that are considered equal can actually have different values, that's why I can't just use foo.

Nathan
  • 8,093
  • 8
  • 50
  • 76
foobar
  • 4,968
  • 3
  • 17
  • 11
  • 2
    This post is already widely discussed, and good answers have been suggested. However if you're just looking for an ordered set, simply use `SortedSet` and its implementations, which are map-based (e.g. `TreeSet` allows for accessing `first()`). – Eliran Malka Feb 10 '14 at 15:08
  • 3
    I miss that method, too, for exactly the same case you described above. Objective-C (`NSSet`) has such a method. It is called `member` and it returns the object within the set that compares "equal" to the parameter of the `member` method (which may of course be a different object and also have different properties, that equal may not check). – Mecki Mar 06 '15 at 17:31

22 Answers22

466

To answer the precise question "Why doesn't Set provide an operation to get an element that equals another element?", the answer would be: because the designers of the collection framework were not very forward looking. They didn't anticipate your very legitimate use case, naively tried to "model the mathematical set abstraction" (from the javadoc) and simply forgot to add the useful get() method.

Now to the implied question "how do you get the element then": I think the best solution is to use a Map<E,E> instead of a Set<E>, to map the elements to themselves. In that way, you can efficiently retrieve an element from the "set", because the get() method of the Map will find the element using an efficient hash table or tree algorithm. If you wanted, you could write your own implementation of Set that offers the additional get() method, encapsulating the Map.

The following answers are in my opinion bad or wrong:

"You don't need to get the element, because you already have an equal object": the assertion is wrong, as you already showed in the question. Two objects that are equal still can have different state that is not relevant to the object equality. The goal is to get access to this state of the element contained in the Set, not the state of the object used as a "query".

"You have no other option but to use the iterator": that is a linear search over a collection which is totally inefficient for large sets (ironically, internally the Set is organized as hash map or tree that could be queried efficiently). Don't do it! I have seen severe performance problems in real-life systems by using that approach. In my opinion what is terrible about the missing get() method is not so much that it is a bit cumbersome to work around it, but that most programmers will use the linear search approach without thinking of the implications.

jschreiner
  • 4,840
  • 2
  • 12
  • 16
  • 31
    meh. Overriding the implementation of equals so that non-equal objects are "equal" is the problem here. Asking for a method that says "get me the the identical object to this object", and then expect a non-identical object to be returned seems crazy and easy to cause maintenance problems. As others have suggested, using a map solves all of these problems: and it makes what you are doing self-explanatory. It's easy to understand that two non-equal objects might have the same key in a map, and having the same key would show the relationship between them. – David Ogren Feb 10 '14 at 22:27
  • 33
    Strong words, @David Ogren. Meh? Crazy? But in your comment, you are using the words "identical" and "equal" as if they meant the same thing. They do not. Specifically, in Java, identity is expressed by the "==" operator and equality is expressed by the equals() method. If they meant the same thing, there wouldn't be the need for an equals() method at all. In other languages, this can of course be different. For example, in Groovy, identity is the is() method, and equality is "==". Funny, isn't it? – jschreiner Feb 27 '14 at 14:11
  • 17
    Your criticism of my use of the word identical when I should have used the word equivalent is very valid. But defining equals on an object so that Foo and Bar are "equal" but are not "equal enough" for him to use them equivalently is going to create all kinds of problems both with both functionality and with readability/maintainability. This issue with Set just the tip of the iceberg for potential problems. For example, equal objects must have equal hash codes. So he's going to have potential hash collisions. Is it crazy to object calling .get(foo) specifically to get something other than foo? – David Ogren Feb 27 '14 at 14:47
  • 18
    It is probably worth noting that, e.g., HashSet is implemented as a wrapper around a HashMap (that maps the keys to a dummy value). So, using a HashMap explicitly instead of a HashSet would not cause overhead in the memory use. – Alexey B. Jun 19 '14 at 23:20
  • 2
    @DavidOgren It makes plenty of sense. I can give you concrete example. WeakHashSet (which does not exist, sadly) to make a cache of weak objects. Then you have something that makes instances of something, like getClass().getMethods(). This will create many identical copies of Method instance that you don't want to, but you want to have one, the one that you stored in the set. Thus, set.get(object) would make sense. – Enerccio Apr 22 '15 at 15:46
  • 2
    @DavidOgren: Just another use case where your assertion that "equals" is simply too lax doesn't hold: I have immutable objects and want to avoid a proliferation of duplicates. The objects are already stored in a Set (well, actually as keys of a Map, but the problem is the same), so it would be helpful to reuse the objects from the Set elsewhere rather than the objects that were newly created from database values. – user686249 Apr 29 '15 at 13:00
  • 5
    @user686249 I feel like this has devolved into just an academic debate. I do concede that I might have been overboard in objecting to overriding equals. Especially in a use like yours. But, I still have an objection to the idea of calling this method `get()`. In your example, I would be very confused by customerSet.get(thisCustomer). (Whereas, a Map, as suggested by many answers) would be just fine with canonicalCustomerMap.get(this customer). I'd also be OK with a method that is more clearly named (such as Objective-C's member method on NSSet). – David Ogren Apr 30 '15 at 13:34
  • @user686249 (I missed the edit window on my previous comment) I concede the point. Thinking about my comment made me realize I got too hung up on the method name "get()" and wasn't thinking about the actual question (the value of a method like this existing). I still think a Map is a decent workaround, but I concede the value of the method. – David Ogren Apr 30 '15 at 13:43
  • 1
    Wanted to add a comment echoing similar user686249; This kind of stuff comes up constantly when monitoring object burn and garbage collection (doesn't matter if its short term / long term). Why is my process burning through thousands of objects? Oh because I am creating a bunch of temporary objects and relying on .equals and just replacing the existing object with a new one or performing a .contains check and returning the new one. Why didn't you just grab the existing? Because I can't.... Don't use a Set then. – mhoglan Sep 04 '15 at 14:42
  • 1
    "not very forward looking" ... I've come to the view that this is a bit harsh: see my answer and suggestion for a `GettableSet`, and why you would only want to implement something like that in conjunction with a `NoVisibleConstructor` class of element... – mike rodent Mar 19 '17 at 12:03
  • 1
    @mikerodent your suggested solution for the simple task of "getting an element from a set" seems quite complex. For me personally that only reinforces my opinion that java.util.Set is not well designed. – jschreiner Mar 20 '17 at 15:41
  • @jschreiner I think you're complaining not about Java but about the inherent logic involved here. In fact, once you've designed the GettableHashSet the rest is (as I show) very easy: no visible constructor combined with a factory method. Are you thinking of a particular language which does this better, in your opinion? – mike rodent Mar 20 '17 at 20:22
  • @mikerodent For example std::set in C++ has a find() method that does exactly that. – jschreiner Mar 21 '17 at 08:12
  • 1
    I'd say that method should be called findEqualTo(). I also have a problem with Java's Set.contains(): I think it should be called containsEqualTo(). I think the Java designers of Set probably realised the danger of including a findEqualTo() method. As others have said elsewhere, having multiple Set elements co-existing (outside the Set) which are ***equal but not identical*** is the problem. To me it is a non-trivial problem which shouldn't really be dismissed with an assumption that the designers were dogmatic or lacked foresight... unless you can find evidence to back up that claim... – mike rodent Mar 21 '17 at 09:09
  • Another very legitimate use case is to avoid maintaining duplicate object that contains exactly all the same properties, but different identifiers (different location in the memory). – justhalf May 02 '17 at 03:35
  • @jschreiner, regarding DavidOgrens comment, you wrote "But in your comment, you are using the words 'identical' and 'equal' as if they meant the same thing. They do not. Specifically, in Java, identity is expressed by the '==' operator and equality is expressed by the equals() method." I'd say 'identical' and 'equal' should be considered the same thing (they shouldn't be equal unless they're identical). I think *you* used a bad terminology when you refer to 'identical' and 'identity' as the same thing! '==' means *the same as* which is semantically different from both 'identical' and 'equal'. – aioobe Aug 19 '17 at 22:38
  • @aioobe According to the Merriam-Webster dictionary, the definition of "identical" is "being the same". It seems like my terminology is fine. – jschreiner Jul 06 '18 at 12:54
  • If you're referring to [this](https://www.merriam-webster.com/dictionary/identical) there are several definitions. In the context of discussing Java objects I'd say that the second definition (_"having such close resemblance as to be essentially the same"_) is more reasonable. IMHO two objects can indeed be _identical_ without being _the same_. – aioobe Jul 06 '18 at 20:21
  • A very simple use case: what if I want to correct the capitalization of an input using case insensitive matching to a set? Set caseInsensitiveSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); – Jared Sep 13 '18 at 17:14
  • I'm dealing with a nice use case for a Set with mutable elements: Multiple cached image renderers. They are instantiated with their three parameters (which are used in equals/hashcode). I look if I already have such an instance in my Set. If yes, I ask that instance for the image (And thus NEED that instance. I'll use a Map now.), which will probably come from its cache-field. Else I put the new instance into the Set and ask THAT for the image. – Dreamspace President Aug 14 '20 at 10:44
144

There would be no point of getting the element if it is equal. A Map is better suited for this usecase.


If you still want to find the element you have no other option but to use the iterator:

public static void main(String[] args) {

    Set<Foo> set = new HashSet<Foo>();
    set.add(new Foo("Hello"));

    for (Iterator<Foo> it = set.iterator(); it.hasNext(); ) {
        Foo f = it.next();
        if (f.equals(new Foo("Hello")))
            System.out.println("foo found");
    }
}

static class Foo {
    String string;
    Foo(String string) {
        this.string = string;
    }
    @Override
    public int hashCode() { 
        return string.hashCode(); 
    }
    @Override
    public boolean equals(Object obj) {
        return string.equals(((Foo) obj).string);
    }
}
dacwe
  • 43,066
  • 12
  • 116
  • 140
  • 277
    There absolutely could be a point to getting the element. What if you wish to update some of the element's values after it has already been added to the Set? For example, when .equals() does not use all of the fields, as the OP specified. A less efficient solution would be to remove the element and re-add it with its values updated. – KyleM Feb 19 '13 at 17:48
  • 16
    I would still argue that a `Map` is better suited (`Map` in this case.) – dacwe Feb 19 '13 at 20:48
  • 1
    I like this explanation... for some reason, I've never thought to use a Map like this. – Justin Smith Oct 08 '13 at 19:04
  • 26
    @dacwe, I got here because I started looking for a way to avoid exactly that! An object that acts, at the same time, both as a key and corresponding value is exactly what a set should be all about. In my case, I'd like to get some complex object from a set by key (String). This String is encapsulated (and unique) to the object being mapped. In fact, the whole object 'revolves' around said key. Furthermore, the caller knows said String, but not the object itself; that's exactly why it wants to retrieve it by key. I'm using a Map now of course, but it remains odd behaviour. – pauluss86 Jan 16 '14 at 19:29
  • 4
    @KyleM I understand the use case, but I want to stress the importance of not touching attributes that are part of hashCode/equals. From the Set Javadoc: "Note: Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set." -- I recommend those objects to be immutable, or at least have immutable key attributes. – stivlo Feb 26 '15 at 17:33
  • 6
    I agree that you can use `Map` as a replacement, the downside is that a map always must store at least a key and a value (and for performance it should also store the hash), while a set can get away just storing the value (and maybe hash for performance). So a good set implementation can be equally fast to `Map` but use up to 50% less memory. In case of Java it won't matter, as the HashSet is internally based on HashMap anyway. – Mecki Mar 06 '15 at 17:38
  • 1
    its a good answer. A problem I might worry about with a map is that they are not unique by default. Sets ensure each element (it would be key in the map) is unique. – roberto tomás Apr 19 '16 at 14:40
  • @robertotomás, Map key collection is always a set. So when you have a structure Map, it can not store duplicate Foo objects up to its equality method! – qartal Dec 17 '16 at 18:17
  • 1
    Another use case would be to access an interned pool of value objects. – gary Aug 17 '17 at 15:21
  • @gary, if you want two equal objects to be the same object, I would suggest using a [factory](https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)). – aioobe Aug 19 '17 at 22:25
  • The Set class lacks E addIfAbsent(E) method, a similar one to the Map.putIfAbsent method. – 0kcats Jan 04 '19 at 19:17
  • @aioobe Exactly. However, a non-duplicating factory needs to be backed by... an accessible `Set` that OP is asking for! You've just invented catch-22. – Agent_L Jul 12 '21 at 09:44
  • @Agent_L, nah, you can use a `Map` from `` to actual object created. – aioobe Jul 12 '21 at 12:57
  • @aioobe A map that maps every key to itself - again, that's the `Set` that OP is asking for. – Agent_L Jul 12 '21 at 17:15
  • @Agent_L, no, no need complicate it like that. it would simply map the argument required to create the object, to the previously created object. `static Map existing = new HashMap<>();` and then the factory method would look like `static Person createPerson(String name) { return existing.computeIfAbsent(name, Person::new); }` – aioobe Jul 14 '21 at 17:56
  • @aioobe It's you who's complicating. All I want is to check if a given `Person` already exists (possible) and use it (not). That's what `equals` is for. Obviously, one `String` is not enough to compare `Person` objects. "Having together 'whatever parameters were used to create it'" is precisely THE reason to have the class we're talking about. The purpose of this class is to be a key for maps, don't tell to not use it as a key : ) – Agent_L Jul 14 '21 at 18:30
  • @Agent_L I don't follow. You said _"However, a non-duplicating factory needs to be backed by... an accessible Set that OP is asking for! You've just invented catch-22"_ That is not true as I showed in my previous comment. Then you say _"All I want is to check if a given Person already exists (possible) and use it (not)."_ If you have a `Person` object, then surely it exists. Could you clarify? It should be perfectly fine to use `Person` as a key in a map, as long as it's not mutable and so on. – aioobe Jul 14 '21 at 19:14
  • @aioobe if you have more than 1 argument, you need to bind them together in order to make a key. And that key object in most cases is the outcome object, like a `Person` that has first, last and maiden names. So a factory always creates new object and then deduplicates it using a hash-smth. Eg. a `Map` that maps every object to itself. Something that `Set` should do, but it doesn't. – Agent_L Jul 15 '21 at 10:02
  • Ok, I understand your line of thought. If a `Person` is nothing but a bunch of final references to immutable objects however, then I see no reason to try so hard to reuse previously created instances. (I.e. the _"You don't need to get the element, because you already have an equal object"_ quote from jschreiner's answer holds.) – aioobe Jul 15 '21 at 18:48
38

If you have an equal object, why do you need the one from the set? If it is "equal" only by a key, an Map would be a better choice.

Anyway, the following will do it:

Foo getEqual(Foo sample, Set<Foo> all) {
  for (Foo one : all) {
    if (one.equals(sample)) {
      return one;
    }
  } 
  return null;
}

With Java 8 this can become a one liner:

return all.stream().filter(sample::equals).findAny().orElse(null);
Nathan
  • 8,093
  • 8
  • 50
  • 76
Arne Burmeister
  • 20,046
  • 8
  • 53
  • 94
  • I like this answer better, i would just avoid using two return statements, since that is against OOP and it makes the Cyclomatic Complexity value higher. – Leo Dec 16 '16 at 18:21
  • 9
    @Leo thanks, but single exit paradigm is not against OOP and is mostly invalid for languages more modern than Fortran or COBOL, see also http://softwareengineering.stackexchange.com/questions/118703/where-did-the-notion-of-one-return-only-come-from# – Arne Burmeister Dec 17 '16 at 00:39
  • 1
    Using a Map instead of a Set does seem like a better option: iterating over the elements of a Set is more work than getting a single value from a Map. (O(N) vs O(1)) – Jamie Flournoy Oct 05 '18 at 21:16
  • @JamieFlournoy right, if you have to check the same set for different elements multiple times that’s much better. For single usage not, as it requires more effort to build the map first. – Arne Burmeister Oct 05 '18 at 21:20
21

Default Set in Java is, unfortunately, not designed to provide a "get" operation, as jschreiner accurately explained.

The solutions of using an iterator to find the element of interest (suggested by dacwe) or to remove the element and re-add it with its values updated (suggested by KyleM), could work, but can be very inefficient.

Overriding the implementation of equals so that non-equal objects are "equal", as stated correctly by David Ogren, can easily cause maintenance problems.

And using a Map as an explicit replacement (as suggested by many), imho, makes the code less elegant.

If the goal is to get access to the original instance of the element contained in the set (hope I understood correctly your use case), here is another possible solution.


I personally had your same need while developing a client-server videogame with Java. In my case, each client had copies of the components stored in the server and the problem was whenever a client needed to modify an object of the server.

Passing an object through the internet meant that the client had different instances of that object anyway. In order to match this "copied" instance with the original one, I decided to use Java UUIDs.

So I created an abstract class UniqueItem, which automatically gives a random unique id to each instance of its subclasses.

This UUID is shared between the client and the server instance, so this way it could be easy to match them by simply using a Map.

However directly using a Map in a similar usecase was still inelegant. Someone might argue that using an Map might be more complicated to mantain and handle.

For these reasons I implemented a library called MagicSet, that makes the usage of an Map "transparent" to the developer.

https://github.com/ricpacca/magicset


Like the original Java HashSet, a MagicHashSet (which is one of the implementations of MagicSet provided in the library) uses a backing HashMap, but instead of having elements as keys and a dummy value as values, it uses the UUID of the element as key and the element itself as value. This does not cause overhead in the memory use compared to a normal HashSet.

Moreover, a MagicSet can be used exactly as a Set, but with some more methods providing additional functionalities, like getFromId(), popFromId(), removeFromId(), etc.

The only requirement to use it is that any element that you want to store in a MagicSet needs to extend the abstract class UniqueItem.


Here is a code example, imagining to retrieve the original instance of a city from a MagicSet, given another instance of that city with the same UUID (or even just its UUID).

class City extends UniqueItem {

    // Somewhere in this class

    public void doSomething() {
        // Whatever
    }
}

public class GameMap {
    private MagicSet<City> cities;

    public GameMap(Collection<City> cities) {
        cities = new MagicHashSet<>(cities);
    }

    /*
     * cityId is the UUID of the city you want to retrieve.
     * If you have a copied instance of that city, you can simply 
     * call copiedCity.getId() and pass the return value to this method.
     */
    public void doSomethingInCity(UUID cityId) {
        City city = cities.getFromId(cityId);
        city.doSomething();
    }

    // Other methods can be called on a MagicSet too
}
ricpacca
  • 800
  • 6
  • 9
15

With Java 8 you can do:

Foo foo = set.stream().filter(item->item.equals(theItemYouAreLookingFor)).findFirst().get();

But be careful, .get() throws a NoSuchElementException, or you can manipulate a Optional item.

cloudy_weather
  • 2,837
  • 12
  • 38
  • 63
14

If your set is in fact a NavigableSet<Foo> (such as a TreeSet), and Foo implements Comparable<Foo>, you can use

Foo bar = set.floor(foo); // or .ceiling
if (foo.equals(bar)) {
    // use bar…
}

(Thanks to @eliran-malka’s comment for the hint.)

Jesse Glick
  • 24,539
  • 10
  • 90
  • 112
  • 5
    If I didn't mind anybody reading my code having the initial thought that I'd gone completely insane, this would be a great solution. – Adam Nov 09 '17 at 17:47
  • 1
    Unfortunately all basic operations have log(N) time complexity when using TreeSets (TreeMaps under the hood) :c – nllsdfx Apr 06 '21 at 13:49
8

Convert set to list, and then use get method of list

Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);
To Kra
  • 3,344
  • 3
  • 38
  • 45
  • 47
    I don't get this. This will retrieve an *arbitrary* object of the set. Not *the* object. – aioobe Aug 19 '17 at 22:39
  • 4
    Why did this get so many upvotes? in this answer you convert the set to a list and retrieve the first object, not the "foo" – Uri Loya Jul 20 '20 at 11:27
  • 1
    overcomplicates it and makes it potentially very inefficient (much memory consumed, slow). The right way to do it is using a Map. – zakmck Oct 05 '20 at 16:34
8

Why:

It seems that Set plays a useful role in providing a means of comparison. It is designed not to store duplicate elements.

Because of this intention/design, if one were to get() a reference to the stored object, then mutate it, it is possible that the design intentions of Set could be thwarted and could cause unexpected behavior.

From the JavaDocs

Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.

How:

Now that Streams have been introduced one can do the following

mySet.stream()
.filter(object -> object.property.equals(myProperty))
.findFirst().get();
Jason M
  • 79
  • 1
  • 2
  • Upvote because of Stream API. Still, `get()` is not recommended, use `orElse()` or `ifPresent()` instead. – alistairv Dec 11 '20 at 22:53
6
Object objectToGet = ...
Map<Object, Object> map = new HashMap<Object, Object>(set.size());
for (Object o : set) {
    map.put(o, o);
}
Object objectFromSet = map.get(objectToGet);

If you only do one get this will not be very performing because you will loop over all your elements but when performing multiple retrieves on a big set you will notice the difference.

Xymon
  • 225
  • 1
  • 3
  • 8
3

you can use Iterator class

import java.util.Iterator;
import java.util.HashSet;

public class MyClass {
 public static void main(String[ ] args) {
 HashSet<String> animals = new HashSet<String>();
animals.add("fox");
animals.add("cat");
animals.add("dog");
animals.add("rabbit");

Iterator<String> it = animals.iterator();
while(it.hasNext()) {
  String value = it.next();
  System.out.println(value);   
 }
 }
}
chiha asma
  • 41
  • 1
3

If you look at the first few lines of the implementation of java.util.HashSet you will see:

public class HashSet<E>
    ....
    private transient HashMap<E,Object> map;

So HashSet uses HashMap interally anyway, which means that if you just use a HashMap directly and use the same value as the key and the value you will get the effect you want and save yourself some memory.

rghome
  • 8,529
  • 8
  • 43
  • 62
3

it looks like the proper object to use is the Interner from guava :

Provides equivalent behavior to String.intern() for other immutable types. Common implementations are available from the Interners class.

It also has a few very interesting levers, like concurrencyLevel, or the type of references used (it might be worth noting that it doesn't offer a SoftInterner which I could see as more useful than a WeakInterner).

molyss
  • 256
  • 2
  • 2
2

I know, this has been asked and answered long ago, however if anyone is interested, here is my solution - custom set class backed by HashMap:

http://pastebin.com/Qv6S91n9

You can easily implement all other Set methods.

Lukas Z.
  • 483
  • 6
  • 14
2

Been there done that!! If you are using Guava a quick way to convert it to a map is:

Map<Integer,Foo> map = Maps.uniqueIndex(fooSet, Foo::getKey);
Heisenberg
  • 5,514
  • 2
  • 32
  • 43
2

If you want nth Element from HashSet, you can go with below solution, here i have added object of ModelClass in HashSet.

ModelClass m1 = null;
int nth=scanner.nextInt();
for(int index=0;index<hashset1.size();index++){
    m1 = (ModelClass) itr.next();
    if(nth == index) {
        System.out.println(m1);
        break;
    }
}
Hardik Patel
  • 1,033
  • 1
  • 12
  • 16
0

Yes, use HashMap ... but in a specialised way: the trap I foresee in trying to use a HashMap as a pseudo-Set is the possible confusion between "actual" elements of the Map/Set, and "candidate" elements, i.e. elements used to test whether an equal element is already present. This is far from foolproof, but nudges you away from the trap:

class SelfMappingHashMap<V> extends HashMap<V, V>{
    @Override
    public String toString(){
        // otherwise you get lots of "... object1=object1, object2=object2..." stuff
        return keySet().toString();
    }

    @Override
    public V get( Object key ){
        throw new UnsupportedOperationException( "use tryToGetRealFromCandidate()");
    }

    @Override
    public V put( V key, V value ){
       // thorny issue here: if you were indavertently to `put`
       // a "candidate instance" with the element already in the `Map/Set`: 
       // these will obviously be considered equivalent 
       assert key.equals( value );
       return super.put( key, value );
    }

    public V tryToGetRealFromCandidate( V key ){
        return super.get(key);
    }
}

Then do this:

SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
    SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
    ...
    realThing.useInSomeWay()...
}

But... you now want the candidate to self-destruct in some way unless the programmer actually immediately puts it in the Map/Set... you'd want contains to "taint" the candidate so that any use of it unless it joins the Map makes it "anathema". Perhaps you could make SomeClass implement a new Taintable interface.

A more satisfactory solution is a GettableSet, as below. However, for this to work you have either to be in charge of the design of SomeClass in order to make all constructors non-visible (or... able and willing to design and use a wrapper class for it):

public interface NoVisibleConstructor {
    // again, this is a "nudge" technique, in the sense that there is no known method of 
    // making an interface enforce "no visible constructor" in its implementing classes 
    // - of course when Java finally implements full multiple inheritance some reflection 
    // technique might be used...
    NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet );
};

public interface GettableSet<V extends NoVisibleConstructor> extends Set<V> {
    V getGenuineFromImpostor( V impostor ); // see below for naming
}

Implementation:

public class GettableHashSet<V extends NoVisibleConstructor> implements GettableSet<V> {
    private Map<V, V> map = new HashMap<V, V>();

    @Override
    public V getGenuineFromImpostor(V impostor ) {
        return map.get( impostor );
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean contains(Object o) {
        return map.containsKey( o );
    }

    @Override
    public boolean add(V e) {
        assert e != null;
        V result = map.put( e,  e );
        return result != null;
    }

    @Override
    public boolean remove(Object o) {
        V result = map.remove( o );
        return result != null;
    }

    @Override
    public boolean addAll(Collection<? extends V> c) {
        // for example:
        throw new UnsupportedOperationException();
    }

    @Override
    public void clear() {
        map.clear();
    }

    // implement the other methods from Set ...
}

Your NoVisibleConstructor classes then look like this:

class SomeClass implements NoVisibleConstructor {

    private SomeClass( Object param1, Object param2 ){
        // ...
    }

    static SomeClass getOrCreate( GettableSet<SomeClass> gettableSet, Object param1, Object param2 ) {
        SomeClass candidate = new SomeClass( param1, param2 );
        if (gettableSet.contains(candidate)) {
            // obviously this then means that the candidate "fails" (or is revealed
            // to be an "impostor" if you will).  Return the existing element:
            return gettableSet.getGenuineFromImpostor(candidate);
        }
        gettableSet.add( candidate );
        return candidate;
    }

    @Override
    public NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet ){
       // more elegant implementation-hiding: see below
    }
}

PS one technical issue with such a NoVisibleConstructor class: it may be objected that such a class is inherently final, which may be undesirable. Actually you could always add a dummy parameterless protected constructor:

protected SomeClass(){
    throw new UnsupportedOperationException();
}

... which would at least let a subclass compile. You'd then have to think about whether you need to include another getOrCreate() factory method in the subclass.

Final step is an abstract base class (NB "element" for a list, "member" for a set) like this for your set members (when possible - again, scope for using a wrapper class where the class is not under your control, or already has a base class, etc.), for maximum implementation-hiding:

public abstract class AbstractSetMember implements NoVisibleConstructor {
    @Override
    public NoVisibleConstructor
            addOrGetExisting(GettableSet<? extends NoVisibleConstructor> gettableSet) {
        AbstractSetMember member = this;
        @SuppressWarnings("unchecked") // unavoidable!
        GettableSet<AbstractSetMembers> set = (GettableSet<AbstractSetMember>) gettableSet;
        if (gettableSet.contains( member )) {
            member = set.getGenuineFromImpostor( member );
            cleanUpAfterFindingGenuine( set );
        } else {
            addNewToSet( set );
        }
        return member;
    }

    abstract public void addNewToSet(GettableSet<? extends AbstractSetMember> gettableSet );
    abstract public void cleanUpAfterFindingGenuine(GettableSet<? extends AbstractSetMember> gettableSet );
}

... usage is fairly obvious (inside your SomeClass's static factory method):

SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
mike rodent
  • 14,126
  • 11
  • 103
  • 157
  • Nope. If you really want a new data structure to manage this, you should extend the Set interface, eg, interface RetrievableElementsSet extends Set, it should have a new method E get ( E e ) and then you could implement it as RetrievableElementHasSet by internally using a HashMap. This SelfMappingHashMap isn't abstract and clean enough, rather than this sort of mess, using Map/HashMap directly is quickly. – zakmck Oct 07 '20 at 11:10
  • 1
    @zakmck Firstly, thanks for actually taking an interest in this answer of mine. Re-reading it, I conclude that you are right to qualify it as a "mess". I don't really understand your reasoning or your final point ("quickly"?) but thanks again for giving the matter some thought. – mike rodent Oct 07 '20 at 18:24
  • I meant "quicker". Using Map directly is a quick way to do what the OP asked. Defining a new Set extension is a more purist way, which makes the abstraction of "objects retrievable from a set" explicit, but it's longer to implement. I initially meant that if one has to define the long, more abstract way, it makes sense only if it's done right. – zakmck Oct 08 '20 at 00:11
0

The contract of the hash code makes clear that:

"If two objects are equal according to the Object method, then calling the hashCode method on each of the two objects must produce the same integer result."

So your assumption:

"To clarify, the equals method is overridden, but it only checks one of the fields, not all. So two Foo objects that are considered equal can actually have different values, that's why I can't just use foo."

is wrong and you are breaking the contract. If we look at the "contains" method of Set interface, we have that:

boolean contains(Object o);
Returns true if this set contains the specified element. More formally, returns true if and only if this set contains an element "e" such that o==null ? e==null : o.equals(e)

To accomplish what you want, you can use a Map where you define the key and store your element with the key that defines how objects are different or equal to each other.

jfajunior
  • 1,211
  • 1
  • 15
  • 19
0

Here's what you can do if you have a NavigableSet (e.g. a TreeSet):

public static <E> E get(NavigableSet<E> set, E key) {
    return set.tailSet(key, true).floor(key);
}

The things are slightly trickier for HashSet and its descendants like LinkedHashSet:

import java.util.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Test {
    private static final Field mapField;
    private static final Method hashMethod;
    private static final Method getNodeMethod;
    private static final Field keyField;
    static {
        try {
            mapField = HashSet.class.getDeclaredField("map");
            mapField.setAccessible(true);
            hashMethod = HashMap.class.getDeclaredMethod("hash", Object.class);
            hashMethod.setAccessible(true);
            getNodeMethod = HashMap.class.getDeclaredMethod("getNode",
                    Integer.TYPE, Object.class);
            getNodeMethod.setAccessible(true);
            keyField = Class.forName("java.util.HashMap$Node").getDeclaredField("key");
            keyField.setAccessible(true);
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    public static <E> E get(HashSet<E> set, E key) {
        try {
            Object map = mapField.get(set);
            Object hash = hashMethod.invoke(null, key);
            Object node = getNodeMethod.invoke(map, hash, key);
            if (node == null)
                return null;
            @SuppressWarnings("unchecked")
            E result = (E)keyField.get(node);
            return result;
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    public static <E> E get(NavigableSet<E> set, E key) {
        return set.tailSet(key, true).floor(key);
    }

    public static void main(String[] args) {
        HashSet<Integer> s = new HashSet<>();
//      HashSet<Integer> s = new LinkedHashSet<>();
//      TreeSet<Integer> s = new TreeSet<>();
        for (int i = 0; i < 100_000; i++)
            s.add(i);
        Integer key = java.awt.event.KeyEvent.VK_FIND;
        Integer hidden = get(s, key);
        System.out.println(key);
        System.out.println(hidden);
        System.out.println(key.equals(hidden));
        System.out.println(key == hidden);
    }
}
John McClane
  • 3,498
  • 3
  • 12
  • 33
0

Because any particular implementation of Set may or may not be random access.

You can always get an iterator and step through the Set, using the iterators' next() method to return the result you want once you find the equal element. This works regardless of the implementation. If the implementation is NOT random access (picture a linked-list backed Set), a get(E element) method in the interface would be deceptive, since it would have to iterate the collection to find the element to return, and a get(E element) would seem to imply this would be necessary, that the Set could jump directly to the element to get.

contains() may or may not have to do the same thing, of course, depending on the implementation, but the name doesn't seem to lend itself to the same sort of misunderstandings.

Tom Tresansky
  • 19,364
  • 17
  • 93
  • 129
  • 2
    Anything the get() method would do is already being done by the contains() method. You can't implement contains() without getting the contained object and calling its .equals() method. The API designers seemed to have no qualms about adding a get() to java.util.List even though it would be slow in some implementations. – Bryan Rink Oct 25 '13 at 22:14
  • I don't think this is true. Two objects can be equal via equals, but not identical via ==. If you had object A, and a set S containing object B, and A.equals(B) but A != B and you wanted to get a reference to B, you could call S.get(A) to get a reference to B, assuming you had a get method with the semantics of List's get method, which is a different use case than checking if S.contains(A) (which it would). This isn't even that rare a use case for collections. – Tom Tresansky Nov 01 '13 at 15:21
-2

Quick helper method that might address this situation:

<T> T onlyItem(Collection<T> items) {
    if (items.size() != 1)
        throw new IllegalArgumentException("Collection must have single item; instead it has " + items.size());

    return items.iterator().next();
}
Matthieu
  • 2,736
  • 4
  • 57
  • 87
Chris Willmore
  • 574
  • 3
  • 13
  • 10
    It is very strange that this answer has gotten so many upvotes since it doesn't answer the question or even attempt to address it in any way. – David Conrad Nov 01 '16 at 12:58
-2

Try using an array:

ObjectClass[] arrayName = SetOfObjects.toArray(new ObjectClass[setOfObjects.size()]);
Roshana Pitigala
  • 8,437
  • 8
  • 49
  • 80
canni
  • 9
-3

Following can be an approach

   SharedPreferences se_get = getSharedPreferences("points",MODE_PRIVATE);
   Set<String> main = se_get.getStringSet("mydata",null);
   for(int jk = 0 ; jk < main.size();jk++)
   {
      Log.i("data",String.valueOf(main.toArray()[jk]));
   }
Vikrant
  • 444
  • 1
  • 5
  • 22