Partilhar: WhatsApp
aulify · Sebenta
UC · Unidade de Competência · UC00619

Sebenta · Java (UC00619)

Linguagem OOP industrial e multiplataforma
50h · 4.5 pontos crédito Curso: T. Desenv. Software ↗ Referencial oficial SNQ
Índice

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

Onde se usa Java

Quando NÃO usar Java

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 é, 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

Princípios

Java moderno (vale a pena adoptar)

// 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:

Glossário

Recursos

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.