Olá! Meu nome é Jacqueline Oliveira, sou engenheira de software e instrutora aqui na Alura.
Audiodescrição: Jaqueline se identifica como uma mulher de pele branca. Possui cabelos longos e alguns reflexos louros. Veste uma blusa verde. Ao fundo, há uma parede lisa e um armário à direita, iluminados em tons de azul.
Aprofundaremos nossos conhecimentos em programação em Java, praticando coleções e streams (fluxos). Nosso objetivo neste curso é trabalhar e entender:
List
, Set
e Map
, e quando devemos usá-los;Esses tópicos são extremamente relevantes, pois são recursos amplamente utilizados quando precisamos lidar com coleções de dados. Na maioria dos casos, precisaremos trabalhar com um grande volume de dados e, portanto, com coleções de dados.
Esperamos por você no próximo vídeo!
Vamos aprofundar nosso conhecimento e prática sobre as coleções do Java. Antes disso, é importante entender por que utilizamos coleções ao invés de arrays comuns, que são estruturas conhecidas na programação.
Os arrays são basicamente uma construção de baixo nível, apresentando inflexibilidade em alguns aspectos, como no ajuste de tamanho. Eles não possuem operações para buscar, classificar ou adicionar elementos, além de terem tamanhos fixos, o que gera uma complexidade indesejada.
Por isso, damos prioridade ao uso das **coleções **do Java.
Vamos explorar um pouco sobre essas coleções, os tipos mais conhecidos, como são utilizadas e como programamos com elas.
Assim como os arrays, as coleções são estruturas de dados que armazenam múltiplos elementos. No Java, as interfaces principais que declaram esses métodos são:
List
;Set
eMap
.Entenderemos a diferença entre as três e como decidimos por uma ou outra.
Basicamente, a List
permite ordenação e é útil quando queremos ordenar os elementos de uma coleção desse tipo. Ela também permite duplicidade.
As implementações principais da interface List
são as classes:
ArrayList
eLinkedList
.Usamos ArrayList
quando queremos realizar buscas rápidas. Já o LinkedList
é preferido quando fazemos muitas inserções e remoções.
Dependendo do caso, optamos por um ou outro. O uso mais comum é o ArrayList
.
As operações mais comuns ao utilizar esse tipo de coleção são:
add
para adicionar um elemento;remove
para remover;contains
para verificar se um elemento existe na lista;get
para obter um elemento em uma posição específica esize
para saber o tamanho total da lista.Vamos praticar alguns exemplos. Acessando a IDE IntelliJ, já temos uma classe Principal
e um método main
. Dentro dele, simularemos a criação de uma lista de pessoas funcionárias.
Primeiro, vamos importar a classe List
de java.util
e declarar o tipo List
, utilizando generics com os símbolos de maior e menor (<>
) para indicar que é uma lista de String
, pois guardaremos os nomes das pessoas funcionárias.
Nomearemos essa variável como funcionarios
. A criação é feita pela classe ArrayList
, com new ArrayList<>()
. Também devemos importá-la.
import java.util.ArrayList;
import java.util.List;
public class Principal {
public static void main(String[] args) {
List<String> funcionarios = new ArrayList<>();
}
}
Para adicionar itens à lista, utilizamos funcionarios.add()
e inserimos o nome de uma pessoa funcionária, como "João". Podemos adicionar mais, como "Maria", e incluir repetidos, como outro "João".
public static void main(String[] args) {
List<String> funcionarios = new ArrayList<>();
public static void main(String[] args) {
List<String> funcionarios = new ArrayList<>();
}
Podemos imprimir a lista com System.out.println(funcionarios)
System.out.println(funcionarios);
Com isso, veremos "João", "Maria" e "João" novamente no terminal, separados por vírgulas. A lista permite duplicados e mantém a ordem de inserção.
[João, Maria, João]
Podemos alterar esse println()
e realizar outras operações, como funcionarios.getFirst()
para obter o primeiro elemento. Isso retornará "João".
System.out.println(funcionarios.getFirst());
Também podemos retornar um elemento em uma posição específica. O comando funcionarios.get(1)
retornará "Maria", pois a primeira posição é sempre 0.
System.out.println(funcionarios.get(1));
Set
Agora, vamos analisar a diferença do List
para o próximo tipo, o Set
.
O Set
é usado quando a ordenação não é o foco. Ele não permite duplicidade, então não poderíamos ter dois "João". As implementações do Set
incluem:
HashSet
;LinkedHashSet
eTreeSet
.O HashSet
é o mais rápido, pois não se preocupa com ordenação e não permite duplicidade. O LinkedHashSet
mantém a ordem de inserção, enquanto o TreeSet
permite ordenação, mas é mais lento.
O HashSet
é o mais utilizado por ser rápido e não se preocupar com a ordem.
Vamos criar um exemplo com Set
, desta vez com produtos. A criação é semelhante à da lista.
Abaixo do último println()
, declararemos um Set
de String
usando generics para produtos
e utilizaremos new HashSet<>()
para a implementação.
Set<String> produtos = new HashSet<>();
Para adicionar produtos, usaremos produtos.add()
, como "Água", por exemplo.
produtos.add("Água");
produtos.add("Coca-cola");
Se tentarmos adicionar "Água" novamente e imprimir, não será exibido, pois não é permitida duplicidade. Vamos copiar e colar a linha referente à "Água" para verificar isso, adicionando em seguida um println()
de produtos
.
produtos.add("Água");
produtos.add("Coca-cola");
System.out.println(produtos);
Ao executar a aplicação, o terminal exibirá "Coca-Cola" e "Água".
[Coca-Cola, Água]
Ele não considera a ordem de inserção, pois primeiro inserimos "Água" e depois "Coca-Cola", mas não permitiu a repetição de "Água", mostrando apenas dois elementos.
Antes de inserir, há uma verificação para ver se o elemento já existe na lista, impedindo a inclusão novamente. É importante entender para que serve cada estrutura e qual utilizar em cada caso de aplicação.
Map
Por fim, vamos abordar a última estrutura, o Map
. O Map
utiliza a ideia de chave-valor, não permitindo valores duplicados. Temos um índice e um valor que compõem esse índice.
As principais implementações são:
HashMap
;LinkedHashMap
eTreeMap
.O objetivo é semelhante ao do Set
. O HashMap
é o mais rápido e não considera a ordem. O LinkedHashMap
mantém a ordem de inserção, e o TreeMap
é ordenado pela chave. No Map
, o primeiro item é a chave e o segundo é o valor.
Ele possui operações diferentes dos anteriores, como:
Put
;Get
;Remove
;ContainsKey
ekeySet
.A sintaxe é um pouco diferente, mas similar.
Vamos abordar um exemplo no qual criaremos um novo mapa para verificar. Já fizemos isso com pessoas funcionárias e produtos — agora, faremos com clientes.
Abaixo do último prinln()
, criaremos um Map
de Integer
e String
, onde Integer
será o código do cliente (a chave) e String
o nome (o valor). Para inicializar, usaremos um HashMap
.
Também importaremos Map
de java.util
.
Quando trabalhamos com coleções, não usamos os tipos primitivos. Em vez disso, usamos os Wrappers, classes que envolvem os tipos primitivos e adicionam funcionalidades extras. Essas classes encapsulam os tipos básicos e permitem que eles sejam usados em contextos onde objetos são necessários, como em listas e outras estruturas de coleção.
import java.util.Map;
public static void main(String[] args) {
// Código omitido
Map<Integer, String> clientes = new HashMap<>();
}
Para inserir, utilizaremos clientes.put()
em vez de add()
. Adicionaremos três clientes: clientes.put(1, "Maria")
, clientes.put(2, "Marcos")
e clientes.put(3, "Ana")
.
clientes.put(1, "Maria");
clientes.put(2, "Marcos");
clientes.put(3, "Ana");
Para visualizar o mapa, usaremos System.out.println(clientes)
.
System.out.println(clientes);
Ao executar, ele exibirá "Maria", "Marcos" e "Ana", sem considerar a ordenação, mostrando a chave e o valor rapidamente.
{1=Maria, 2=Marcos, 3=Ana}
Se inserirmos uma chave repetida, como clientes.put(1, "Ana")
, ele sobrescreverá o valor anterior.
clientes.put(1, "Maria");
clientes.put(2, "Marcos");
clientes.put(1, "Ana");
Ao visualizar, teremos 1=Ana
e 2=Marcos
. Mesmo com valores diferentes, a mesma chave considera o último valor inserido.
{1=Ana, 2=Marcos}
Para buscar uma posição específica, usamos get()
. Se quisermos apenas o cliente com chave 1, usaremos clientes.get(1)
no println()
, que retornará "Ana", pois sobrescrevemos o valor.
System.out.println(clientes.get(1));
Ana
Vamos voltar a Ana para a chave 3, evitando confusões.
clientes.put(1, "Maria");
clientes.put(2, "Marcos");
clientes.put(3, "Ana");
Se quisermos a chave 2, retornará "Marcos".
System.out.println(clientes.get(2));
Marcos
Essa abordagem garante que buscamos pela chave, não pela ordem de inserção.
Um cenário para trabalhar com essa busca seria usar o CPF como chave e o nome como valor. Ao digitar o CPF, que é um identificador único, localizaríamos a pessoa correspondente, independentemente da ordem de armazenamento.
Trabalhar com coleções envolve chamar a coleção e, ao adicionar um ponto, visualizar todos os métodos disponíveis. É essencial consultar a documentação e exemplos, pois nenhum curso abrange todos os métodos. A curiosidade e a experimentação são fundamentais para explorar o universo de possibilidades com coleções em Java.
Esperamos que tenham gostado deste vídeo. No próximo, falaremos mais sobre como manipular essas informações utilizando Streams.
Um recurso fundamental ao trabalhar com coleções são as Streams, pois nos permitem realizar operações funcionais e processar essas coleções de forma funcional.
Isso significa que, em vez de utilizar um laço e realizar ações em cada elemento da coleção, a API nos oferece o recurso de fazer isso por meio de operações intermediárias e terminais. Isso ficará mais claro ao longo da explicação.
Ao trabalhar com a API de Streams, geralmente desejamos utilizar métodos para realizar filtros, transformações e agregações sem modificar a coleção original. Sempre faremos isso em outra coleção, mantendo a coleção original inalterada.
Teremos operações intermediárias que processam dados, gerando novos Streams, e operações terminais que encerram o fluxo, como uma impressão ou uma soma, finalizando a operação.
As operações intermediárias mais comuns são:
filter()
emap()
.O filter
é utilizado para filtrar, enquanto o map
permite realizar transformações.
As operações terminais mais comuns são:
reduce()
ecollect()
.O reduce()
é usado para reduzir e finalizar algum cálculo, e o collect()
pode transformar os dados em outra lista.
Abordaremos alguns exemplos para esclarecer, e perceberemos como a API de Streams do Java é incrível.
filter()
No IntelliJ, acessaremos a classe Principal
só com a estrutura implementada:
Principal.java
:
package br.com.alura;
public class Principal {
public static void main(String[] args) {
}
}
Entre as chaves do método main()
, criaremos uma lista de String
de pessoas funcionárias, como fizemos anteriormente. Em vez de adicionar item a item, utilizaremos List.of()
para incluir alguns nomes padrão: "Ana", "Bruno", "Carlos" e "Amanda".
Também devemos importar a classe List
do pacote java.util
:
import java.util.List;
List<String> funcionarios = List.of("Ana", "Bruno", "Carlos", "Amanda");
Suponhamos que desejássemos filtrar todos os funcionários que começam com a letra A, pois vamos presenteá-los. A cada dia da semana, será a vez de quem começa com uma letra.
Para isso, criaremos uma nova lista de String
chamada funcionariosLetraA
. Para pegar os elementos da lista de funcionários e os transformar em uma nova lista por meio de programação funcional, chamaremos a coleção funcionários
e utilizaremos .stream()
para indicar o início das operações.
List<String> funcionariosLetraA = funcionarios.stream()
Vamos pular uma linha para maior clareza e utilizaremos .filter()
, pois queremos filtrar. No momento do filtro, precisaremos realizar uma operação com lambda (λ): para cada pessoa funcionária f
(sendo f
o predicado), verificaremos se o nome dela começa com A. Diremos: para cada funcionário f
, verifique se F.startsWith("A")
.
List<String> funcionariosLetraA = funcionarios.stream()
.filter(f -> f.startsWith("A"))
Nessa linha, filtramos cada pessoa funcionária da lista, verificando se seu nome começa com a letra A. Isso gerará outro Stream apenas com pessoas funcionárias cujos nomes começam com a letra A.
Em seguida, utilizaremos .collect()
para gerar essa nova lista.
List<String> funcionariosLetraA = funcionarios.stream()
.filter(f -> f.startsWith("A"))
.collect(Collectors.toList());
Portanto, estamos dizendo: pegue a lista de pessoas funcionárias, filtre todas com a letra A e crie uma nova lista chamada funcionariosLetraA
.
Agora, imprimiremos todos os nomes. Primeiro, imprimiremos a lista de pessoas funcionárias e, logo abaixo, a lista destas que possuem a inicial A, para verificar se o filtro foi realizado corretamente.
System.out.println(funcionarios);
System.out.println(funcionariosLetraA);
Ao executar a aplicação, veremos Ana, Bruno, Carlos e Amanda, e, na sequência, apenas Ana e Amanda, as pessoas funcionárias com a letra A.
[Ana, Bruno, Carlos, Amanda]
[Ana, Amanda]
Trabalhar dessa maneira torna nosso código muito mais simples e limpo, em comparação a percorrer a coleção, verificar se o item começa com "A", adicionar em outra coleção e depois imprimir. Assim, o processo fica mais objetivo, claro e rápido de processar.
map()
Fizemos um exemplo com filtro, agora vamos transformar um dado usando o map()
ao invés do filtro.
Vamos criar uma nova lista, desta vez de comissão das pessoas funcionárias. Será uma lista de Double
, chamada valorVendas
, onde colocaremos todos os valores de vendas para calcular as comissões.
Utilizaremos List.of()
para inserir os elementos: 500, 1.800 e 6.200, por exemplo. Adicionaremos .0
no final de cada valor para evitar erros, já que é uma lista de double
.
List<Double> valorVendas = List.of(500.0, 1800.0, 6200.0);
Agora, queremos calcular 5% de comissão sobre cada valor. Criaremos uma nova lista de Double
, chamada comissao
. Para essa lista, pegaremos valorVendas
, aplicaremos .stream()
e, em seguida, .map()
.
No map()
, definimos o predicado: para cada valor de venda v
, multiplicaremos ele mesmo por 0.05 para calcular a comissão. Em seguida, usaremos collect()
, inserindo Collectors.toList()
para armazenar na lista comissao
.
List<Double> comissao = valorVendas.stream()
.map(v -> v * 0.05)
.collect(Collectors.toList());
Ao imprimir a lista de valorVendas
e logo abaixo a lista de comissao
, veremos que cada valor teve 5% aplicado.
System.out.println(valorVendas);
System.out.println(comissao);
No exemplo, R$ 500,00, R$ 1.800,00 e R$ 6.200,00 resultaram em comissões de R$ 25,00, R$ 90,00 e R$ 310,00, respectivamente.
[500.0, 1800.0, 6200.0]
[25.0, 90.0, 310.0]
reduce()
Para finalizar, vamos somar todas as vendas. Criaremos um double
chamado totalVendas
, recebendo a lista valorVendas
. Nesta, aplicaremos .stream()
e .reduce()
, especificando que queremos somar os valores.
Utilizamos 0.0
para representar o valor inicial da soma e Double::sum
para realizar a soma.
double totalVendas = valorVendas.stream()
.reduce(0.0, Double::sum);
Ao imprimir totalVendas
, ele apresentará o resultado: o total de vendas será R$ 8.500,00.
System.out.println("Total Vendas: " + totalVendas);
Total Vendas: 8500.00
Podemos perceber como é fácil trabalhar com Streams: com praticamente uma linha, conseguimos somar todos os itens de uma coleção de forma rápida e simples, utilizando recursos que a API já oferece.
Vale a pena estudar e se aprofundar em coleções e Streams para trabalhar melhor com dados, de forma eficiente e organizada, aplicando filtros, mapas, transformações e outros recursos que tornam a aplicação mais performática.
Esperamos que tenham gostado deste conteúdo. Não deixe de avaliar o curso e enviar seu feedback! Mostre os códigos que você está desenvolvendo e nos marque nas redes sociais!
Se tiver dúvidas, não hesite em participar do fórum ou enviar mensagens no Discord. Queremos ouvir você para trazer conteúdos cada vez melhores.
Agradecemos por nos acompanhar até aqui. Até o próximo curso!
O curso Praticando Java: coleções e streams possui 29 minutos de vídeos, em um total de 17 atividades. Gostou? Conheça nossos outros cursos de Java em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.