Pular para o conteúdo

Este texto complementa os slides do Tópico 03 e serve como uma apostila de apoio.
A ideia é te guiar pelos principais conceitos da linguagem Java de forma gradual, com exemplos e analogias pensadas para estudantes de Engenharia de Software.

Pense neste material como o “manual do piloto” para suas primeiras aplicações Java.

Quando alguém fala “Java”, muitas vezes está misturando três coisas diferentes:

  • a linguagem Java (sintaxe, tipos, estruturas de controle);
  • a JVM (Java Virtual Machine), onde o programa roda;
  • o JDK/JRE (ferramentas e bibliotecas).

Uma forma de visualizar:

  • A linguagem é o idioma em que você escreve o código.
  • A JVM é o “intérprete universal” que entende um código intermediário chamado bytecode.
  • O JDK é a “caixa de ferramentas” de desenvolvimento (compilador, empacotador, depurador, etc.).
  • Criada na década de 90 na Sun Microsystems.
  • Tornou-se muito popular para aplicações corporativas, Android, sistemas bancários, etc.
  • Em 2010, a Oracle adquiriu a Sun e passou a manter a linguagem.
  • Hoje existe um ecossistema grande de implementações e distribuições (OpenJDK, Temurin, Oracle JDK, etc.).

A promessa original era: “Write once, run anywhere” (WORA) – escreva uma vez, rode em qualquer lugar que tenha uma JVM compatível.

  • Fortemente tipada e estática
    O tipo de cada variável é conhecido em tempo de compilação e não muda:

    int idade = 20;
    String nome = "Ramon";
    // idade = "vinte"; // erro de compilação
  • Baseada em classes e orientada a objetos
    Tudo gira em torno de classes e objetos.

  • Portável
    Você compila uma vez para bytecode e esse bytecode roda em qualquer sistema com uma JVM instalada.

  • Gerenciamento automático de memória
    Em vez de malloc/free (como em C), você cria objetos com new e o Garbage Collector limpa o que não é mais utilizado.

  • Grande ecossistema
    Frameworks como Spring, Quarkus, Micronaut; ferramentas de build como Maven e Gradle; ambientes de desenvolvimento como IntelliJ, Eclipse e VS Code.

Uma analogia útil:
Pense em Java como um jogo multiplataforma. O seu programa é o jogo; a JVM é o “console” que sabe rodar esse jogo em diferentes sistemas operacionais.

Os tipos primitivos são representados diretamente na memória (sem ser objeto):

  • inteiros: byte, short, int, long
  • reais: float, double
  • lógico: boolean
  • caractere: char
byte nivel = 3;
int pontos = 1500;
double taxa = 0.15;
boolean ativo = true;
char inicial = 'J';

A escolha do tipo importa em termos de:

  • memória (quanto menor, mais econômico);
  • faixa de valores (evitar estouro/overflow);
  • clareza de intenção (ex.: usar boolean para flags, não int 0/1).

Tipos não primitivos são objetos:

  • String
  • classes definidas por você (Pessoa, Conta, etc.)
  • coleções (List, Map, Set)
  • arrays (que também são objetos, apesar da sintaxe especial)
String nome = "Ramon";
List<String> linguagens = new ArrayList<>();
linguagens.add("Java");
linguagens.add("Python");
Map<String, Integer> idadePorNome = new HashMap<>();
idadePorNome.put("Ana", 22);

Analogia:
Um primitivo é como um valor escrito diretamente em um post-it.
Um objeto é como um cartão com o “endereço” (referência) de um armário onde o valor real fica guardado.

Uma classe é um “molde” que define atributos (dados) e métodos (comportamentos):

public class Pessoa {
public String nome;
public int idade;
public void andar() {
System.out.println(nome + " está andando...");
}
public void falar(String texto) {
System.out.println(nome + " diz: " + texto);
}
}

Você cria um objeto com o operador new:

Pessoa p = new Pessoa();
p.nome = "Ramon";
p.idade = 30;
p.andar();
p.falar("Olá, mundo!");

Analogia:
A classe é a receita de bolo; o objeto é o bolo pronto.
Você pode fazer vários bolos iguais a partir da mesma receita (várias instâncias da mesma classe).

Inclui tudo o que você precisa para desenvolver:

  • javac – compilador (converte .java em .class);
  • java – launcher (roda o bytecode em uma JVM);
  • jar – empacotador;
  • javadoc – gerador de documentação;
  • bibliotecas padrão (coleções, I/O, rede, etc.).

Fluxo típico:

SeuCódigo.java --(javac)--> SeuCódigo.class --(java)--> JVM executa

A JVM é a “máquina virtual” que interpreta/compila o bytecode para o sistema operacional/hardware real.

Responsabilidades:

  • carregar classes;
  • verificar bytecode (segurança);
  • aplicar otimizações em tempo de execução (JIT – Just-In-Time);
  • gerenciar memória (heap, stack, garbage collection).

Você pode ter várias implementações de JVM (HotSpot, GraalVM, OpenJ9), todas seguindo uma mesma especificação.

De forma simplificada:

  • Stack:

    • variáveis locais, parâmetros de métodos, controle de chamadas;
    • vida curta (seguem o fluxo de chamadas de função).
  • Heap:

    • objetos criados com new;
    • vida potencialmente longa (enquanto forem referenciados).
Object objeto = new Object(); // 'objeto' na stack; instância em heap
objeto = null; // referência perdida; objeto pode ser coletado

Linguagens como C exigem que o programador libere a memória manualmente:

int *ptr = (int*) malloc(sizeof(int));
/* ... usa ptr ... */
free(ptr); // se esquecer, há vazamento de memória

Em Java, a JVM identifica objetos que não são mais alcançáveis por nenhuma referência ativa e marca-os para remoção.

  • Você não controla diretamente quando o GC roda.
  • Seu papel é não criar referências desnecessárias e evitar manter objetos vivos sem necessidade (por exemplo, guardando tudo em uma lista global).
  • ; encerra instruções;
  • {} definem blocos (classes, métodos, if, loops);
  • // e /* ... */ são comentários;
  • convenção de nomes:
    • MinhaClasse (PascalCase);
    • minhaVariavel, meuMetodo() (camelCase);
    • MINHA_CONSTANTE (caixa alta com _).
public int numero;
public String nome;
protected boolean estaChovendo;
private String[] chamada;

Os modificadores de acesso controlam visibilidade:

  • public: acessível de qualquer lugar;
  • protected: acessível dentro do pacote e por subclasses;
  • private: acessível apenas na própria classe.

Um método é um “bloco reutilizável de código” associado a uma classe:

public void imprimir() {
System.out.println("Imprimindo algo...");
}
public String retornaNome(String nome) {
return nome;
}
public int soma(int a, int b) {
return a + b;
}

Assinatura básica:

[modificador] [tipoRetorno] nome(parametros) { corpo }

O tipo boolean só assume dois valores: true ou false.

boolean estaChovendo = true;
boolean probabilidadeAlta = true;
boolean vaiChover = estaChovendo || probabilidadeAlta;

Operadores lógicos comuns:

  • && – E (verdadeiro se ambas condições forem verdadeiras);
  • || – OU (verdadeiro se pelo menos uma for verdadeira);
  • ! – negação (inverte o valor).

Exemplo de uso:

int idade = 20;
boolean temCarteira = true;
if (idade >= 18 && temCarteira) {
System.out.println("Pode dirigir.");
}

String representa texto e é imutável (cada operação que “muda” a string cria um novo objeto).

String nome = "Wesley";
String sobrenome = "Safadão";
String artista = nome + " " + sobrenome;

Considerando:

String texto = "Backend Java";
  • texto.length() – tamanho;
  • texto.equals("Backend Java") – compara conteúdo (não use == para strings);
  • texto.toLowerCase(), texto.toUpperCase();
  • texto.replace("a", "e");
  • texto.split(" ") – quebra em um array de palavras.

Analogia:
Pense em String como um PDF: você pode gerar outro PDF alterado, mas o arquivo original continua igual.

Arrays são coleções de tamanho fixo, com elementos do mesmo tipo.

int[] numerosMegaSena = new int[6];
// posições de 0 a 5
numerosMegaSena[0] = 4;
numerosMegaSena[5] = 42;
// numerosMegaSena[6]; // lança IndexOutOfBoundsException

Outra forma, com valores pré-definidos:

int[] numeros = {4, 11, 19, 25, 33, 42};
Object[] dados = {"matrix", 10, true};
String[] cidades = {"Içara", "Maracajá", "Araranguá"};
for (int i = 0; i < cidades.length; i++) {
System.out.println(cidades[i]);
}
for (String cidade : cidades) {
System.out.println(cidade);
}
import java.util.Arrays;
int[] lista = {3, 1, 2};
System.out.println(lista.length);
System.out.println(Arrays.toString(lista)); // [3, 1, 2]
Arrays.sort(lista); // [1, 2, 3]
Arrays.fill(lista, 0); // [0, 0, 0]
if (condicao) {
// bloco executado se condição for verdadeira
} else {
// bloco executado se condição for falsa
}

Exemplo:

int nota = 7;
if (nota >= 6) {
System.out.println("Aprovado");
} else {
System.out.println("Reprovado");
}

Uma forma compacta de escolher entre dois valores:

int idade = 17;
String status = (idade >= 18) ? "maior de idade" : "menor de idade";

Útil para expressões curtas – evite aninhar ternários demais para não perder legibilidade.

Quando você sabe mais ou menos quantas iterações quer:

int quantidade = 5;
for (int i = 0; i < quantidade; i++) {
System.out.println("Iteração " + i);
}

Ideal para coleções e arrays:

String[] linguagens = {"Java", "Python", "Go"};
for (String l : linguagens) {
System.out.println(l);
}

Quando o número de repetições depende de uma condição que muda dentro do loop:

int contador = 0;
while (contador < 3) {
System.out.println("Contador: " + contador);
contador++;
}

Servem para organizar o código e evitar conflitos de nomes:

package com.organizacao.modelos;
public class Pessoa {
// ...
}

Permitem usar classes de outros pacotes sem escrever o caminho completo:

import com.organizacao.modelos.Pessoa;
import com.organizacao.servicos.*;
public class App {
public static void main(String[] args) {
Pessoa pessoa = new Pessoa();
}
}

Sem import, você precisa do nome completo:

com.organizacao.modelos.Pessoa p =
new com.organizacao.modelos.Pessoa();
System.out.println("Hello World!");
import java.util.Scanner;
public class ExemploEntrada {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Digite seu nome: ");
String nome = scanner.nextLine();
System.out.println("Olá, " + nome + "!");
scanner.close();
}
}

O nextInt() não consome a quebra de linha ao final:

int numero = scanner.nextInt();
scanner.nextLine(); // consome o '\n' restante
String nome = scanner.nextLine();

Também é importante ter atenção ao Locale para decimais (. vs ,).

Em Java, variáveis de tipos não primitivos guardam referências a objetos na heap.

Pessoa pessoa1 = new Pessoa();
Pessoa pessoa2 = pessoa1; // mesma referência
pessoa2.nome = "Harry Potter";
System.out.println(pessoa1.nome); // "Harry Potter"

Para objetos, o método recebe uma cópia da referência, mas o objeto é o mesmo:

public static void alterarNome(Pessoa p) {
p.nome = "Novo Nome";
}
Pessoa pessoa = new Pessoa();
pessoa.nome = "Antigo Nome";
alterarNome(pessoa);
// pessoa.nome agora é "Novo Nome"

Para primitivos, o método recebe uma cópia do valor:

public static void alterar(int valor) {
valor = 10;
}
int numero = 5;
alterar(numero);
// numero continua sendo 5
  • == compara referências (mesmo objeto na memória?).
  • equals() compara conteúdo (se for sobrescrito adequadamente).
String a = new String("Java");
String b = new String("Java");
System.out.println(a == b); // false (referências diferentes)
System.out.println(a.equals(b)); // true (conteúdo igual)

Cada tipo primitivo possui uma classe “wrapper” correspondente:

  • intInteger
  • doubleDouble
  • booleanBoolean
  • etc.

Eles permitem usar valores primitivos onde são exigidos objetos (ex.: coleções):

Integer numero = Integer.valueOf(10);
Boolean ativo = Boolean.TRUE;
String texto = Integer.toString(numero);
int valor = Integer.parseInt("42");

Java faz autoboxing/unboxing automaticamente em muitos casos:

List<Integer> lista = new ArrayList<>();
lista.add(10); // autoboxing: int -> Integer
int x = lista.get(0); // unboxing: Integer -> int

Palavras como class, public, static, if, else, for, while, return têm significado especial e não podem ser usadas como nomes de variáveis ou métodos.

Escopo define onde uma variável é visível:

  • Classe: campos definidos diretamente na classe;
  • Método: variáveis locais do método;
  • Bloco: variáveis declaradas dentro de {}.
public class ContaBancaria {
double saldo = 1000.0; // escopo de classe
public void sacar(double valor) {
double taxa = 2.5; // escopo de método
if (valor > 0 && valor + taxa <= saldo) {
double saldoAposSaque = saldo - valor - taxa; // escopo de bloco
System.out.println("Saldo: " + saldoAposSaque);
}
}
}

Quando o compilador converte automaticamente de um tipo menor para um maior:

int numero = 10;
long maior = numero; // conversão implícita

Necessário quando há risco de perda de informação:

double valor = 10.7;
int inteiro = (int) valor; // 10

Também existe casting entre tipos de referência (upcast/downcast) quando há relação de herança, o que será aprofundado em tópicos de orientação a objetos.

Neste capítulo, você viu os fundamentos para começar a programar em Java:

  • o que é a plataforma Java (JDK, JVM, linguagem);
  • tipos de dados, classes, objetos e gerenciamento de memória;
  • sintaxe básica, estruturas de decisão e repetição;
  • como trabalhar com strings, arrays e entrada/saída;
  • conceitos importantes de referência, escopo e casting.

Com esses blocos, já é possível construir pequenos programas de linha de comando e começar a praticar a lógica de programação em um ambiente muito próximo ao que se usa em projetos reais.