DevTools

Cheatsheet Java

Linguagem de programação orientada a objetos

Voltar às linguagens
Java
126 cards encontrados
Categorias:
Versões:

Sintaxe Básica


14 cards
Olá Mundo
public class App {
    public static void main(String[] args) {
        System.out.println("Olá Mundo");
    }
}

Todo o programa Java começa num método main dentro de uma class. O System.out.println() imprime uma linha no consola. O nome do ficheiro deve igualar o nome da classe pública.

Constantes (final)
final int MAX = 100;
final String APP = "MinhaApp";

// constante de classe
public static final double PI = 3.14159;

A palavra-chave final torna a variável imutável após a primeira atribuição. Por convenção, constantes usam MAIÚSCULAS. Combina com static para constantes partilhadas pela classe.

Comparar Strings
String a = "Ana";
String b = new String("Ana");

a.equals(b);          // true (conteúdo)
a == b;               // false (referência)
a.equalsIgnoreCase("ana"); // true

Para comparar o conteúdo de strings usa equals(), nunca == (que compara referências). O equalsIgnoreCase() ignora maiúsculas. É um dos erros mais comuns em Java.

Arrays multidimensionais
int[][] matriz = {
    { 1, 2, 3 },
    { 4, 5, 6 }
};
int val = matriz[1][2]; // 6

Um array de arrays cria uma matriz. Declara com tipo[][] e acede com dois índices [linha][coluna]. As linhas podem ter tamanhos diferentes, pois cada linha é um array independente.

Variáveis
int idade = 30;
String nome = "Ana";
double preco = 9.99;
boolean ativo = true;
char letra = 'A';

Declara variáveis indicando o tipo seguido do nome e do valor. Java é fortemente tipado, pelo que cada variável tem um tipo fixo. Usa String para texto e char para um único carácter entre plicas.

Operadores aritméticos
int a = 10, b = 3;
int soma = a + b;   // 13
int resto = a % b;  // 1
a++;                // incrementa
b--;                // decrementa
int div = a / b;    // 3 (inteiro)

Os operadores + - * / fazem as contas básicas e % devolve o resto da divisão. Como ambos são int, a divisão trunca o resultado. Usa ++ e -- para incrementar ou decrementar.

Casting
int n = (int) 3.99;     // 3 (trunca)
double d = (double) 5;  // 5.0
long l = 100;           // widening automático
int x = (int) l;        // narrowing manual

O casting converte entre tipos. O widening (pequeno para grande) é automático; o narrowing (grande para pequeno) exige (tipo) explícito e pode perder dados.

Estrutura e comentários
// comentário de linha
/* comentário
   de bloco */
/** Javadoc: documentação */

package com.exemplo;
import java.util.List;

Java tem comentários de linha (//), de bloco (/* */) e de documentação (/** */). O package organiza as classes e o import traz classes de outros pacotes.

Tipos primitivos
byte  b = 127;      // 8 bits
short s = 32000;    // 16 bits
int   i = 2_000_000; // 32 bits
long  l = 9_000_000_000L;
float f = 3.14f;
double d = 3.14159;
boolean ok = true;
char c = '€';

Os tipos primitivos não são objetos e guardam o valor diretamente. O int é o inteiro padrão e o double o decimal padrão. O sufixo L marca um long e f um float.

Comparação e lógicos
boolean iguais = (a == b);
boolean maior  = (a > b);
boolean ambos  = (a > 0 && b > 0);
boolean algum  = (a > 0 || b > 0);
boolean negado = !ativos;

Os operadores de comparação (==, !=, >, <=) devolvem boolean. Os lógicos && (e), || (ou) e ! (negação) combinam condições. Usa == apenas para primitivos.

Conversões de texto
int n = Integer.parseInt("42");
double d = Double.parseDouble("3.14");
String s = String.valueOf(42);
String t = 42 + "";      // atalho
String bin = Integer.toBinaryString(10);

Converte texto em número com Integer.parseInt() e Double.parseDouble(). Para o sentido inverso usa String.valueOf(). Estes métodos lançam NumberFormatException se o texto for inválido.

Wrappers
Integer n = 10;        // autoboxing
int x = n;             // unboxing
int v = Integer.parseInt("123");
double d = Double.parseDouble("3.14");
int max = Integer.MAX_VALUE;

Cada tipo primitivo tem uma classe wrapper (Integer, Double, Boolean...). O autoboxing converte automaticamente entre primitivo e objeto. Os wrappers oferecem métodos úteis como parseInt().

Strings
String s = "Olá " + nome;
String f = String.format("%d anos", idade);
int tam = s.length();
String maius = s.toUpperCase();
boolean tem = s.contains("Olá");

A String é imutável em Java. Concatena com + ou formata com String.format(). Métodos úteis: length(), toUpperCase(), contains(), substring() e trim().

Arrays
int[] nums = { 1, 2, 3 };
int[] out = new int[5];
out[0] = 10;
int tam = nums.length;
int primeiro = nums[0];

Um array tem tamanho fixo definido na criação. Declara com tipo[] e acede por índice (base 0). A propriedade length dá o tamanho. Usa new int[n] para criar um array vazio.

Controlo de Fluxo


11 cards
if / else
if (idade >= 18) {
    System.out.println("adulto");
} else if (idade > 12) {
    System.out.println("jovem");
} else {
    System.out.println("criança");
}

O if executa um bloco quando a condição é verdadeira. Encadeia alternativas com else if e usa else para o caso restante. As chavetas são opcionais para uma só instrução, mas recomendadas.

for
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

for (int i = 10; i > 0; i--) {
    System.out.println(i);
}

O ciclo for tem três partes: inicialização, condição e incremento. Repete enquanto a condição for verdadeira. É ideal quando sabes quantas iterações queres.

Operador ternário
String r = idade >= 18 ? "adulto" : "menor";

int max = (a > b) ? a : b;

O operador condicao ? valorSeVerdade : valorSeFalso é uma condicional numa linha. Devolve um dos dois valores conforme a condição. É prático para atribuições simples.

switch
switch (dia) {
    case 1:
        System.out.println("Seg");
        break;
    case 2:
        System.out.println("Ter");
        break;
    default:
        System.out.println("?");
}

O switch compara um valor com vários case. Cada caso precisa de break para sair, senão continua para o seguinte (fall-through). O default trata os valores não previstos.

for-each
int[] nums = { 1, 2, 3 };
for (int x : nums) {
    System.out.println(x);
}

for (String s : lista) {
    System.out.println(s);
}

O for-each percorre cada elemento de um array ou coleção sem gerir índices. A sintaxe é for (tipo item : colecao). É mais legível, mas não dá acesso direto ao índice.

break e continue
for (int i = 0; i < 10; i++) {
    if (i == 3) continue; // salta
    if (i == 7) break;    // termina
    System.out.println(i);
}

O continue salta para a próxima iteração e o break termina o ciclo por completo. Ambos ajudam a controlar o fluxo dentro de ciclos sem condições complexas.

switch expression (14+)
String r = switch (dia) {
    case 1 -> "Seg";
    case 2 -> "Ter";
    case 6, 7 -> "Fim de semana";
    default -> "?";
};

A sintaxe moderna com -> devolve um valor diretamente e não precisa de break. Podes agrupar vários casos com vírgulas (case 6, 7). É mais segura e legível que o switch clássico.

while
int x = 10;
while (x > 0) {
    System.out.println(x);
    x--;
}

O while repete enquanto a condição for verdadeira, testando antes de cada iteração. Se a condição começar falsa, o corpo nunca executa. Não te esqueças de atualizar a variável de controlo.

Ciclos com rótulo
externo:
for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
        if (i * j > 6) break externo;
    }
}

Um rótulo (label) identifica um ciclo externo. O break rotulo sai diretamente do ciclo etiquetado, útil em ciclos aninhados onde um break simples só sairia do mais interno.

switch com yield
int letras = switch (dia) {
    case 1 -> 3;
    case 2 -> {
        System.out.println("a calcular");
        yield 3;
    }
    default -> 0;
};

Quando um caso precisa de várias instruções, usa um bloco com yield para devolver o valor. O yield termina o caso e fornece o resultado da switch expression.

do-while
int x = 0;
do {
    System.out.println(x);
    x++;
} while (x < 10);

O do-while executa o corpo pelo menos uma vez, pois testa a condição no fim. É útil para menus ou validações em que queres correr o bloco antes de verificar.

Métodos e Lambdas


11 cards
Método simples
public int somar(int a, int b) {
    return a + b;
}

int r = somar(2, 3); // 5

Um método declara-se com modificador tipoRetorno nome(parametros). Usa return para devolver o resultado. O tipo de retorno deve corresponder ao valor devolvido.

Sobrecarga (overload)
int area(int l) { return l * l; }
int area(int a, int b) { return a * b; }
double area(double r) { return Math.PI * r * r; }

A sobrecarga permite vários métodos com o mesmo nome, desde que os parâmetros difiram em número ou tipo. O compilador escolhe a versão certa conforme os argumentos passados.

Method reference
lista.forEach(System.out::println);
lista.sort(String::compareTo);

Supplier<List<String>> s = ArrayList::new;

A referência de método Classe::metodo é um atalho para lambdas que apenas chamam um método. Formas comuns: objeto::metodo, Classe::metodo e Classe::new para construtores.

Método void
public void imprimir(String msg) {
    System.out.println(msg);
}

imprimir("Olá"); // sem retorno

Um método void não devolve nenhum valor. É usado para ações com efeitos secundários, como imprimir ou alterar estado. Não precisa de return (ou usa return; para sair cedo).

Construtor
public class Pessoa {
    String nome;

    public Pessoa(String nome) {
        this.nome = nome;
    }
}
Pessoa p = new Pessoa("Ana");

O construtor tem o mesmo nome da classe e não tem tipo de retorno. É chamado com new para inicializar o objeto. O this distingue o campo do parâmetro com o mesmo nome.

Recursão
int fatorial(int n) {
    if (n <= 1) return 1;
    return n * fatorial(n - 1);
}
fatorial(5); // 120

Um método recursivo chama-se a si próprio, resolvendo o problema em casos menores. Precisa sempre de um caso base para parar, senão causa StackOverflowError.

Método estático
public static int dobro(int x) {
    return x * 2;
}

int r = Math.max(3, 5); // chama sem instância

Um método static pertence à classe e não a uma instância, podendo ser chamado sem criar objeto. Não acede a campos de instância (this). É comum em utilitários como Math.

Encadeamento de construtores
public class Ponto {
    int x, y;
    Ponto() { this(0, 0); }
    Ponto(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

Um construtor pode chamar outro da mesma classe com this(...), evitando repetir código. A chamada deve ser a primeira instrução. É útil para definir valores por omissão.

Interfaces funcionais
Predicate<Integer> maior = n -> n > 10;
Function<Integer, Integer> dobro = n -> n * 2;
Supplier<String> ola = () -> "olá";
Consumer<String> imprime = System.out::println;

O pacote java.util.function traz interfaces funcionais prontas: Predicate (teste), Function (transforma), Supplier (fornece) e Consumer (consome). São a base das lambdas.

Varargs
int soma(int... nums) {
    int total = 0;
    for (int n : nums) total += n;
    return total;
}
soma(1, 2, 3, 4); // 10

O parâmetro tipo... aceita um número variável de argumentos, tratados internamente como um array. Deve ser o último parâmetro do método. Prático para funções como String.format().

Lambda
Runnable r = () -> System.out.println("Olá");

lista.forEach(x -> System.out.println(x));

Comparator<Integer> c = (a, b) -> a - b;

Uma lambda é uma função anónima com a sintaxe (params) -> corpo. Implementa interfaces funcionais de forma concisa. É muito usada com forEach(), sort() e streams.

Classes e Objetos


12 cards
Classe com getter/setter
public class Pessoa {
    private String nome;

    public String getNome() { return nome; }
    public void setNome(String n) { nome = n; }
}

O encapsulamento esconde os campos com private e expõe-nos através de getters e setters. Assim controlas como os dados são lidos e alterados.

Enum com campos
enum Planeta {
    TERRA(5.97e24), MARTE(6.39e23);

    final double massa;
    Planeta(double massa) { this.massa = massa; }
}

Um enum pode ter campos, construtor e métodos como uma classe. Cada constante chama o construtor com os seus argumentos. Útil para associar dados a cada valor.

Classe anónima
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Olá");
    }
};

Uma classe anónima define e instancia uma implementação sem lhe dar nome. É usada para implementar interfaces ou estender classes pontualmente. Hoje é muitas vezes substituída por lambdas.

Modificadores de acesso
public    // todos
protected // subclasses + pacote
(default) // só o pacote
private   // só a própria classe

Java tem quatro níveis de acesso. public é visível em todo o lado, private só dentro da classe, protected para subclasses e o padrão (sem modificador) restringe ao pacote.

this e super
this.nome = nome;   // campo da instância
this.metodo();      // método atual

super();            // construtor da superclasse
super.metodo();     // método do pai

O this refere-se à instância atual e o super à superclasse. O this() chama outro construtor da mesma classe e super() o construtor do pai.

Classe imutável
public final class Moeda {
    private final double valor;
    public Moeda(double valor) { this.valor = valor; }
    public double getValor() { return valor; }
}

Uma classe imutável tem campos final, construtor que os define e apenas getters (sem setters). Marcar a classe como final impede herança. Objetos imutáveis são seguros em concorrência.

Record (16+)
public record Ponto(int x, int y) {}

Ponto p = new Ponto(1, 2);
p.x();        // 1 (não é getX)
p.toString(); // Ponto[x=1, y=2]

Um record é uma classe de dados imutável gerada automaticamente. Cria construtor, getters (com o nome do campo), equals(), hashCode() e toString(). Ideal para transportar dados.

static
public class Contador {
    static int total = 0;
    static void inc() { total++; }
}
Contador.inc(); // partilhado por todos

Membros static pertencem à classe e são partilhados por todas as instâncias. Acede-se com Classe.membro. Um bloco static { } executa uma vez no carregamento da classe.

toString()
@Override
public String toString() {
    return "Pessoa[nome=" + nome + "]";
}
System.out.println(pessoa); // chama toString()

O método toString() devolve uma representação textual do objeto. É chamado automaticamente ao imprimir ou concatenar. Sobrescreve-o para obter output legível em vez de Classe@hash.

Enum
enum Estado { ATIVO, INATIVO, SUSPENSO }

Estado e = Estado.ATIVO;
Estado[] todos = Estado.values();
String nome = e.name();

Um enum define um conjunto fixo de constantes nomeadas. É mais seguro que usar inteiros ou strings. Os métodos values() e name() listam e obtêm os valores.

Classes aninhadas
class Externa {
    class Interna { }          // inner
    static class Estatica { }  // nested
}
Externa.Interna i = new Externa().new Interna();

Uma classe interna (inner) tem acesso aos campos da classe externa e precisa de uma instância dela. Uma static nested é independente. Servem para agrupar lógica relacionada.

equals() e hashCode()
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Pessoa p)) return false;
    return nome.equals(p.nome);
}
@Override
public int hashCode() { return nome.hashCode(); }

Sobrescreve equals() para comparar pelo conteúdo e hashCode() para usar o objeto em HashMap/HashSet. Devem ser sempre sobrescritos juntos: objetos iguais têm o mesmo hashCode.

Herança e Interfaces


11 cards
Herança (extends)
public class Animal {
    public void falar() { }
}
public class Cao extends Animal {
    @Override
    public void falar() { }
}

A herança usa extends para uma classe filha herdar campos e métodos do pai. Java só permite herdar de uma classe. O @Override indica que o método substitui o do pai.

Polimorfismo
Animal a = new Cao();
a.falar(); // versão de Cao

List<String> l = new ArrayList<>();

O polimorfismo permite tratar um objeto pela sua superclasse ou interface, executando o método da classe real. A variável Animal guarda um Cao, mas chama a implementação concreta em tempo de execução.

Sealed classes (17+)
public sealed interface Forma
    permits Circulo, Quadrado { }

public final class Circulo implements Forma { }

Uma classe sealed controla quais classes a podem estender ou implementar, através da cláusula permits. As filhas devem ser final, sealed ou non-sealed. Dá segurança ao switch por padrões.

Classe abstrata
public abstract class Forma {
    public abstract double area();
    public void descrever() { }
}

Uma classe abstract não pode ser instanciada e pode ter métodos abstratos (sem corpo) que as subclasses são obrigadas a implementar. Serve de modelo base com lógica partilhada.

final
public final class Imutavel { }
// class Filha extends Imutavel { } // ERRO

public final int x = 5; // constante
public final void metodo() { } // não sobrescreve

O final aplicado a uma classe impede a herança, a um método impede a sobrescrita e a uma variável torna-a constante. A classe String é final.

Múltiplas interfaces
public class Cao extends Animal
    implements Nadador, Brincalhao {
    public void nadar() { }
    public void brincar() { }
}

Java não permite herança múltipla de classes, mas uma classe pode implementar várias interfaces, separadas por vírgulas. É assim que se obtém comportamento múltiplo sem os problemas do diamante.

Interface
public interface Animal {
    void falar();
}
public class Cao implements Animal {
    public void falar() { }
}

Uma interface define um contrato de métodos que as classes implementam com implements. Uma classe pode implementar várias interfaces. É a base do polimorfismo em Java.

instanceof
if (obj instanceof Cao) {
    Cao c = (Cao) obj;
    c.falar();
}

O operador instanceof verifica se um objeto é de um determinado tipo antes de fazer casting. Evita ClassCastException. Devolve true ou false.

Object: a raiz
Object o = "texto"; // tudo é Object

o.toString();
o.equals(outro);
o.hashCode();

Todas as classes herdam implicitamente de Object. Por isso qualquer objeto tem toString(), equals() e hashCode(). Uma variável Object aceita qualquer tipo.

Default methods
public interface Animal {
    void falar();
    default void dormir() {
        System.out.println("zzz");
    }
}

Um default method tem implementação na própria interface, evitando quebrar classes existentes ao adicionar métodos. As classes podem usá-lo diretamente ou sobrescrevê-lo.

instanceof pattern (16+)
if (obj instanceof Cao c) {
    c.falar(); // c já está disponível
}

O pattern matching para instanceof combina a verificação e o casting numa só instrução. A variável c fica disponível dentro do bloco se o teste passar, eliminando o cast manual.

Coleções


13 cards
List (ArrayList)
List<Integer> lista = new ArrayList<>();
lista.add(1);
lista.add(2);
lista.get(0);     // 1
lista.remove(0);
lista.size();     // 1

A List é uma coleção ordenada que aceita duplicados. O ArrayList é a implementação mais comum, baseada em array dinâmico. Acede por índice com get(i) e adiciona com add().

Set (HashSet)
Set<String> set = new HashSet<>();
set.add("a");
set.add("a");      // ignorado
set.contains("a"); // true
set.size();        // 1

Um Set é uma coleção sem duplicados. O HashSet é o mais rápido, sem ordem garantida. O add() de um elemento existente não tem efeito. Útil para eliminar repetidos.

Iterator
Iterator<String> it = lista.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.isEmpty()) it.remove();
}

Um Iterator percorre uma coleção com hasNext() e next(). É a única forma segura de remover elementos com remove() durante a iteração sem lançar ConcurrentModificationException.

Coleções imutáveis
List<String> l = List.of("a", "b", "c");
Set<Integer> s = Set.of(1, 2, 3);
Map<String, Integer> m = Map.of("a", 1, "b", 2);
// l.add("d"); // UnsupportedOperationException

Os métodos de fábrica List.of(), Set.of() e Map.of() criam coleções imutáveis e compactas. Qualquer tentativa de modificação lança UnsupportedOperationException.

LinkedList
List<String> lista = new LinkedList<>();
lista.add("a");
lista.addFirst("b");
lista.addLast("c");
String p = lista.get(0);

A LinkedList guarda os elementos em nós encadeados. É eficiente a inserir/remover nas extremidades com addFirst() e addLast(), mas mais lenta a aceder por índice que o ArrayList.

TreeSet
Set<Integer> nums = new TreeSet<>(List.of(3, 1, 2));
nums.first(); // 1
nums.last();  // 3

O TreeSet mantém os elementos ordenados. Oferece métodos como first() e last() para aceder aos extremos. Os elementos devem ser comparáveis entre si.

Comparable
public class Pessoa implements Comparable<Pessoa> {
    @Override
    public int compareTo(Pessoa o) {
        return nome.compareTo(o.nome);
    }
}

A interface Comparable define a ordenação natural de uma classe através do método compareTo(). Devolve negativo, zero ou positivo. Permite usar Collections.sort() diretamente.

Map (HashMap)
Map<String, Integer> mapa = new HashMap<>();
mapa.put("a", 1);
mapa.get("a");          // 1
mapa.containsKey("a");  // true
mapa.getOrDefault("z", 0);

Um Map associa chaves a valores. O HashMap é a implementação padrão, sem ordem garantida. Usa put() para inserir e get() para ler. getOrDefault() evita null.

Queue e Deque
Queue<String> fila = new LinkedList<>();
fila.offer("a");   // adiciona
fila.poll();       // remove e devolve
fila.peek();       // vê sem remover

Deque<String> pilha = new ArrayDeque<>();
pilha.push("x");   // LIFO
pilha.pop();

A Queue é uma fila FIFO com offer()/poll(). O Deque permite inserir/remover nas duas pontas e serve de pilha LIFO com push()/pop().

Comparator
lista.sort(Comparator.comparing(Pessoa::getNome));
lista.sort(Comparator.comparingInt(Pessoa::getIdade).reversed());

Um Comparator define uma ordenação externa, sem alterar a classe. Cria-se com Comparator.comparing() e encadeia com reversed() ou thenComparing(). Ideal para várias ordenações.

TreeMap e LinkedHashMap
Map<String, Integer> ord = new TreeMap<>();   // chaves ordenadas
Map<String, Integer> ins = new LinkedHashMap<>(); // ordem de inserção

O TreeMap mantém as chaves ordenadas naturalmente e o LinkedHashMap preserva a ordem de inserção. Escolhe conforme precisas de ordenação ou de manter a sequência em que adicionaste.

Arrays (utilitário)
int[] nums = { 3, 1, 2 };
Arrays.sort(nums);
Arrays.toString(nums);     // [1, 2, 3]
List<Integer> l = Arrays.asList(1, 2, 3);
int[] copia = Arrays.copyOf(nums, 5);

A classe Arrays traz utilitários para arrays: sort() ordena, toString() imprime, asList() converte em lista e copyOf() copia. Não confundir com o tipo array.

Collections
Collections.sort(lista);
Collections.reverse(lista);
Collections.shuffle(lista);
int max = Collections.max(nums);
List<String> sync = Collections.synchronizedList(lista);

A classe Collections oferece algoritmos estáticos: sort(), reverse(), shuffle(), max()/min(). Também cria vistas sincronizadas ou imutáveis de coleções.

Exceções


10 cards
try / catch / finally
try {
    int r = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println(e.getMessage());
} finally {
    System.out.println("sempre executa");
}

O bloco try contém código de risco e o catch trata a exceção se ocorrer. O finally executa sempre, haja ou não erro, sendo ideal para limpeza.

Checked vs Unchecked
// checked: obriga a tratar (IOException)
// unchecked: RuntimeException
NullPointerException
IllegalArgumentException
IndexOutOfBoundsException

Exceções checked (herdam de Exception) obrigam a tratamento com try ou throws. As unchecked (herdam de RuntimeException) indicam erros de programação e não obrigam.

Exceções comuns
NullPointerException     // usar null
ArrayIndexOutOfBoundsException // índice inválido
ClassCastException       // cast errado
NumberFormatException    // parse inválido
IllegalArgumentException // argumento mau

As exceções unchecked mais frequentes: NullPointerException ao usar null, IndexOutOfBounds em índices inválidos e NumberFormatException ao converter texto. Prevenção vale mais que captura.

throw
if (valor < 0) {
    throw new IllegalArgumentException("Valor inválido");
}

A instrução throw lança uma exceção manualmente, interrompendo o fluxo. Cria-se uma instância da exceção com uma mensagem. É usada para validar argumentos e estados inválidos.

multi-catch
try {
    // código
} catch (IOException | SQLException e) {
    System.out.println("Erro: " + e);
}

O multi-catch trata várias exceções no mesmo bloco, separadas por |. Reduz código duplicado quando o tratamento é igual. As exceções não podem ser da mesma hierarquia.

Boas práticas com exceções
// específico antes de genérico
try { } catch (IOException e) { }
      catch (Exception e) { }

// não usar exceções para controlo de fluxo

Captura exceções específicas antes das genéricas e nunca ignores um catch vazio. Usa exceções para erros excecionais, não para controlo de fluxo normal. Prefere Optional a devolver null.

throws (declarar)
public void ler() throws IOException {
    Files.readString(Path.of("f.txt"));
}

A cláusula throws na assinatura declara que o método pode lançar uma exceção checked, transferindo a responsabilidade para quem o chama. O chamador deve tratar ou redeclarar.

Exceção personalizada
public class SaldoException extends Exception {
    public SaldoException(String msg) {
        super(msg);
    }
}
throw new SaldoException("Saldo insuficiente");

Cria a tua própria exceção estendendo Exception (checked) ou RuntimeException (unchecked). Passa a mensagem ao construtor com super(msg). Dá nomes terminados em Exception.

try-with-resources
try (var br = Files.newBufferedReader(p)) {
    String linha = br.readLine();
} // fecha automaticamente

O try-with-resources declara recursos entre parênteses e fecha-os automaticamente no fim, mesmo com erro. Exige que o recurso implemente AutoCloseable. Evita fugas de recursos.

Informação da exceção
catch (Exception e) {
    String msg = e.getMessage();
    e.printStackTrace();
    Throwable causa = e.getCause();
}

Um objeto de exceção oferece getMessage() (mensagem), printStackTrace() (pilha de chamadas) e getCause() (exceção original). Úteis para registar e depurar erros.

Recursos Modernos


11 cards
var (inferência)
var lista = new ArrayList<String>();
var n = 10;
var s = "texto"; // String

for (var i = 0; i < 5; i++) { }

O var (Java 10+) deixa o compilador inferir o tipo da variável local a partir do valor. Só funciona em variáveis locais inicializadas. Não muda o tipo: continua fortemente tipado.

main simplificado (21+)
void main() {
    System.out.println("Olá");
}

Com a pré-visualização de instance main methods, o ponto de entrada pode ser um simples void main() sem public static nem String[] args. Reduz a cerimónia para programas pequenos.

Stream.iterate e generate
Stream.iterate(1, n -> n * 2)
    .limit(5)
    .forEach(System.out::println);

Stream.generate(Math::random).limit(3);

O Stream.iterate() gera uma sequência infinita aplicando uma função ao valor anterior. O Stream.generate() cria valores com um Supplier. Usa limit() para os terminar.

Text blocks (15+)
String json = """
        {
            "nome": "Ana",
            "idade": 30
        }
        """;

Um text block usa três aspas """ para strings multi-linha sem escapes. A indentação comum é removida automaticamente. Ideal para JSON, SQL e HTML legíveis.

String: novos métodos
"  x  ".strip();      // "x" (Unicode)
"  x  ".trim();       // "x"
"".isBlank();         // true
"ab".repeat(3);       // "ababab"
"a\nb".lines();       // Stream<String>

Métodos modernos de String: strip() remove espaços (Unicode-aware), isBlank() testa se está vazia/espaços, repeat(n) repete e lines() devolve uma stream de linhas.

var em lambdas
BiFunction<Integer, Integer, Integer> soma =
    (var a, var b) -> a + b;

lista.forEach((var x) -> System.out.println(x));

Podes usar var nos parâmetros de uma lambda para manter a sintaxe consistente ou para lhes adicionar anotações. Todos os parâmetros devem usar var ou nenhum.

Switch pattern matching (21+)
String r = switch (obj) {
    case Integer i -> "inteiro " + i;
    case String s  -> "texto " + s;
    case null      -> "nulo";
    default        -> "outro";
};

O switch moderno aceita padrões de tipo em vez de só constantes. Cada case Tipo var testa e extrai o valor. Com classes sealed, o default pode ser dispensado.

Compact constructor (record)
public record Idade(int valor) {
    public Idade {
        if (valor < 0)
            throw new IllegalArgumentException();
    }
}

Um compact constructor num record omite os parâmetros e valida os campos antes da atribuição automática. Não precisa de this.valor = valor. Ideal para validar invariantes em records.

Unnamed variables (22+)
try {
    // ...
} catch (Exception _) {
    System.out.println("erro");
}
int total = 0;
for (var _ : lista) total++;

A variável sem nome _ marca valores que não vais usar, como exceções ou elementos de iteração. Torna a intenção clara e evita nomes descartáveis. Pode repetir-se no mesmo âmbito.

Pattern com guard (when)
String r = switch (valor) {
    case Integer i when i < 0 -> "negativo";
    case Integer i            -> "positivo";
    default                   -> "outro";
};

A cláusula when adiciona uma condição extra (guard) a um padrão. Permite refinar casos sem if interno. Os casos mais específicos devem vir antes dos genéricos.

Sequenced Collections (21+)
SequencedCollection<String> s = new LinkedHashSet<>();
s.addFirst("a");
s.addLast("z");
s.getFirst();
s.reversed();

A interface SequencedCollection uniformiza coleções com ordem, expondo addFirst(), addLast(), getFirst() e reversed(). Também existe SequencedMap.

Dicas e Boas Práticas


12 cards
StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
String s = sb.toString();

O StringBuilder é eficiente para concatenar strings em ciclos, pois é mutável e evita criar objetos a cada +. Usa append() e converte no fim com toString().

Convenções de nomes
class MinhaClasse { }       // PascalCase
int minhaVariavel;          // camelCase
void meuMetodo() { }        // camelCase
final int MAX_VALOR = 100;  // UPPER_SNAKE
package com.empresa.app;    // minúsculas

Por convenção: classes em PascalCase, variáveis e métodos em camelCase, constantes em UPPER_SNAKE_CASE e pacotes em minúsculas. Seguir o padrão torna o código idiomático.

printf e formatação
System.out.printf("Nome: %s, Idade: %d%n", nome, idade);
System.out.printf("Preço: %.2f%n", 9.99);
String s = String.format("%05d", 42); // "00042"

O printf() formata a saída com marcadores: %s (string), %d (inteiro), %f (decimal, %.2f com 2 casas) e %n (nova linha). String.format() devolve a string formatada.

Verificar null
if (obj != null) { }
Objects.requireNonNull(obj, "não pode ser null");
boolean eq = Objects.equals(a, b);
String h = Objects.toString(obj, "padrão");

A classe Objects ajuda a lidar com null: requireNonNull() lança exceção se for nulo, equals() compara com segurança e toString() dá um valor por omissão.

Math
Math.max(3, 5);     // 5
Math.min(3, 5);     // 3
Math.abs(-7);       // 7
Math.pow(2, 3);     // 8.0
Math.sqrt(16);      // 4.0
Math.round(3.6);    // 4

A classe Math oferece funções matemáticas estáticas: max()/min(), abs() (valor absoluto), pow() (potência), sqrt() (raiz) e round() (arredondar).

Random
Random r = new Random();
int n = r.nextInt(100);     // 0 a 99
double d = r.nextDouble();  // 0.0 a 1.0

int dado = ThreadLocalRandom.current().nextInt(1, 7);

A classe Random gera números aleatórios: nextInt(limite) para inteiros e nextDouble() para decimais. O ThreadLocalRandom é preferível em concorrência e aceita intervalos.

Importar
import java.util.List;
import java.util.*;
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;

O import traz classes de outros pacotes. import java.util.* importa todas as classes do pacote e import static permite usar membros estáticos (como PI) sem o nome da classe.

Datas (java.time)
LocalDate hoje = LocalDate.now();
LocalDate data = LocalDate.of(2024, 1, 15);
LocalDate amanha = hoje.plusDays(1);
int ano = hoje.getYear();

O pacote java.time (Java 8+) trata datas de forma imutável. LocalDate é uma data sem hora, LocalTime uma hora e LocalDateTime ambos. Usa plusDays() para somar.

Ficheiros (Files)
Path p = Path.of("dados.txt");
String texto = Files.readString(p);
List<String> linhas = Files.readAllLines(p);
Files.writeString(p, "Olá");

A classe Files (com Path) simplifica a leitura e escrita de ficheiros. readString() lê tudo, readAllLines() devolve uma lista de linhas e writeString() escreve. Lança IOException.

Compilar e executar
javac App.java      // compila -> App.class
java App            // executa

java App.java       // executa direto (11+)
java -jar app.jar   // executa um JAR

O javac compila o código-fonte para bytecode (.class) e o java executa-o na JVM. Desde o Java 11 podes correr um ficheiro diretamente com java App.java.

Ler input (Scanner)
Scanner sc = new Scanner(System.in);
String nome = sc.nextLine();
int idade = sc.nextInt();
sc.close();

O Scanner lê dados do consola. nextLine() lê uma linha de texto e nextInt() um inteiro. Fecha com close() quando terminares. Para performance usa BufferedReader.

Boas práticas
// prefere interfaces nos tipos
List<String> l = new ArrayList<>();

// usa try-with-resources
// evita null: devolve Optional ou listas vazias
// métodos curtos e coesos

Declara variáveis pelo tipo da interface (List em vez de ArrayList) para flexibilidade. Devolve coleções vazias em vez de null, usa try-with-resources e mantém métodos pequenos.

Streams e Funcional


12 cards
Stream: filter e map
List<Integer> r = lista.stream()
    .filter(x -> x > 5)
    .map(x -> x * 2)
    .collect(Collectors.toList());

Uma stream processa coleções de forma funcional e declarativa. O filter() seleciona elementos e o map() transforma-os. A stream não altera a coleção original.

Stream: groupingBy
Map<String, List<Pessoa>> porCidade = pessoas.stream()
    .collect(Collectors.groupingBy(Pessoa::getCidade));

O Collectors.groupingBy() agrupa elementos por uma chave, devolvendo um Map de listas. É o equivalente a um "group by" de SQL. Combina com counting() para contar por grupo.

Optional: criar
Optional<String> a = Optional.of("Ana");
Optional<String> b = Optional.ofNullable(nome);
Optional<String> v = Optional.empty();

O Optional é um contentor que pode ou não ter um valor, evitando null. of() exige valor presente, ofNullable() aceita null e empty() cria um Optional vazio.

Stream: agregação
long n = lista.stream().count();
boolean algum = lista.stream().anyMatch(x -> x > 10);
boolean todos = lista.stream().allMatch(x -> x > 0);
Optional<Integer> prim = lista.stream().findFirst();

As operações terminais de agregação resumem a stream: count() conta, anyMatch()/allMatch() testam condições e findFirst() devolve o primeiro elemento num Optional.

Stream: flatMap
List<String> todas = listas.stream()
    .flatMap(List::stream)
    .collect(Collectors.toList());

O flatMap() transforma cada elemento numa stream e achata tudo numa só. É útil quando cada elemento gera vários resultados (ex.: listas de listas) e queres uma stream única e plana.

Optional: obter valor
String r = opt.orElse("Anónimo");
String l = opt.orElseGet(() -> calcular());
opt.ifPresent(System.out::println);
String v = opt.orElseThrow();

Para obter o valor com segurança usa orElse() (valor por omissão), orElseGet() (calculado), ifPresent() (só se existir) ou orElseThrow(). Evita chamar get() diretamente.

Stream: reduce
int soma = lista.stream()
    .reduce(0, (acc, x) -> acc + x);

int produto = lista.stream()
    .reduce(1, (a, b) -> a * b);

O reduce() combina todos os elementos num único valor, partindo de um acumulador inicial. O primeiro argumento é o valor inicial e o segundo a função que acumula. Substitui ciclos de soma/produto.

Stream: distinct/sorted/limit
List<Integer> r = lista.stream()
    .distinct()
    .sorted()
    .limit(5)
    .collect(Collectors.toList());

Operações intermédias comuns: distinct() remove duplicados, sorted() ordena e limit(n) restringe aos primeiros n elementos. Encadeiam-se antes da operação terminal.

Optional: map e filter
String r = opt
    .map(String::toUpperCase)
    .filter(s -> s.length() > 2)
    .orElse("vazio");

O Optional suporta operações funcionais: map() transforma o valor se presente e filter() mantém-no só se passar a condição. Se ficar vazio em qualquer passo, o orElse() devolve o padrão.

Stream: collect
List<String> nomes = pessoas.stream()
    .map(Pessoa::getNome)
    .collect(Collectors.toList());

String csv = pessoas.stream()
    .map(Pessoa::getNome)
    .collect(Collectors.joining(", "));

O collect() acumula os resultados numa estrutura. Collectors.toList() cria uma lista e Collectors.joining() junta strings com separador. É a operação terminal mais versátil.

Streams numéricos
int soma = lista.stream().mapToInt(x -> x).sum();
double media = lista.stream().mapToInt(x -> x).average().orElse(0);
int max = lista.stream().mapToInt(x -> x).max().orElse(0);

As streams numéricas (IntStream, DoubleStream) evitam o boxing e oferecem sum(), average() e max(). Converte com mapToInt() para aceder a estas operações.

Pipeline completo
List<String> top = pessoas.stream()
    .filter(p -> p.getIdade() >= 18)
    .sorted(Comparator.comparing(Pessoa::getNome))
    .map(Pessoa::getNome)
    .limit(3)
    .toList();

Um pipeline típico encadeia várias operações: filtra, ordena, transforma e limita. O toList() (Java 16+) é um atalho para collect(Collectors.toList()). As operações só executam na terminal.

Genéricos


9 cards
Classe genérica
public class Caixa<T> {
    private T valor;
    public void set(T valor) { this.valor = valor; }
    public T get() { return valor; }
}
Caixa<String> c = new Caixa<>();

Uma classe genérica usa um parâmetro de tipo <T> que é substituído ao instanciar. Permite escrever código reutilizável e type-safe. O T é uma convenção para "Type".

Wildcard (?)
void imprimir(List<?> lista) {
    for (Object o : lista) System.out.println(o);
}

O wildcard ? representa um tipo desconhecido. List<?> aceita listas de qualquer tipo, mas só podes ler os elementos como Object. Dá flexibilidade a métodos.

Restrições
// NÃO permitido:
// List<int> x;        // só objetos
// new T();            // sem instanciar
// new T[10];          // sem arrays de T
// if (x instanceof List<String>) {}

Genéricos só aceitam tipos de referência (não primitivos — usa wrappers). Não podes instanciar T, criar arrays de T nem usar instanceof com tipos genéricos, devido ao type erasure.

Método genérico
public <T> T primeiro(List<T> lista) {
    return lista.get(0);
}
String s = primeiro(List.of("a", "b"));

Um método genérico declara o tipo antes do retorno: <T> T nome(...). O tipo é inferido a partir dos argumentos. Funciona em qualquer classe, mesmo não genérica.

Upper bounded (? extends)
double soma(List<? extends Number> nums) {
    double total = 0;
    for (Number n : nums) total += n.doubleValue();
    return total;
}

O ? extends Number aceita Number ou subtipos (Integer, Double). É covariante: podes ler como Number, mas não adicionar elementos. Ideal para produtores de dados.

Diamond operator
List<String> a = new ArrayList<String>(); // antigo
List<String> b = new ArrayList<>();       // diamond

Map<String, List<Integer>> m = new HashMap<>();

O operador diamond <> deixa o compilador inferir os tipos a partir da declaração, evitando repeti-los. Torna o código mais limpo sem perder segurança de tipos.

Lower bounded (? super)
void adicionar(List<? super Integer> lista) {
    lista.add(1);
    lista.add(2);
}

O ? super Integer aceita Integer ou supertipos (Number, Object). É contravariante: podes adicionar Integer com segurança. Ideal para consumidores de dados (regra PECS).

Bounded types
public <T extends Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}

A restrição <T extends Tipo> limita o genérico a um tipo ou seus subtipos. Assim podes chamar métodos desse tipo (aqui compareTo()). Aceita classes e interfaces.

Type erasure
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
a.getClass() == b.getClass(); // true!

O type erasure remove os tipos genéricos em tempo de execução, substituindo T pelo seu limite. Por isso List<String> e List<Integer> são a mesma classe em runtime. A verificação é só em compilação.