How to work with sets in Java

April 6, 2020

by — Posted in Core Java

Hello! This post describes Java implementation of set concept (collections without duplicates) and provides an overview of common operations with java.util.Set.

Set as a data structure

In computer programming, set is an implementation of the mathematical concept of (finite) set. It can be defined as a collection of elements with no duplicates and no ordering. Although, for some concrete implementations it is true that they have ordered elements.

Work with sets in Java

Java includes sets as a part of Java Collections Framework. There, set is defined as a a collection that contains to duplicates and at most 1 null element. Only one null element is allowed because, logically, one null is unique by its nature. So, how Java understands if an element is unique? For unique element e1 is true that there is no element e2 in set already that e1.equals(e2) == true.

Add a new element

Sets use add (E e) method as any other collection. This operation returns a boolean value that is true if element was inserted successfully, or false is element presents already. To validate an uniqueness of an element, Java uses equals() logic for an element.

Advice

Code examples in this post use AssertJ library to write fluent assertions. You can learn more on how to utilize AssertJ with Java Collections in this tutorial

Let have a quick example:

@Test
void addToSetTest(){
    Set<Integer> numbers = new HashSet<>();
    numbers.add(1);
    numbers.add(2);
    numbers.add(3);
    assertThat(numbers.add(2)).isFalse();
}

Remove an element

To remove an element we use a method remove(Object obj) that is also common for Java collections. As same with the previous case of an addition, delete operation uses equals() to determine an existance of an element in the set and returns boolean value, which is true is the element was removed. Set will not contain the element once the call returns.

Take a look on the code snippet below:

@Test
void removeFromSetTest(){
    Set<Integer> numbers = new HashSet<>();
    numbers.add(1);
    numbers.add(2);
    numbers.add(3);
    assertThat(numbers.remove(3)).isTrue();
}

Pay attention on the method signature: it accepts Object, rather than type. So, ClassCastException may be thrown in case when the argument does not match its type to the type of set.

Get an arbitary element from set

We previously discussed, that java.util.Collection itself does not offer ways to access an arbitrary element, as it is strictly depends on the logic of concrete data structures. For example, lists permit developers to get a value based on numeric index [0; length – 1].

Sets do not have these methods, but we implement it in two ways:

  1. Using iterator
  2. Using streams

Advice

In this section I use Java streams. If you’re not familiar with them, feel free to check my guide on Java Streams API

Take a look on the code snippet below:

@Test
void getAnElementTest(){
    TreeSet<Integer> numbers = new TreeSet<>();
    numbers.add(50);
    numbers.add(10);
    numbers.add(15);
    numbers.add(8);

    // as treeset has sorted elements,
    // then first = 8
    Iterator<Integer> iterator = numbers.iterator();
    Integer first = iterator.next();
    assertThat(first).isEqualTo(8);

    //use custom objects
    // people are ordered by the first name
    TreeSet<Person> people = new TreeSet<>();
    people.add(new Person("Alejandra", "Morales"));
    people.add(new Person("Katarina", "Rodriguez"));
    people.add(new Person("Maria", "Sanchez"));
    people.add(new Person("Robetra", "Iglesias"));

    Optional<Person> person = people.stream().filter(p -> p.equals(new Person("Maria", "Sanchez"))).findFirst();
    assertThat(person).isPresent();
}

Get set length

Alike other Java collections, sets use size() method, which returns an integer of numbers of elements in the set. Here is demonstration of using it:

@Test
void getSizeTest(){
    Set<Integer> numbers = new HashSet<>();
    numbers.add(1);
    numbers.add(2);
    numbers.add(3);
    numbers.add(4);
    numbers.add(5);

    int size = numbers.size();
    assertThat(size).isEqualTo(5);
}

Create subsets

Compare to lists, generally, sets don’t have built-in method to create sublists (with exception of SortedSet, which does have). So, to achieve this goal we can follow two approaches:

  1. Use streams
  2. Use iterators

The code snippet below demontrates usages of both ways:

@Test
void createSubsetTest(){
    // Here, I use Guava to create hash set
    Set<Integer> numbers = Sets.newHashSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    // approach 1: streams
    Set<Integer> streamSubset = numbers.stream().limit(5).collect(Collectors.toSet());
    assertThat(streamSubset).hasSize(5).contains(1,2,3,4,5);

    // approach 2: iterators
    Iterator<Integer> iterator = numbers.iterator();
    Set<Integer> iteratorSubset = new HashSet<>();
    int limit = 5;
    for (int i = 0; i<limit && iterator.hasNext(); i++){
        iteratorSubset.add(iterator.next());
    }
    assertThat(iteratorSubset).hasSize(5).hasSameElementsAs(streamSubset);
}

Source code

You can find the full source code for this post in this github repository. If you have questions regarding this post, don’t hesitate to contact me. Have a nice day!