How to get an item from a Set in Java

Donald Raab
Javarevisited
Published in
5 min readSep 25, 2023

--

The mystery of the missing get method on the Java Set interface.

Photo by Pierre Bamin on Unsplash

How to get from a Set with no get?

The Set interface in Java does not have a get method. This has been the case since the Set interface was added in Java 2. The Map interface on the other hand has a get method.

The get method on Map.

The question for a get method on a Set is what should it return? If you consider that a HashSet in Java is built using a HashMap, I would propose that a get method on a Set should return the key in the Map.

Since no get method exists on Set, the best we can do is either use an iterator and find the first element that matches based on equals, or to use the Stream API. Let’s see what both look like.

The following is the code finding the first element that matches using a Java 5 for each loop, which under the covers will use an Iterator.

@Test
public void findFirstMatchingElementInSetUsingIterator()
{
String three = null;
Set<String> set = Set.of("1", "2", "3", "4", "5");
for (String item : set)
{
if (item.equals(new String("3")))
{
three = item;
}
}
Assertions.assertSame("3", three);
}

This is kind of verbose and has linear time complexity because it has to iterate from the beginning and test each element. I am asserting that the result I get back is the same as the one in the Set. I used new String(“3”) for the equals test because I wanted to make sure the item that I looked up with was different than the one in the Set.

The following is the code finding the first element that matches using a Java Stream.

@Test
public void findFirstMatchingElementInSetUsingStream()
{
Set<String> set = Set.of("1", "2", "3", "4", "5");
String three = set.stream()
.filter(each -> each.equals(new String("3")))
.findFirst()
.orElse(null);
Assertions.assertSame("3", three);
}

The code is less verbose, but has more method calls and has the same linear time complexity.

If we want fast lookup for items in a Set, we could just use a Map. After all, in Java, a Set is just implemented using Map.

The follow code shows how we could use a Map as a Set.

@Test
public void findFirstMatchingElementInMapActingAsSet()
{
var set = Map.of("1", "1", "2", "2", "3", "3", "4", "4", "5", "5");
String three = set.get(new String("3"));
Assertions.assertSame("3", three);
}

I was attempting to be clever here using var to obfuscate the type of the set variable, which you might be able to guess is Map<String, String>.

By now, I hope you are scratching your head thinking “why didn’t we get a Set with a get?”

Enter Eclipse Collections UnifiedSet and Pool

Would you be surprised if I told you that the class UnifiedSet in Eclipse Collections has a get method?

@Test
public void findFirstMatchingElementInUnfiedSet()
{
UnifiedSet<String> set = UnifiedSet.newSetWith("1", "2", "3", "4", "5");
String three = set.get(new String("3"));
Assertions.assertSame("3", three);
}

Notice that I had to use UnifiedSet<String> as the type here. I was not able to use MutableSet<String> for the type on the left. The reason is that we did not add the get method to the MutableSet interface. We instead introduced a new interface named Pool. UnifiedSet is both a MutableSet and a Pool.

@Test
public void findFirstMatchingElementInPool()
{
Pool<String> set = UnifiedSet.newSetWith("1", "2", "3", "4", "5");
String three = set.get(new String("3"));
Assertions.assertSame("3", three);
}

A Pool is a useful data structure for pooling (aka deduplicating) instances of immutable data, like String, LocalDate, integral wrappers (Integer, Long, Short), etc.

Here’s an implementation of the Pool interface using the ConcurrentHashMap class available in Eclipse Collections.

public static class ConcurrentPool<K> implements Pool<K>
{
private ConcurrentHashMap<K, K> map = new ConcurrentHashMap<>();

public static <V> Pool<V> with(V... values)
{
ConcurrentPool<V> pool = new ConcurrentPool<V>();
for (V each : values)
{
pool.put(each);
}
return pool;
}

@Override
public K get(K key)
{
return this.map.get(key);
}

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

@Override
public K put(K key)
{
return this.map.getIfAbsentPut(key, key);
}

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

@Override
public K removeFromPool(K key)
{
return this.map.removeKey(key);
}
}

With this implementation, I can write the Set equivalent code as follows.

@Test
public void findFirstMatchingElementInCHMPool()
{
Pool<String> set = ConcurrentPool.with("1", "2", "3", "4", "5");
String three = set.get(new String("3"));
Assertions.assertSame("3", three);
}

As you should be able to see from the ConcurrentPool implementation, a Pool has a get and a put method, similar to a Map. However, a Pool only deals with keys, not key/value pairs as a Map does.

StackOverflow to the rescue

I answered a question similar to the title of this blog on StackOverflow over a decade ago. I didn’t give as much detail in the answer about the Pool interface as I did in this blog, but did provide a link to the Javadoc for the interface.

My answer, which was accepted, is shown below for convenience.

That’s all you get folks

That’s all I have to say about the mystery of the missing get method on the Java Set interface. You have learned that the UnifiedSet class in Eclipse Collections has a get method, but that is because it implements both the MutableSet and Pool interfaces. It is trival to implement the Pool interface, as it has a lot less methods than either Set or Map. The implementation example I chose uses the ConcurrentHashMap class from Eclipse Collections.

Thank you for reading, and I hope you found this useful!

I am the creator of and committer for the Eclipse Collections OSS project, which is managed at the Eclipse Foundation. Eclipse Collections is open for contributions.

--

--

Donald Raab
Javarevisited

Java Champion. Creator of the Eclipse Collections OSS Java library (https://github.com/eclipse/eclipse-collections). Inspired by Smalltalk. Opinions are my own.