Métodos de fábrica de conveniência Java 9 para coleções
1. Visão geral
Java 9 traz o açúcar sintático tão esperado para a criação de pequenas instânciasCollection não modificáveiss usando um código de linha conciso. De acordo comJEP 269, novos métodos de fábrica de conveniência serão incluídos no JDK 9.
Neste artigo, vamos cobrir seu uso junto com os detalhes de implementação.
2. História e Motivação
Criar um pequenoCollection imutável em Java é muito prolixo usando a maneira tradicional.
Vamos dar um exemplo de umSet:
Set set = new HashSet<>();
set.add("foo");
set.add("bar");
set.add("baz");
set = Collections.unmodifiableSet(set);
Isso é código demais para uma tarefa simples e deve ser possível fazer em uma única expressão.
O também é verdadeiro para aMap, no entanto, paraList, há um método de fábrica:
List list = Arrays.asList("foo", "bar", "baz");
Embora a criação deList seja melhor do que a inicialização do construtor, isso éless obvious, pois a intuição comum não seria procurar na classeArrays métodos para criar umList:
Existem outras maneiras de reduzir o detalhamento, como a técnicadouble-brace:
Set set = Collections.unmodifiableSet(new HashSet() {{
add("foo"); add("bar"); add("baz");
}});
ou usando Java 8Streams:
Stream.of("foo", "bar", "baz")
.collect(collectingAndThen(toSet(), Collections::unmodifiableSet));
A técnica de cinta dupla é apenas um pouco menos detalhada, mas reduz bastante a legibilidade (e é considerada um antipadrão).
A versão do Java 8, no entanto, é uma expressão de uma linha, também tem alguns problemas. Primeiro, não é óbvio e intuitivo, segundo, ainda é prolixo, terceiro, envolve a criação de objetos desnecessários e quarto, este método não pode ser usado para criar umMap.
Para resumir as deficiências, nenhuma das abordagens acima trata o caso de uso específico criando um pequeno problema de primeira classeCollection não modificável.
3. Descrição e uso
Métodos estáticos foram fornecidos para as interfacesList,Set eMap que usam os elementos como argumentos e retornam uma instância deList,SeteMap respectivamente.
Este método é denominadoof(…) para todas as três interfaces.
3.1. List eSet
A assinatura e as características dos métodos de fábrica deListeSet são as mesmas:
static List of(E e1, E e2, E e3)
static Set of(E e1, E e2, E e3)
uso dos métodos:
List list = List.of("foo", "bar", "baz");
Set set = Set.of("foo", "bar", "baz");
Como você pode ver, é muito simples, curto e conciso.
No exemplo, usamos o método com leva exatamente três elementos como parâmetros e retorna umList /Set de tamanho 3.
Porém, existem 12 versões sobrecarregadas desse método - onze com 0 a 10 parâmetros e uma com var-args:
static List of()
static List of(E e1)
static List of(E e1, E e2)
// ....and so on
static List of(E... elems)
Para fins mais práticos, 10 elementos seriam suficientes, mas se forem necessários mais, a versão var-args pode ser usada.
Agora você pode perguntar, qual é o sentido de ter 11 métodos extras se houver uma versão var-args que pode funcionar para qualquer número de elementos.
A resposta para isso é desempenho. Every var-args method call implicitly creates an array. Having the overloaded methods avoid unnecessary object creation and the garbage collection overhead thereof.
Durante a criação de umSet usando um método de fábrica, se elementos duplicados forem passados como parâmetros, entãoIllegalArgumentException é lançado no tempo de execução:
@Test(expected = IllegalArgumentException.class)
public void onDuplicateElem_IfIllegalArgExp_thenSuccess() {
Set.of("foo", "bar", "baz", "foo");
}
Um ponto importante a ser observado aqui é que, como os métodos de fábrica usam genéricos, os tipos primitivos são autoboxados.
Se um array de tipo primitivo é passado, umList dearray desse tipo primitivo é retornado.
Por exemplo:
int[] arr = { 1, 2, 3, 4, 5 };
List list = List.of(arr);
Nesse caso, umList<int[]> de tamanho 1 é retornado e o elemento no índice 0 contém a matriz.
3.2. Map
A assinatura do método de fábrica deMap é:
static Map of(K k1, V v1, K k2, V v2, K k3, V v3)
e o uso:
Map map = Map.of("foo", "a", "bar", "b", "baz", "c");
Da mesma forma queListeSet, o métodoof(…) é sobrecarregado para ter de 0 a 10 pares de valores-chave.
No caso deMap, há um método diferente para mais de 10 pares de valores-chave:
static Map ofEntries(Map.Entry extends K,? extends V>... entries)
e é o uso:
Map map = Map.ofEntries(
new AbstractMap.SimpleEntry<>("foo", "a"),
new AbstractMap.SimpleEntry<>("bar", "b"),
new AbstractMap.SimpleEntry<>("baz", "c"));
Passar valores duplicados para a chave geraria umIllegalArgumentException:
@Test(expected = IllegalArgumentException.class)
public void givenDuplicateKeys_ifIllegalArgExp_thenSuccess() {
Map.of("foo", "a", "foo", "b");
}
Novamente, no caso deMap também, os tipos primitivos são autoboxed.
4. Notas de implementação
As coleções criadas usando os métodos de fábrica não são implementações comumente usadas.
Por exemplo,List não éArrayListeMap não éHashMap. Essas são implementações diferentes que são introduzidas no Java 9. Essas implementações são internas e seus construtores têm acesso restrito.
Nesta seção, veremos algumas diferenças de implementação importantes que são comuns a todos os três tipos de coleções.
4.1. Imutável
As coleções criadas usando métodos de fábrica são imutáveis e alterar um elemento, adicionar novos elementos ou remover um elemento geraUnsupportedOperationException:
@Test(expected = UnsupportedOperationException.class)
public void onElemAdd_ifUnSupportedOpExpnThrown_thenSuccess() {
Set set = Set.of("foo", "bar");
set.add("baz");
}
@Test(expected = UnsupportedOperationException.class)
public void onElemModify_ifUnSupportedOpExpnThrown_thenSuccess() {
List list = List.of("foo", "bar");
list.set(0, "baz");
}
@Test(expected = UnsupportedOperationException.class)
public void onElemRemove_ifUnSupportedOpExpnThrown_thenSuccess() {
Map map = Map.of("foo", "a", "bar", "b");
map.remove("foo");
}
4.2. Nenhum elementonull permitido
No caso deList eSet, nenhum elemento pode sernull. No caso de aMap, nem as chaves nem os valores podem sernull. Passar o argumentonull gera umNullPointerException:
@Test(expected = NullPointerException.class)
public void onNullElem_ifNullPtrExpnThrown_thenSuccess() {
List.of("foo", "bar", null);
}
4.3. Instâncias baseadas em valor
As instâncias criadas pelos métodos de fábrica são baseadas em valor. Isso significa que as fábricas são livres para criar uma nova instância ou retornar uma instância existente.
Portanto, se criarmos listas com os mesmos valores, elas poderão ou não se referir ao mesmo objeto no heap:
List list1 = List.of("foo", "bar");
List list2 = List.of("foo", "bar");
Neste caso,list1 == list2 pode ou não ser avaliado comotrue dependendo da JVM.
4.4. Serialização
Coleções criadas a partir de métodos de fábrica sãoSerializable se os elementos da coleção sãoSerializable.
5. Conclusão
Neste artigo, apresentamos os novos métodos de fábrica para coleções introduzidos no Java 9.
Concluímos por que esse recurso é uma mudança bem-vinda, passando por alguns métodos anteriores para criar coleções não modificáveis. Abordamos seu uso e destacamos os principais pontos a serem considerados ao usá-los.
Por fim, esclarecemos que essas coleções são diferentes das implementações comumente usadas e apontamos as principais diferenças.
O código-fonte completo para este artigo e os testes de unidade sãoavailable over on GitHub.