Sebenta · Java (UC00619)
Introdução
Java é uma das linguagens mais usadas no mundo. Criada em 1995 pela Sun Microsystems (hoje Oracle), domina bancos, sistemas enterprise, Android, big data (Hadoop, Spark) e milhares de aplicações que provavelmente usas todos os dias. Aprender Java significa entrar num ecossistema vasto, maduro e bem remunerado.
Esta sebenta cobre Java moderno (JDK 17+/21+) com OOP completo, colecções, excepções, ficheiros, JDBC para base de dados e princípios de boa engenharia.
1. Java em contexto
Características-chave
- Compilado para bytecode: corre na JVM (Java Virtual Machine), funciona em qualquer SO sem recompilar.
- Tipado estaticamente: tipos verificados em compilação, menos bugs em runtime.
- Orientado a objectos: tudo é classe (ou primitivo).
- Garbage collected: gestão automática de memória.
- Maduro: 30 anos de evolução, ecossistema gigantesco.
Onde se usa Java
- Backend enterprise: Spring Boot, Jakarta EE.
- Android: Java/Kotlin (interoperáveis).
- Big Data: Hadoop, Spark, Kafka.
- Sistemas financeiros: bancos, trading.
- IDEs: IntelliJ, Eclipse, Android Studio (escritos em Java).
- Build tools: Maven, Gradle (e até Bazel parte).
Quando NÃO usar Java
- Scripting rápido → Python.
- Frontend → JavaScript.
- Sistemas de baixo nível → C/Rust.
- Prototipagem rápida → Python/Node.
Mas para aplicações grandes, com equipas grandes, que vão durar 10+ anos — Java continua imbatível.
2. Setup
Instalar o JDK
JDK = Java Development Kit (compilador + ferramentas). JRE = Java Runtime Environment (só corre, não desenvolve).
Recomendado: - Adoptium Temurin 21 (LTS) — gratuito, open source. - Amazon Corretto 21 — alternativa enterprise gratuita.
Verifica:
java --version
# openjdk 21.0.x ...
javac --version
# javac 21.0.x
IDE
- IntelliJ IDEA Community (gratuita) — padrão de facto.
- Eclipse IDE for Java Developers.
- VSCode com extensão "Extension Pack for Java".
IntelliJ é, de longe, a melhor experiência. Auto-import, refactoring excepcional, navegação inteligente.
Hello World standalone
// HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Olá mundo!");
}
}
Compila e corre:
javac HelloWorld.java # gera HelloWorld.class
java HelloWorld # corre (sem .class)
Single-file execution (Java 11+)
java HelloWorld.java # compila e corre directamente
Útil para scripts rápidos.
3. Projecto Maven
Para projectos reais, usa Maven (ou Gradle).
pom.xml mínimo
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.exemplo</groupId>
<artifactId>minha-app</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- dependências aqui -->
</dependencies>
</project>
Estrutura standard
minha-app/
├── pom.xml
└── src/
├── main/
│ ├── java/
│ │ └── com/exemplo/App.java
│ └── resources/
└── test/
├── java/
│ └── com/exemplo/AppTest.java
└── resources/
Comandos essenciais
mvn compile # compila
mvn test # corre testes
mvn package # cria .jar em target/
mvn clean install # limpa, compila, testa, instala localmente
mvn dependency:tree # mostra dependências
4. Sintaxe básica
Tipos primitivos
Java tem 8 tipos primitivos (com tamanho fixo):
| Tipo | Tamanho | Range |
|---|---|---|
byte |
8 bits | -128 a 127 |
short |
16 bits | ±32K |
int |
32 bits | ±2B |
long |
64 bits | muito grande |
float |
32 bits | decimal |
double |
64 bits | decimal preciso |
boolean |
- | true/false |
char |
16 bits | Unicode char |
int idade = 25;
long populacao = 8_000_000_000L; // L para long
double preco = 9.99;
float taxa = 0.21f; // f para float
boolean activo = true;
char letra = 'A';
String não é primitivo — é objecto:
String nome = "Ana";
String saudacao = "Olá, " + nome;
int len = nome.length();
String maiusc = nome.toUpperCase();
Variáveis com var
Java 10+ permite inferência de tipo:
var idade = 25; // int
var nome = "Ana"; // String
var lista = new ArrayList<Integer>(); // ArrayList<Integer>
Só funciona para locais (não em campos de classe ou parâmetros).
Operadores
// Aritméticos
int soma = 10 + 5; // 15
int sub = 10 - 5; // 5
int mul = 10 * 5; // 50
int div = 10 / 3; // 3 (truncado!)
int mod = 10 % 3; // 1
double real = 10.0 / 3; // 3.333...
// Atribuição compostas
int x = 10;
x += 5; // x = x + 5
x *= 2; // x = x * 2
// Comparação
boolean eq = (a == b); // CUIDADO: == em objectos compara referência!
boolean neq = (a != b);
// Lógicos
boolean e = (a && b);
boolean ou = (a || b);
boolean nao = !a;
// Strings: usa .equals(), não ==
String s1 = "Ana";
String s2 = "Ana";
boolean igual = s1.equals(s2); // ✅
boolean iguais = (s1 == s2); // ⚠️ funciona aqui por causa do pool, mas não confiável
Input do teclado
import java.util.Scanner;
Scanner sc = new Scanner(System.in);
System.out.print("Nome: ");
String nome = sc.nextLine();
System.out.print("Idade: ");
int idade = sc.nextInt();
System.out.println("Olá, " + nome + ", " + idade + " anos");
sc.close();
Estruturas de controlo
// If/else
if (idade >= 18) {
System.out.println("Adulto");
} else if (idade >= 13) {
System.out.println("Adolescente");
} else {
System.out.println("Criança");
}
// Switch tradicional
switch (dia) {
case 1: System.out.println("Segunda"); break;
case 2: System.out.println("Terça"); break;
default: System.out.println("Outro");
}
// Switch expression (Java 14+) — moderno
String tipo = switch (dia) {
case 1, 2, 3, 4, 5 -> "Útil";
case 6, 7 -> "Fim-de-semana";
default -> "Inválido";
};
Loops
// For tradicional
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
// Foreach (enhanced for)
String[] nomes = {"Ana", "Bruno", "Carla"};
for (String nome : nomes) {
System.out.println(nome);
}
// While
int i = 0;
while (i < 5) {
System.out.println(i);
i++;
}
// Do-while (corre pelo menos 1 vez)
int n;
do {
n = sc.nextInt();
} while (n < 0);
// Break e continue
for (int i = 0; i < 100; i++) {
if (i == 50) break; // sai do loop
if (i % 2 == 0) continue; // pula iteração
System.out.println(i);
}
Arrays
// Declaração + inicialização
int[] numeros = new int[5]; // [0, 0, 0, 0, 0]
int[] valores = {1, 2, 3, 4, 5};
// Acesso e modificação
numeros[0] = 10;
int primeiro = valores[0];
// Tamanho
int n = valores.length;
// Iterar
for (int i = 0; i < valores.length; i++) {
System.out.println(valores[i]);
}
// Multi-dimensional
int[][] matriz = {
{1, 2, 3},
{4, 5, 6}
};
int valor = matriz[1][2]; // 6
5. OOP — Programação Orientada a Objectos
Classe e objecto
Uma classe é um molde; um objecto é uma instância dessa classe.
public class Pessoa {
// Atributos (estado)
private String nome;
private int idade;
// Construtor
public Pessoa(String nome, int idade) {
this.nome = nome;
this.idade = idade;
}
// Métodos (comportamento)
public void apresentar() {
System.out.println("Olá, sou " + nome + " e tenho " + idade + " anos");
}
// Getters
public String getNome() {
return nome;
}
public int getIdade() {
return idade;
}
// Setters
public void setNome(String nome) {
this.nome = nome;
}
public void setIdade(int idade) {
if (idade < 0) {
throw new IllegalArgumentException("Idade negativa");
}
this.idade = idade;
}
}
Usar:
Pessoa p1 = new Pessoa("Ana", 25);
Pessoa p2 = new Pessoa("Bruno", 30);
p1.apresentar();
System.out.println(p2.getNome());
p1.setIdade(26);
Encapsulamento
Atributos privados, acesso controlado:
- private: só dentro da classe.
- protected: classe + subclasses + mesmo package.
- public: qualquer lado.
- (default, sem modificador): só mesmo package.
Vantagens: - Validação em setters. - Mudar implementação sem quebrar callers. - Pensar interfaces, não detalhes.
Construtores múltiplos (overloading)
public class Pessoa {
private String nome;
private int idade;
public Pessoa() {
this("Anónimo", 0);
}
public Pessoa(String nome) {
this(nome, 0);
}
public Pessoa(String nome, int idade) {
this.nome = nome;
this.idade = idade;
}
}
// Uso
Pessoa p1 = new Pessoa();
Pessoa p2 = new Pessoa("Ana");
Pessoa p3 = new Pessoa("Bruno", 30);
Herança
public class Empregado extends Pessoa {
private double salario;
private String cargo;
public Empregado(String nome, int idade, double salario, String cargo) {
super(nome, idade); // chama construtor da Pessoa
this.salario = salario;
this.cargo = cargo;
}
@Override
public void apresentar() {
super.apresentar();
System.out.println("Cargo: " + cargo + ", salário: " + salario + "€");
}
public double getSalarioAnual() {
return salario * 14; // 14 meses em PT
}
}
super = referência à classe pai.
@Override = anotação a indicar que sobrepões método (compilador valida).
Polimorfismo
Pessoa p = new Empregado("Ana", 25, 1000, "Dev");
p.apresentar(); // chama versão de Empregado (dynamic dispatch)
Uma variável do tipo Pessoa pode referenciar qualquer subclasse. O método chamado é o da classe real, não o do tipo da variável.
Classes abstractas
public abstract class Figura {
public abstract double area(); // sem implementação
public void descrever() {
System.out.println("Área: " + area());
}
}
public class Circulo extends Figura {
private double raio;
public Circulo(double raio) {
this.raio = raio;
}
@Override
public double area() {
return Math.PI * raio * raio;
}
}
Não se pode fazer new Figura() — só serve como base.
Interfaces
public interface Veiculo {
void acelerar();
void travar();
// Método default (Java 8+) — opcional sobrescrever
default void buzinar() {
System.out.println("Beep!");
}
// Constante
int VELOCIDADE_MAX = 120;
}
public class Carro implements Veiculo {
@Override
public void acelerar() { /* ... */ }
@Override
public void travar() { /* ... */ }
}
Uma classe implementa múltiplas interfaces mas herda de uma só classe.
Records (Java 16+)
Para classes simples de dados (DTOs):
public record Ponto(double x, double y) { }
// Equivalente a uma classe imutável com:
// - construtor (x, y)
// - getters x() e y()
// - equals, hashCode, toString automáticos
Ponto p = new Ponto(1, 2);
System.out.println(p.x()); // 1.0
System.out.println(p); // Ponto[x=1.0, y=2.0]
6. Strings
String s = "Hello World";
// Tamanho
int len = s.length(); // 11
// Substring
String sub = s.substring(0, 5); // "Hello"
// Case
String upper = s.toUpperCase(); // "HELLO WORLD"
String lower = s.toLowerCase();
// Substituição
String r = s.replace("World", "Java");
// Split
String csv = "Ana,Bruno,Carla";
String[] partes = csv.split(",");
// Concatenação eficiente
StringBuilder sb = new StringBuilder();
sb.append("Olá ");
sb.append("mundo");
sb.append("!");
String resultado = sb.toString();
// Formatação
String formatted = String.format("%s tem %d anos", "Ana", 25);
// Comparação
boolean igual = s.equals("Hello World");
boolean igualIgnoreCase = s.equalsIgnoreCase("HELLO WORLD");
// Contains
boolean contem = s.contains("World");
boolean comeca = s.startsWith("Hello");
boolean termina = s.endsWith("World");
7. Colecções
A package java.util tem o Collections Framework: estruturas de dados padronizadas.
List
Lista ordenada, permite duplicados.
import java.util.*;
List<String> nomes = new ArrayList<>();
nomes.add("Ana");
nomes.add("Bruno");
nomes.add("Carla");
// Acesso
String primeiro = nomes.get(0);
int n = nomes.size();
boolean tem = nomes.contains("Ana");
// Remover
nomes.remove("Bruno");
nomes.remove(0); // por índice
// Iterar
for (String nome : nomes) {
System.out.println(nome);
}
// Lista imutável
List<String> fixos = List.of("a", "b", "c");
Map
Chave → valor.
Map<String, Integer> idades = new HashMap<>();
idades.put("Ana", 25);
idades.put("Bruno", 30);
// Acesso
Integer idadeAna = idades.get("Ana"); // 25
Integer naoExiste = idades.get("Carla"); // null
Integer comDefault = idades.getOrDefault("Carla", 0); // 0
// Verificar
boolean tem = idades.containsKey("Ana");
// Remover
idades.remove("Ana");
// Iterar
for (Map.Entry<String, Integer> e : idades.entrySet()) {
System.out.println(e.getKey() + " -> " + e.getValue());
}
// Só keys ou só values
for (String k : idades.keySet()) { /* ... */ }
for (Integer v : idades.values()) { /* ... */ }
Set
Sem duplicados, sem ordem.
Set<String> unicos = new HashSet<>();
unicos.add("Ana");
unicos.add("Ana"); // ignorado
unicos.add("Bruno");
System.out.println(unicos.size()); // 2
// TreeSet — ordenado
Set<String> ordenados = new TreeSet<>();
ordenados.add("Carla");
ordenados.add("Ana");
ordenados.add("Bruno");
// Ana, Bruno, Carla (ordem alfabética)
Streams API
Programação funcional sobre colecções:
import java.util.stream.*;
List<Integer> numeros = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Filter
List<Integer> pares = numeros.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// Map
List<Integer> dobrados = numeros.stream()
.map(n -> n * 2)
.collect(Collectors.toList());
// Reduce / sum
int soma = numeros.stream()
.mapToInt(Integer::intValue)
.sum();
// Count
long quantos = numeros.stream()
.filter(n -> n > 5)
.count();
// Cadeias
String resultado = List.of("ana", "bruno", "carla").stream()
.map(String::toUpperCase)
.filter(s -> s.length() > 3)
.collect(Collectors.joining(", "));
// "BRUNO, CARLA"
8. Excepções
Hierarquia
Throwable
├── Error (não tratar — JVM crashes)
└── Exception
├── RuntimeException (unchecked — opcional tratar)
│ ├── NullPointerException
│ ├── ArithmeticException
│ └── IndexOutOfBoundsException
└── IOException (checked — obrigatório tratar)
Try-catch-finally
try {
int[] arr = {1, 2, 3};
System.out.println(arr[10]); // ArrayIndexOutOfBoundsException
int x = 10 / 0; // ArithmeticException
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Índice inválido: " + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("Erro aritmético: " + e.getMessage());
} catch (Exception e) {
System.out.println("Outro erro: " + e.getMessage());
} finally {
System.out.println("Sempre executa");
}
Multi-catch
try {
// ...
} catch (IOException | SQLException e) {
// tratar ambos igual
}
Try-with-resources
Para recursos que precisam ser fechados (ficheiros, conexões):
try (Scanner sc = new Scanner(new File("dados.txt"))) {
while (sc.hasNextLine()) {
System.out.println(sc.nextLine());
}
} // sc.close() automático, mesmo se houver excepção
Throw
public void retirar(double valor) {
if (valor <= 0) {
throw new IllegalArgumentException("Valor deve ser positivo");
}
if (valor > saldo) {
throw new RuntimeException("Saldo insuficiente");
}
saldo -= valor;
}
Excepções custom
public class SaldoInsuficienteException extends Exception {
public SaldoInsuficienteException(String message) {
super(message);
}
}
// Uso (checked — declara no throws)
public void sacar(double valor) throws SaldoInsuficienteException {
if (valor > saldo) {
throw new SaldoInsuficienteException(
"Saldo: " + saldo + "€, pedido: " + valor + "€"
);
}
saldo -= valor;
}
9. Ficheiros e I/O
NIO.2 (moderno, Java 7+)
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
Path path = Path.of("dados.txt");
// Ler tudo
String texto = Files.readString(path);
// Ler linhas
List<String> linhas = Files.readAllLines(path);
// Escrever
Files.writeString(path, "Olá mundo!\n");
Files.write(path, List.of("a", "b", "c"));
// Append
Files.writeString(path, "Nova linha\n", StandardOpenOption.APPEND);
// Verificar
boolean existe = Files.exists(path);
long tamanho = Files.size(path);
// Criar / apagar
Files.createFile(path);
Files.createDirectories(Path.of("pasta/subpasta"));
Files.delete(path);
// Listar directório
try (var stream = Files.list(Path.of("."))) {
stream.forEach(System.out::println);
}
Stream-based para ficheiros grandes
try (var stream = Files.lines(Path.of("dados.txt"))) {
stream
.filter(l -> l.contains("erro"))
.forEach(System.out::println);
}
Não carrega tudo em memória — lê linha a linha.
10. JDBC — Base de Dados
JDBC = Java Database Connectivity. API standard para qualquer SGBD.
Adicionar driver
No pom.xml (MySQL exemplo):
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
Para PostgreSQL: org.postgresql:postgresql.
Para SQLite: org.xerial:sqlite-jdbc.
Conexão
import java.sql.*;
String url = "jdbc:mysql://localhost:3306/loja";
String user = "root";
String password = "senha";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
System.out.println("Conectado!");
}
SELECT
String sql = "SELECT id, nome, preco FROM produtos WHERE preco > ?";
try (Connection conn = DriverManager.getConnection(url, user, pwd);
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setDouble(1, 10.0);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
int id = rs.getInt("id");
String nome = rs.getString("nome");
double preco = rs.getDouble("preco");
System.out.println(id + " | " + nome + " | " + preco + "€");
}
}
}
INSERT / UPDATE / DELETE
String sql = "INSERT INTO produtos (nome, preco) VALUES (?, ?)";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, "Caneta");
stmt.setDouble(2, 1.50);
int linhas = stmt.executeUpdate();
System.out.println(linhas + " linha(s) inserida(s)");
}
Padrão DAO (Data Access Object)
Separar acesso à BD do resto do código:
public class ProdutoDAO {
private final String url, user, password;
public ProdutoDAO(String url, String user, String password) {
this.url = url;
this.user = user;
this.password = password;
}
private Connection conectar() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
public List<Produto> listar() throws SQLException {
List<Produto> lista = new ArrayList<>();
String sql = "SELECT id, nome, preco FROM produtos";
try (Connection conn = conectar();
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
lista.add(new Produto(
rs.getInt("id"),
rs.getString("nome"),
rs.getDouble("preco")
));
}
}
return lista;
}
public void inserir(Produto p) throws SQLException {
String sql = "INSERT INTO produtos (nome, preco) VALUES (?, ?)";
try (Connection conn = conectar();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, p.getNome());
stmt.setDouble(2, p.getPreco());
stmt.executeUpdate();
}
}
}
Segurança — sempre PreparedStatement
// ❌ NUNCA — vulnerável a SQL injection
String sql = "SELECT * FROM users WHERE name = '" + nome + "'";
Statement st = conn.createStatement();
st.executeQuery(sql);
// ✅ SEMPRE
String sql = "SELECT * FROM users WHERE name = ?";
PreparedStatement st = conn.prepareStatement(sql);
st.setString(1, nome);
11. Testes com JUnit 5
Adicionar dependência:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
Test class:
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculadoraTest {
@Test
void somaSimples() {
Calculadora c = new Calculadora();
assertEquals(5, c.somar(2, 3));
}
@Test
void divisaoPorZeroLancaExcepcao() {
Calculadora c = new Calculadora();
assertThrows(ArithmeticException.class, () -> c.dividir(10, 0));
}
@BeforeEach
void setup() {
// antes de cada teste
}
}
Correr: mvn test.
12. Boas práticas
Convenções de código
- Classes/Records/Interfaces:
PascalCase—Cliente,BancoCentral,Comparable. - Métodos/variáveis:
camelCase—getNome(),totalVendas. - Constantes:
UPPER_SNAKE_CASE—MAX_TENTATIVAS,PI. - Packages:
lowercase.dotted—com.empresa.app.servicos. - Boolean: começa com
is/has—isActivo,hasPermissao.
Princípios
- Encapsular: atributos privados, comportamento público.
- Composição > herança quando possível.
- Imutabilidade: prefira
finale records. - Single Responsibility: cada classe faz uma coisa.
- DRY: extrai métodos para evitar repetição.
- YAGNI: não over-engineerize.
Java moderno (vale a pena adoptar)
varpara locais.recordpara DTOs.switch expression(->).text blocks(""").Optional<T>para evitar null.- Streams para colecções.
// Text block (Java 15+)
String json = """
{
"nome": "Ana",
"idade": 25
}
""";
// Optional — evita null
public Optional<User> findById(int id) {
// ...
return Optional.ofNullable(user);
}
// Uso
userOpt.ifPresent(u -> System.out.println(u.getName()));
String nome = userOpt.map(User::getName).orElse("Desconhecido");
13. Próximos passos
Depois desta base, explora:
- Spring Boot — framework dominante de backend Java.
- Hibernate / JPA — ORM para BD.
- Lombok — reduz boilerplate (getters, builders).
- JUnit + Mockito — testes completos.
- Gradle — alternativa moderna a Maven.
- Kotlin — linguagem JVM moderna, interoperável.
- Android development — Kotlin é hoje preferido.
- Reactive programming — Reactor, RxJava.
Glossário
- JVM: Java Virtual Machine, corre bytecode.
- JDK: Java Development Kit (inclui JRE + compilador).
- POJO: Plain Old Java Object — classe simples.
- DTO: Data Transfer Object.
- DAO: Data Access Object.
- POM: Project Object Model — manifesto Maven.
- Bytecode: código intermédio gerado pelo compilador.
- Garbage Collector: limpa objectos sem referências.
- Stack vs Heap: primitivos/refs na stack, objectos no heap.
- Checked Exception: declarada com
throws, obrigatório tratar. - Generics:
List<String>— tipos parametrizados.
Recursos
- Docs oficiais: docs.oracle.com/en/java.
- Tutorials: dev.java.
- Baeldung: baeldung.com (excelentes artigos práticos).
- Effective Java (Joshua Bloch) — livro essencial.
- JetBrains Academy — cursos gratuitos.
- Java Code Geeks, DZone Java.
Java é uma linguagem que recompensa o investimento. A sintaxe é mais verbosa que Python ou JavaScript, mas o tipo estático apanha bugs cedo, o ecossistema é gigantesco, e os salários são consistentemente altos. Dominar Java abre portas em finança, telco, governo, e grandes empresas tecnológicas. O segredo é construir muito código — não chega ler.