Java 21 Sequenced Collections: Exemplos Práticos

Sequenced Collections

O framework de coleções do Java é uma das partes mais importantes da linguagem. Ele fornece um conjunto de classes e interfaces para armazenar e manipular coleções de objetos. O Java 21 foi lançado e nos trouxe a Sequenced Collections (JEP 431) que fornece APIs uniformes para acessar o primeiro e o último elemento de uma coleção e para processar seus elementos na ordem inversa.

São três as novas interfaces que foram introduzidas pela JEP 431. Entre elas, temos a SequencedCollection, SequencedSet e SequencedMap. Observe o diagrama a seguir e veja a hierarquia formada após a chega dessas interfaces:

Alguns ajustes foram realizados para modernizar classes e interfaces existentes no Java 21, veja:

  • List – agora tem SequencedCollection como sua superinterface imediata;
  • Deque – agora tem SequencedCollection como sua super interface imediata;
  • LinkedHashSet – implementa adicionalmente SequencedSet;
  • SortedSet – agora tem SequencedSet como sua super interface imediata;
  • LinkedHashMap – implementa adicionalmente SequencedMap, e
    SortedMap agora tem SequencedMap como sua super interface imediata.

A interface SequencedCollection

A interface SequencedCollection define os seguintes novos métodos:

  • addFirst(E e): Adiciona um elemento no início da coleção.
  • addLast(E e): Adiciona um elemento no final da coleção.
  • getFirst(): Retorna o primeiro elemento da coleção.
  • getLast(): Retorna o último elemento da coleção.
  • removeFirst(): Remove o primeiro elemento da coleção.
  • removeLast(): Remove o último elemento da coleção.
  • reversed(): Retorna uma visão invertida da coleção.

Como usar os novos métodos da SequencedCollection

Entre os métodos da SequencedCollection já listados, temos dois em destaque, que são o getFirst() e getLast(). Vamos ver um breve exemplo de como usá-los para recuperar de uma lista de nomes o primeiro e último nome inseridos.

Exemplo 1.

public static void main(String[] args) {
    SequencedCollection<String> sequence = new ArrayList<>();
    sequence.add("Ana Maria");
    sequence.add("Bianca Rios");
    sequence.add("Carlos Peres");
    sequence.add("Diana Marim");
    sequence.add("Everton Nunes");

    System.out.println(sequence.getFirst()); // Ana Maria
    System.out.println(sequence.getLast()); // Everton Nunes
}

Com o uso dos métodos citados teremos como retorno o primeiro e último nome inseridos na lista. Contudo, e se essa lista precisasse que um novo nome fosse inserido na primeira posição e outro nome na última posição? Bem, com a SequencedCollection podemos resolver isso usando os métodos addFirst() e addLast().

Exemplo 2.

public static void main(String[] args) {
    SequencedCollection<String> sequence = new ArrayList<>();
    sequence.add("Ana Maria");
    sequence.add("Bianca Rios");
    sequence.add("Carlos Peres");
    sequence.add("Diana Marim");
    sequence.add("Everton Nunes");

    sequence.addFirst("Anita Silva");
    sequence.addLast("Fernando Ferreira");

    System.out.println(sequence.getFirst()); // Anita Silva
    System.out.println(sequence.getLast()); // Fernando Ferreira
}

Outra facilidade que a SequencedCollection nos trouxe foi inverter a lista, ou seja, podemos imprimir a lista do último elemento para o primeiro. Para isso, usaremos o método reversed().

Exemplo 3.

public static void main(String[] args) {
    SequencedCollection<String> sequence = new ArrayList<>();
    sequence.add("Ana Maria");
    sequence.add("Bianca Rios");
    sequence.add("Carlos Peres");
    sequence.add("Diana Marim");
    sequence.add("Everton Nunes");

    sequence.addFirst("Anita Silva");
    sequence.addLast("Fernando Ferreira");

    SequencedCollection<String> reversed = sequence.reversed();
    System.out.println(reversed);
    // [Fernando Ferreira, Everton Nunes, Diana Marim, Carlos Peres, Bianca Rios, Ana Maria, Anita Silva]
}

No exemplo acima pegamos a lista sequence e atribuímos a nova lista reversed com a ordenação contrária, ou seja, invertida ou reversa.

Por fim, temos os métodos para remover o primeiro e o último elemento da lista. Este métodos são o removeFirst() e removeLast(). Veja no exemplo 4 como usá-los.

public static void main(String[] args) {
    SequencedCollection<String> sequence = new ArrayList<>();
    sequence.add("Ana Maria");
    sequence.add("Bianca Rios");
    sequence.add("Carlos Peres");
    sequence.add("Diana Marim");
    sequence.add("Everton Nunes");

    sequence.addFirst("Anita Silva");
    sequence.addLast("Fernando Ferreira");

    System.out.println(sequence);
    // [Anita Silva, Ana Maria, Bianca Rios, Carlos Peres, Diana Marim, Everton Nunes, Fernando Ferreira]
        
    sequence.removeFirst();
    sequence.removeLast();

    System.out.println(sequence);
    // [Ana Maria, Bianca Rios, Carlos Peres, Diana Marim, Everton Nunes]
}

Entretanto, é bom destacar que, ao tentar remover o primeiro ou último elemento de uma coleção durante a execução de um laço de repetição ainda é perigoso. Você terá que lidar com uma exceção do tipo java.util.ConcurrentModificationException.

Exemplo 5.

public static void main(String[] args) {
    SequencedCollection<String> sequence = new ArrayList<>();
    sequence.add("Ana Maria");
    sequence.add("Bianca Rios");
    sequence.add("Carlos Peres");
    sequence.add("Diana Marim");
    sequence.add("Everton Nunes");

    System.out.println(sequence);

    for (String nome : sequence) {
        if (nome.contains("Carlos")) {
                sequence.removeFirst();
        }
    }

    System.out.println(sequence);
}

Portanto, cuidado com isso. No console você verá uma mensagem de erro como esta:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049)
	at com.mballem.Exemplo5.main(Exemplo5.java:20)

A interface SequencedSet

O Java 21 nos trouxe também a interface SequencedSet. O código fonte dessa interface contém apenas uma assinatura de método, como podemos ver aqui:

public interface SequencedSet<E> extends SequencedCollection<E>, Set<E> {
    /**
     * {@inheritDoc}
     *
     * @return a reverse-ordered view of this collection, as a {@code SequencedSet}
     */
    SequencedSet<E> reversed();
}

Veja que o método reversed() dessa vez tem como retorno um objeto do tipo SequencedSet, diferente daquele visto no Exemplo 3 que retorna um objeto do tipo SequencedCollection. O objetivo é o mesmo, então não temos muito o que falar sobre este método. O importante a destacar é que a interface herda os demais métodos de SequencedCollection e também da interface Set.

Para instanciar um objeto do tipo SequencedSet você pode fazer uso da implementação LinkedHashSet.

Exemplo 6.

public static void main(String[] args) {
    SequencedSet<String> set = new LinkedHashSet<>();
    set.add("Ana Maria");
    set.add("Bianca Rios");
    set.add("Carlos Peres");
    set.add("Diana Marim");
    set.add("Everton Nunes");

    System.out.println(set);
    // [Ana Maria, Bianca Rios, Carlos Peres, Diana Marim, Everton Nunes]

    System.out.println(set.reversed());
    // [Everton Nunes, Diana Marim, Carlos Peres, Bianca Rios, Ana Maria]
}

A interface SequencedMap

Vamos explorar, no Exemplo 7, as operações de inclusão de elementos na primeira e última posição do Map. A SequencedMap pode ser atribuída com um objeto do tipo LinkedHashMap. Outras implementações como HashMap e TreeMap não implementam a interface SequencedMap e por isso, não podem ser usadas para instanciar SequencedMap.

Exemplo 7.

public static void main(String[] args) {
    SequencedMap<String, Object> mapFrutas = new LinkedHashMap<>();
    mapFrutas.put("Laranja", 5);
    mapFrutas.put("Limão", 2);
    mapFrutas.put("Pera", 3);
    mapFrutas.put("Morango", 3);
    mapFrutas.put("Banana", 8);

    System.out.println(mapFrutas);
    // {Laranja=5, Limão=2, Pera=3, Morango=3, Banana=8}
    
    mapFrutas.putFirst("Goiaba", 4);
    mapFrutas.putLast("Mamão", 2);
    
    System.out.println(mapFrutas);
    // {Goiaba=4, Laranja=5, Limão=2, Pera=3, Morango=3, Banana=8, Mamão=2}
}

Observe que os nomes dos métodos para inclusão na primeira e última posição são diferentes daqueles que vimos anteriormente em SequencedCollection. Desta vez, temos os métodos putFirst() e putLast() respectivamente.

Também podemos ter acesso direto aos elementos adicionados na primeira e última posição do Map usando os métodos firstEntry() e lastEntry(). Veja como utiliza-los no código do Exemplo 8:

Exemplo 8.

public static void main(String[] args) {
    SequencedMap<String, Object> mapFrutas = new LinkedHashMap<>();
    mapFrutas.put("Laranja", 5);
    mapFrutas.put("Limão", 2);
    mapFrutas.put("Pera", 3);
    mapFrutas.put("Morango", 3);
    mapFrutas.put("Banana", 8);

    Map.Entry<String, Object> first = mapFrutas.firstEntry();
    System.out.println(first); // Laranja=5    

    Map.Entry<String, Object> last = mapFrutas.lastEntry();
    System.out.println(last); //Banana=8        
}

Caso o Map não possua elementos, o uso destes métodos vai retornar null.

Outro dois métodos disponíveis são o pollFirstEntry() e pollLastEntry(). O objetivo deles é retornar e remover ao mesmo a primeira e última linha do Map. Podemos ver um exemplo desse uso no código abaixo:

Exemplo 9.

public static void main(String[] args) {
    SequencedMap<String, Object> mapFrutas = new LinkedHashMap<>();
    mapFrutas.put("Laranja", 5);
    mapFrutas.put("Limão", 2);
    mapFrutas.put("Pera", 3);
    mapFrutas.put("Morango", 3);
    mapFrutas.put("Banana", 8);

    Map.Entry<String, Object> first = mapFrutas.pollFirstEntry();
    System.out.println(first);
    System.out.println(mapFrutas);
    // Laranja=5
    // {Limão=2, Pera=3, Morango=3, Banana=8}

    Map.Entry<String, Object> last = mapFrutas.pollLastEntry();
    System.out.println(last);
    System.out.println(mapFrutas);
    // Banana=8
    // {Limão=2, Pera=3, Morango=3}
}

A interface tem acesso também ao método reversed(), que inverte a ordem de inclusão de elementos no Map. Veja como utilizar no Exemplo 10:

Exemplo 10.

public static void main(String[] args) {
    SequencedMap<String, Object> mapFrutas = new LinkedHashMap<>();
    mapFrutas.put("Laranja", 5);
    mapFrutas.put("Limão", 2);
    mapFrutas.put("Pera", 3);
    mapFrutas.put("Morango", 3);
    mapFrutas.put("Banana", 8);

    System.out.println(mapFrutas);
    // {Laranja=5, Limão=2, Pera=3, Morango=3, Banana=8}

    System.out.println(mapFrutas.reversed());
    // {Banana=8, Morango=3, Pera=3, Limão=2, Laranja=5}

}

Conclusão

O recurso Sequenced Collection é uma adição de grande valia ao framework de coleções do Java. Ele resolve problemas persistentes e promete redefinir como os desenvolvedores interagem com as coleções. As novas interfaces para coleções sequenciadas, conjuntos sequenciados e mapas sequenciados fornecem métodos para adicionar, recuperar ou remover elementos em ambas as extremidades da coleção, juntamente com um método para obter uma visualização ordenada reversa da coleção. Se você ainda não experimentou o recurso Sequenced Collection, agora é a hora de começar a usá-lo em seus projetos Java.

Referencias

Ballem

Marcio Ballem é bacharel em Sistemas de Informação pelo Centro Universitário Franciscano em Santa Maria/RS. Tem experiência com desenvolvimento Delphi e Java em projetos para gestão pública e acadêmica. Possui certificação em Java, OCJP 6.

Você pode gostar...