Curso de Java Intermediário | Estruturas de dados, Stream API, Generics, Packages, Records

Categorias

JavaBackendIntermediário

Informações do Curso

Carga Horária

1h

Nota Mínima para Aprovação

60%

Se você está estudando Java e quer aprender os principais recursos dessa linguagem, este curso de programação com certeza é para você! Nele, nós vamos entender conceitos fundamentais como pacotes, estruturas de dados, generics, records e a String API. Conceitos essenciais para qualquer programador que quer se aprofundar no Java. Ao longo do curso você vai entender como organizar seu código utilizando pacotes, a importância das estruturas de dados como listas, conjuntos e mapas, além de entender como os generics facilitam a reutilização de código. Também iremos aprender a utilizar a Stream API do Java e entender como ela pode simplificar o nosso trabalho com collections.

Fazer Prova e Tirar Certificado

Faça login e adquira créditos para se inscrever e fazer a prova

Material complementar

Java Intermediário | Estruturas de dados, Stream API, Generics, Packages e Records

Depois de dominar os fundamentos e a POO, o passo intermediário é conhecer as ferramentas que você vai usar todos os dias em código real: a organização em packages, as estruturas de dados do Collections Framework, os generics para reuso seguro, os records e a Stream API. São conceitos independentes da primeira aula: se você já está familiarizado com a sintaxe base do Java, pode acompanhar sem problema.

Packages

Packages servem para organizar suas classes de forma hierárquica dentro do projeto. A estrutura dos packages reflete a estrutura de pastas, e isso é importante para a manutenção do código: cada package representa uma entidade, funcionalidade ou responsabilidade, o que separa responsabilidades e ainda evita conflito de nomes (você pode ter duas classes com o mesmo nome em packages diferentes).

Quando você cria um projeto, a IDE já adiciona a declaração de package no topo da classe, respeitando o caminho das pastas a partir do group definido na criação do projeto. Pastas padrão como src, main, java e resources são ignoradas no nome do package, elas dizem respeito à estrutura geral do projeto (código-fonte, testes, recursos como JSON/imagens/HTML), não à hierarquia das suas classes.

package com.kipper.javacourse.carro;

import java.util.List;                    // importando uma classe
import com.kipper.javacourse.carro.*;     // importando um pacote inteiro

Modificadores de acesso e visibilidade entre packages

Esse é o ponto que a professora mais detalha. Por padrão as classes vêm com public, ou seja, são visíveis fora do package onde estão. Mas e quando um atributo ou método não tem nenhum modificador?

package com.kipper.javacourse.carro;

public class Carro {
    String modelo;          // sem modificador = package-private
    public String cor;      // visível fora do package

    void teste() {          // package-private: só dentro do package carro
        System.out.println("teste");
    }
}
  • public: visível para qualquer classe, dentro ou fora do package.
  • sem modificador (package-private): visível para classes do mesmo package. Uma classe em outro package (ex.: Pessoa em com.kipper.javacourse.pessoa) não enxerga modelo nem chama teste() (erro: "is not public in ... cannot be accessed from outside package").
  • private: visível só dentro da própria classe; nem classes do mesmo package acessam.
  • protected: visível no mesmo package e nas subclasses, mesmo que a subclasse esteja em outro package.

Estruturas de dados (Collections Framework)

O Java Collections Framework é o framework que exporta um conjunto de interfaces e classes para armazenar, organizar e manipular grupos de objetos. Os quatro tipos principais são interfaces, a implementação concreta é uma classe à parte:

  • Map: estrutura de chave → valor (como os objetos do JavaScript ou os dicionários do Python).
  • Set: coleção que não permite duplicatas.
  • List: lista ordenada que permite elementos duplicados.
  • Queue: fila, usada para processamento em ordem (o primeiro que entra é o primeiro a sair).

List: ordenada, aceita duplicados

List.of(...) cria uma lista já com valores fixos. Quando os valores vão chegar dinamicamente, use new ArrayList<>() e vá adicionando:

List<String> lista = new ArrayList<>();
lista.add("Fernanda");
lista.add("Fernanda");      // permitido, lista aceita duplicados
lista.get(0);               // recupera pelo índice (ordem de inserção)

ArrayList é rápido para leitura por índice; LinkedList é melhor para muitas inserções/remoções no meio.

Set: sem duplicados

HashSet é a implementação mais comum da interface Set. Internamente usa uma hash table (tabela de dispersão), técnica geral da computação que evita colisões e dá eficiência nas operações de adição, remoção e verificação de existência.

Set<String> setStrings = new HashSet<>();
setStrings.add("Fernanda");
setStrings.add("Fernanda");          // ignorado: já existe (a IDE avisa "duplicate")
setStrings.contains("Fernanda");     // true, é assim que se verifica um valor no Set

HashSet não garante ordem; LinkedHashSet mantém a ordem de inserção; TreeSet mantém ordenado. Como Set é uma interface, você poderia criar sua própria implementação, mas o comum é usar HashSet.

Map: pares chave → valor

No Map você precisa tipar a chave e o valor (diferente de List/Set, que têm um tipo só). HashMap é a implementação mais popular (também baseada em hash table):

Map<String, String> map = new HashMap<>();
map.put("name", "Fernanda");      // put = chave, valor
map.put("surname", "Kipper");
map.get("name");                  // "Fernanda"
map.get("teste");                 // null, chave inexistente NÃO dá erro, retorna null

Queue: fila (FIFO)

Uma fila pode ser implementada com LinkedList (lista duplamente encadeada, em que cada posição aponta para a próxima e a anterior). A interface Queue expõe métodos de fila:

Queue<String> fila = new LinkedList<>();
fila.add("Fernanda");
fila.add("Léo");

fila.poll();     // retorna E REMOVE o primeiro ("Fernanda"); retorna null se vazia
fila.peek();     // só ESPIA o primeiro, sem remover
fila.remove();   // remove o primeiro, mas LANÇA exceção se a fila estiver vazia

A mesma LinkedList, se tipada diretamente como LinkedList<> (em vez de Queue<>), expõe mais métodos de lista encadeada pura: addFirst, addLast, getFirst, getLast, pollFirst, pollLast.

Quando usar cada uma

Precisa de...Use
Ordem e acesso por índice, aceita repetiçãoList / ArrayList
Garantir itens únicosSet / HashSet
Associar uma chave a um valorMap / HashMap
Processar elementos em ordem de chegada (FIFO)Queue / LinkedList
Coleção sempre ordenadaTreeSet / TreeMap

Generics

Você já vinha usando generics sem saber: quando declarou Queue<String> ou Map<String, String>, passou o tipo por parâmetro. Generics são tipos parametrizados que permitem criar classes, interfaces e métodos reutilizáveis por diferentes tipos, mantendo a segurança em tempo de compilação.

A interface Queue<E> recebe um type parameter E (o tipo dos elementos da fila). Por isso você não precisa de uma fila para String, outra para Integer, outra para Boolean, o comportamento é o mesmo, só o tipo muda.

public class Caixa<T> {
    private T conteudo;
    public void guardar(T item) { this.conteudo = item; }
    public T pegar() { return conteudo; }
}

Caixa<String> caixa = new Caixa<>();
caixa.guardar("livro");
String item = caixa.pegar(); // sem cast, sem risco

Generics com restrição (bounded type)

Quando o tipo é totalmente livre (como em Queue<E>), você só armazena/recupera o elemento, sem chamar métodos dele. Mas se você precisa chamar um método específico do tipo genérico, ele tem que estender uma interface/classe conhecida:

public interface Pintavel {
    void aplicarTinta();
    String getCor();
}

// só aceita tipos que implementem Pintavel
public class Pintura<T extends Pintavel> {
    private List<T> coisasQueVouPintar = new ArrayList<>();

    public void pintar(T coisa) {
        coisa.aplicarTinta();          // só funciona porque T é Pintavel
        coisasQueVouPintar.add(coisa);
    }
}

Assim Pintura aceita um Carro, uma Parede, uma Bicicleta, qualquer coisa que implemente Pintavel. A letra do parâmetro (T, E, etc.) é livre; por convenção usa-se um único caractere. Generics não são exclusivos do Java; quem já usou TypeScript vai reconhecer o conceito.

Records

record é um recurso mais recente: uma nova forma de declarar uma classe imutável. Os dados são definidos na construção do objeto e não podem mais ser alterados depois. O compilador gera automaticamente construtor, getters, equals, hashCode e toString, eliminando o código boilerplate.

public record Carro(String modelo, String cor, int ano, String placa) {}

Carro saveiro = new Carro("Saveiro", "Preto", 2020, "ABC1D23");
saveiro.ano();      // getter automático (sem "get"), retorna 2020
// saveiro.ano = 2021;  // ERRO: record é imutável

Não há setters (seria contra a imutabilidade), e os getters não levam o prefixo get, são o próprio nome do atributo.

Para que servem, se não posso alterar os valores? Records são muito usados para:

  • DTOs (Data Transfer Objects): dados que você recebe uma vez (ex.: o corpo de uma requisição HTTP no seu controller) e não vai alterar, só consultar/usar. Usar records para DTOs tem sido o padrão na comunidade.
  • POJOs (Plain Old Java Objects) imutáveis e value objects em geral.

Stream API

A Stream API (também recente) permite realizar operações funcionais sobre as collections de forma declarativa, encadeando filtros, transformações e reduções. A sintaxe tem uma pegada de programação funcional, o que pode confundir no começo.

A ideia central: você transforma a coleção em uma stream, aplica as operações, e retransforma o resultado de volta para a estrutura de dados que quer manipular.

List<String> nomes = List.of(
    "Fernanda Kipper", "Fernanda Santos", "Fernanda Moreira",
    "Eduardo Moreira", "Ariel Souza"
);

// filtrar só as "Fernanda", em maiúsculas, de volta para uma List
List<String> fernandas = nomes.stream()        // vira uma stream
    .filter(nome -> nome.startsWith("Fernanda"))// operação intermediária
    .map(String::toUpperCase)                   // operação intermediária
    .toList();                                  // retransforma em List (terminal)

Operações comuns:

  • filter: mantém só os elementos que passam na condição.
  • map: transforma cada elemento em outra coisa (ex.: nome -> nome.toUpperCase(), ou a forma mais concisa por method reference String::toUpperCase).
  • reduce: combina todos os elementos em um único valor. Recebe um valor inicial (0 para somar inteiros, "" para concatenar strings) e já retorna o tipo reduzido, sem precisar de toString no final:
String concatenado = nomes.stream()
    .map(nome -> nome.replace(" ", ""))
    .reduce("", (a, b) -> a + b);   // junta tudo numa única String
  • collect: materializa em outras collections além de List:
Set<String> set = nomes.stream()
    .filter(nome -> nome.startsWith("Fernanda"))
    .collect(Collectors.toSet());

A Stream API é extremamente usada em projetos Java reais: sem ela, seria preciso escrever for manuais e bem mais verbosos sobre listas, sets e maps. Com ela, a manipulação de estruturas de dados fica sucinta e legível.

Optional

Optional<T> representa um valor que pode ou não existir, evitando NullPointerException e deixando a ausência explícita.

Optional<String> achado = nomes.stream()
    .filter(nome -> nome.equals("Fernanda Kipper"))
    .findFirst();

achado.ifPresent(System.out::println);
String nome = achado.orElse("Padrão");

Resumo

  • Packages organizam o código de forma hierárquica e definem visibilidade. Atributo/método sem modificador é package-private (só dentro do package); a diferença para protected está nas subclasses.
  • Collections: List (ordenada, duplica), Set (única), Map (chave/valor), Queue (fila FIFO). HashSet/HashMap usam hash table; LinkedList implementa a fila.
  • Generics dão reuso com segurança de tipos; use <T extends Interface> quando precisar chamar métodos do tipo genérico.
  • Records são classes imutáveis sem boilerplate, ideais para DTOs e POJOs.
  • Stream API processa coleções de forma declarativa (filter/map/reduce/collect); lembre de retransformar a stream em coleção no final.
  • Optional torna a ausência de valor explícita e segura.

Sobre este Curso Gratuito de Java Intermediário | Estruturas de dados, Stream API, Generics, Packages, Records

Este curso de Java Intermediário | Estruturas de dados, Stream API, Generics, Packages, Records é oferecido gratuitamente pela Kipper Dev, fundada por Fernanda Kipper. O objetivo é democratizar o acesso ao conhecimento de programação e desenvolvimento, permitindo que qualquer pessoa aprenda sem custos.

Após assistir ao curso completo no YouTube, você pode validar seu conhecimento através de uma prova rigorosa. Ao ser aprovado com nota mínima de 60%, você receberá um certificado válido que pode ser usado como horas complementares em universidades brasileiras.

O certificado é emitido pela KipperDev Marketing e Treinamentos e possui uma chave de validação única que permite verificar sua autenticidade a qualquer momento.