How to get an item from a Set in Java
The mystery of the missing get
method on the Java Set
interface.
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 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.