Cheatsheet Java
Linguagem de programação orientada a objetos
Java
Sintaxe Básica
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"); // truePara 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]; // 6Um 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
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
Método simples
public int somar(int a, int b) {
return a + b;
}
int r = somar(2, 3); // 5Um 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 retornoUm 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); // 120Um 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ânciaUm 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); // 10O 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
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 todosMembros 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
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 sobrescreveO 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
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(); // 1Um 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"); // UnsupportedOperationExceptionOs 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
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 fluxoCaptura 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 automaticamenteO 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
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
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úsculasPor 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
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
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.