Maquina Virtual do Java (JVM) é uma máquina virtual que permite que computadores executem programas Java, alem de permitir desenvolver os aplicativos em Java.
package = agrupamento lógico de classes e interfaces relacionadas. modulo = agrupamento lógico de pacotes relacionados. runtime = agrupamento físico (arquivos). aplicação = conjunto de módulos relacionados.
sysout = atalho para `System.out.println();``
Exemplo de código Java:
System.out.print("Olá mundo!");
Codiigo para imprimir algo no console.
System.out.println("Olá mundo!");
Codiigo para imprimir algo no console e o "ln" faz com que tenha quebra de linha.
Exemplo de formatação de números:
double x = 10.35784;
System.out.println(x);
System.out.printf("%.2f%n", x);
System.out.printf("%.4f%n", x);
Locales.setDefault(Locale.US);
System.out.printf("%.4f%n", x);Caso eu tenha duas variaveis de numeros inteiros e eu queira fazer uma divisão entre elas, o resultado será um número inteiro, mesmo que o resultado da divisão seja um número decimal. Para resolver isso, eu posso fazer um "casting" de uma das variaveis para double, assim o resultado será um número decimal.
int a, b;
a = 5;
b = 2;
double resultado = (double) a / b;
System.out.println(resultado);O casting seria como se fosse o parse no C#.
Para ler dados do usuário em Java, utilizamos a classe Scanner.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String x;
x = sc.next();
System.out.println("Você digitou: " + x);
sc.close();
}
}Para ler do tipo int:
int x;
x = sc.nextInt();Para ler do tipo double:
double x;
x = sc.nextDouble();Para ler dados da linha inteira :
string x;
x = sc.nextLine();Math.sqrt = raiz quadrada Math.pow = potência Math.abs = valor absoluto
Debug é o processo de encontrar e corrigir erros no código. O processo de debug depende da IDE que você está utilizando. Vou explicar como fazer debug no IntelliJ e no Eclipse.
- Coloque um breakpoint clicando na margem esquerda da linha onde você quer parar a execução
- Clique no icone de debug (um bug) ou pressione Shift + F9
- A execução do programa irá parar no breakpoint
- Se você quiser indo avançando ao longo do codigo linha por linha, pressione F10 (Step Over)
- Coloque um toggle breakpoint clicando na margem esquerda da linha onde você quer parar a execução
- Clique no arquivo -> Debug As -> Java Application ou pressione F11
- A execução do programa irá parar no breakpoint
- Se você quiser indo avançando ao longo do codigo linha por linha, pressione F6(Step Over)
Bloco de comandos executa pelo menos uma vez, pois a condição é verificada no final.
do {
//bloco de comandos
} while(condição);pacotes atributos métodos variáveis e parâmetros
classes
Operadores bit a bit operam diretamente nos bits dos números inteiros.

- Formatar: toLowerCase(), toUpperCase(), trim()
- Recortar: substring(inicio), substring(inicio, fim)
- Substituir: Replace(char, char), Replace(string, string)
- Buscar: IndexOf, LastIndexOf
- str.Split(" ")
String s = "potato apple lemon";
String[] vect = s.split(" ");
String word1 = vect[0];
String word2 = vect[1];
String word3 = vect[2]As funções do Java são criadas embaixo da public static void main(String[] args) {}
Na pasta entities é criada a classe com os atributos e métodos. Na pasta application é onde o programa principal é executado.
package entities;
public class Triangle {
public double a;
public double b;
public double c;
}package application;
import entities.Triangle;
Triangle x, y;
x = new Triangle();
y = new Triangle();Constantes são variáveis que não podem ter seu valor alterado durante a execução do programa. Em Java, as constantes são declaradas utilizando a palavra-chave final e geralmente são escritas em letras maiúsculas com palavras separadas por underscores _.
public static final double PI = 3.14159;Métodos estáticos pertencem à classe em si, e não a uma instância específica da classe. Eles podem ser chamados sem a necessidade de criar um objeto da classe.
Ou seja, são funções que estão no mesmo arquivo da classe principal e podem ser chamadas diretamente.
public class Program {
public static final double PI= 3.14159;
public static void main (String[] args) {
double c = circumference(radius);
double v = volume(radius);
}
public static double circumference(double radius) {
return 2.0 * PI * radius;
}
public static double volume(double radius) {
return 4.0 * PI * radius * radius * radius / 3.0;
}
}Tambem é possível chamar métodos estáticos de outras classes, mas diferente de metodos de instância, não é necessário criar um objeto da classe para chamar o método estático.
double result = Calculator.volume(25.0);Construtores são métodos especiais usados para inicializar objetos de uma classe. Eles têm o mesmo nome da classe.
public class Product {
public String name;
public double price;
public int quantity;
public Product(String name, double price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
}A sobrecarga de construtores permite que uma classe tenha múltiplos construtores com diferentes listas de parâmetros. Isso possibilita a criação de objetos de maneiras diferentes, dependendo dos dados disponíveis no momento da instânciação. Tambem podemos ter um construtor padrão (sem parâmetros).
Encapsulamento é a restrição do acesso direto aos dados de um objeto, tendo que utilizar métodos específicos para acessar e modificar esses dados (getters e setters).
Primeiro temos que definir os atributos como private e depois criar os métodos get e set.
public class Product {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}- Clique com o botão direito dentro da classe onde você deseja gerar o construtor, getters e setters.
- Selecione "Generate..." ou pressione
Alt + Insert. - Escolha "Constructor" para gerar o construtor, "Getter" para gerar os getters, ou "Setter" para gerar os setters.
- Selecione os atributos que você deseja incluir e clique em "OK".
private = acessível apenas dentro da própria classe. (nada) = acessível dentro do mesmo pacote. protected = acessível dentro do mesmo pacote e em subclasses. public = acessível de qualquer lugar.
Variaveis do tipo classe armazenam referências para objetos na memória, e não os próprios objetos.

Homogênea (dados do mesmo tipo) Ordenada (elementos acessados por meio de posições) Alocada de uma vez só, em um bloco contíguo de memória
Acesso imediato aos elementos pela sua posição
Tamanho fixo Dificuldade para se realizar inserções e deleções.
tipo[] nomeDoVetor = new tipo[tamanho];double[] nomeDoVetor = new double[10];No exemplo abaixo, o usuário informa o tamanho do vetor e depois insere os valores, utilizando de um for para percorrer o vetor e inserir os valores.
int n =sc.nextInt();
double[] vect = new double[n];
for (int i=0; i<n; i++){
vect[i] = sc.nextDouble();
}public class Product {
private String name;
private double price;
} Locale.setDefault(Locale.US);
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
Product[] vect = new Product[n];
for (int i = 0; i < vect.length; i++){
sc.nextLine();
String name = sc.nextLine();
double price = sc.nextDouble();
vect[i] = new Product(price, name);
}Boxing é o processo de converter um tipo primitivo em um objeto do tipo referência correspondente. Unboxing é o processo inverso, onde um objeto do tipo referência é convertido de volta para um tipo primitivo.
int x = 10; // tipo primitivo
Object obj = x; // Boxing
int y = (int) obj; // UnboxingWrappers são classes que encapsulam tipos primitivos em objetos. Cada tipo primitivo em Java tem uma classe wrapper correspondente. Assim, não é preciso fazer boxing e unboxing manualmente, pois o Java faz isso automaticamente. A vantagem de usar wrappers é que eles permitem que sejam nulos.
| Tipo Primitivo | Classe Wrapper |
|---|---|
| int | Integer |
| double | Double |
| boolean | Boolean |
| char | Character |
| long | Long |
| float | Float |
| byte | Byte |
| short | Short |
int x = 10; // tipo primitivo
Interger obj = x; // Boxing
int y = obj; // UnboxingO laço for each é uma forma simplificada de percorrer elementos em arrays ou coleções.
for (tipo item : coleção) {
// bloco de comandos
}String[] vect = new String[] {"Ana", "Bruno", "Carlos"};
for (String nome : vect) {
System.out.println(nome);
}• Homogênea (dados do mesmo tipo) • Ordenada (elementos acessados por meio de posições) • Inicia vazia, e seus elementos são alocados sob demanda • Cada elemento ocupa um "nó" (ou nodo) da lista
• Tipo (interface): List • Classes que implementam: ArrayList, LinkedList, etc.
• Tamanho variável • Facilidade para se realizar inserções e deleções
• Acesso sequencial aos elementos*.
List<tipo> nomeDaLista = new ArrayList<>();nomeDaLista.add(elemento); // Adiciona um elemento no final da lista
nomeDaLista.add(indice, elemento); // Adiciona um elemento em uma posição específica
nomeDaLista.size(); // Retorna o tamanho da lista
nomeDaLista.remove(indice); // Remove o elemento de uma posição específica
nomeDaLista.remove(elemento); // Remove a primeira ocorrência do elemento
nomeDaLista.removeIf(x -> condição); // Remove todos os elementos que satisfazem a condição
nomeDaLista.indexOf(elemento); // Retorna o índice da primeira ocorrência do elemento
List<tipo> subLista = nomeDaLista.stream().filter(x -> condição).collect(Collectors.toList()); // Cria uma sublista com elementos que satisfazem a condiçãoEm programação, "matriz" é o nome dado a arranjos bidimensionais
• Homogênea (dados do mesmo tipo) • Ordenada (elementos acessados por meio de posições) • Alocada de uma vez só, em um bloco contíguo de memória
• Acesso imediato aos elementos pela sua posição
• Tamanho fixo • Dificuldade para se realizar inserções e deleções
// Bidimensional
tipo[][] nomeDaMatriz = new tipo[linhas][colunas];
// Tridimensional
tipo[][][] nomeDaMatriz = new tipo[linhas][colunas];ano-mês-dia-[hora] sem fuso horário [hora] opcional
ano-mês-dia-hora com fuso horário
tempo decorrido entre duas data-hora.
LocalDate d01 = LocalDate.now(); // Data atual
LocalDateTime d02 = LocalDateTime.now(); // Data e hora atual
Instant d03 = Instant.now(); // Data e hora atual em UTC
LocalDate d04 = LocalDate.parse("2024-06-25"); // Data a partir de uma string
LocalDateTime d05 = LocalDateTime.parse("2024-06-25T15:30:45"); // Data e hora a partir de uma string
Instant d06 = Instant.parse("2024-06-25T15:30:45Z"); // Data e hora em UTC a partir de uma string
Instant d06 = Instant.parse("2024-06-25T15:30:45-03:00"); // Data e hora em UTC a partir de uma string no fuso de São PauloDocumentação do DateTimeFormatter
DateTimeFormatter fmt1 = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate d04 = LocalDate.parse("25/06/2024", fmt1); // Data a partir de uma string num formato específicoDateTimeFormatter fmt2 = DateTimeFormatter.ofPattern("dd/MM/yyyy");
DateTimeFormatter fmt3 = DateTimeFormatter.ofPattern("dd/MM/yyyy").withLocale(ZoneId.systemDefault()); // Formatação de data para string com localidade
DateTimeFormatter fmt4 = DateTimeFormatter.ISO_DATE;
// Formatação de data e hora para string
String s1 = d04.format(fmt2); // Formatação de data para string
System.out.println(d04.format(fmt3)); // Formatação de data para string
String s1 = d04.format(fmt4); // Formatação de data para string
// ou
String s2 = fmt2.format(d04); // Formatação de data para string
LocalDate r2 = LocalDate.ofInstant(d06, ZoneId.of("Portugal")); // Converter Instant para LocalDate no fuso local
d04.getDayOfMonth(); // Obter o dia do mês
d04.getMonthValue(); // Obter o mês
d04.getYear(); // Obter o ano
d04.getHour(); // Obter a hora
d04.getMinute(); // Obter os minutosd04.plusDays(7); // Adicionar 7 dias
d04.plusMonths(6); // Adicionar 6 meses
d04.minusDays(7); // Subtrair 7 dias
d04.minusMonths(6); // Subtrair 6 meses
d04.until(d07, ChronoUnit.DAYS); // Diferença em dias entre d04 e d07
Duration.between(d06, d08).toDays(); // Diferença em dias entre
Duration.between(d06, d08).toHours(); // Diferença em horas entre
Duration.between(d06, d08).toMinutes(); // Diferença em minutos entre
Duration t1 = Duration.between(d06.atTime(0, 0), d08.atTime(0, 0)); // Diferença entre duas datas
Duration t1 = Duration.between(d06.atStartOfDay(), d08.atStartOfDay()); // Diferença entre duas datas
sout.println("Diferença em dias: " + t1.toDays());•É um tipo especial que serve para especificar de forma literal um conjunto de constantes relacionadas •Palavra chave em Java: enum •Vantagem: melhor semântica, código mais legível e auxiliado pelo compilador
Ou seja, é um tipo que cria um tipo que você.
public enum OrderStatus {
PENDING_PAYMENT,
PROCESSING,
SHIPPED,
DELIVERED
}E essas enumerações ficam em um pacote separado, geralmente chamado de entities.enums.
Os exercicios 15 e 16 do curso Java Completo da Udemy utilizam enumerações.
StringBuilder é uma classe em Java que permite criar e manipular strings de forma mais eficiente do que a classe String tradicional, especialmente quando se trata de operações que envolvem muitas concatenações ou modificações de strings.
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(title + "\n");
return sb.toString();
}O StringBuilder tem que ser declarado dentro do método que vai ser utilizado, e no final do método é necessário retornar o StringBuilder convertido para String com o método toString().
Herança permite que uma classe (subclasse ou classe derivada) herde atributos e métodos de outra classe (superclasse ou classe base).
A palavra-chave extends é usada para indicar que uma classe herda de outra.
A classe base não tem nenhuma mudança, já na classe derivada, precisamos usar a palavra-chave extends seguida do nome da classe base e tambem o construtor da classe derivada deve chamar o construtor da classe base usando a palavra-chave super.
public class BusinessAccount extends Account {
private Double loanLimit;
public BusinessAccount() {
super();
}
public BusinessAccount(Integer number, String holder, Double balance, Double loanLimit) {
super(number, holder, balance);
this.loanLimit = loanLimit;
}
}Upcasting é o processo de converter uma referência de um objeto de uma subclasse para uma superclasse. Downcasting é o processo inverso, onde uma referência de um objeto de uma superclasse é convertida para uma subclasse.
Upcasting:
Account acc1 = bacc;
Account acc2 = new BusinessAccount(1003, "Bob", 0.0, 200.0);
Account acc3 = new SavingsAccount(1004, "Ana", 0.0, 0.01);Downcasting:
BusinessAccount acc4 = (BusinessAccount) acc2;
// Errado, mas só aparece o erro na hora de compilar
// BusinessAccount acc5 = (BusinessAccount) acc3;
if (acc3 instanceof BusinessAccount) {
BusinessAccount acc5 = (BusinessAccount) acc3;
acc5.loan(200.0);
}O operador instanceof é usado para verificar se um objeto é uma instância de uma classe específica antes de fazer o downcasting, evitando erros na execução.
É a implementação de um método em uma subclasse que já foi definido na superclasse.
É recomendado usar a anotação @Override acima do método sobrescrito para indicar que você está intencionalmente sobrescrevendo um método da superclasse.
@Override
public void withdraw(Double amount) {
balance -= amount;
}A palavra-chave final em Java impede que uma classe seja herdada ou que um método seja sobrescrito.
O final pode ser usado em classes e métodos, e ele tende a deixar o código mais seguro e otimizado.
public final class MathUtils {
// Esta classe não pode ser estendida
} public class BaseClass {
public final void display() {
System.out.println("Este método não pode ser sobrescrito.");
}
}Polimorfismo nada mais é do que um método ter o mesmo nome, mas comportamentos diferentes dependendo do objeto que o chama. Então quando um método é sobrescrito em uma subclasse, o comportamento do método pode variar dependendo do tipo do objeto que o chama.
São classes que não podem ser usadas diretamente, elas só servem como base para outras classes. Pode também servir para tipos de listas genéricas, onde a lista pode armazenar objetos de diferentes subclasses que herdam da classe abstrata.
public abstract class Animal {}Igual as classes abstratas, os métodos abstratos não possuem implementação na classe base, e devem ser implementados nas subclasses. Serve para metodos que são muito específicos de cada subclasse ou algo muito genérico que todas as subclasses devem implementar. E se o método for abstrato, a classe que o contém também deve ser abstrata. O método abstrato se torna obrigatório para todas as subclasses implementarem, tendo que ser sobrescrito.
public abstract void makeSound();A estrutura try-catch é usada para tratar exceções em Java. O código que pode gerar uma exceção é colocado dentro do bloco try, e o tratamento da exceção é feito no bloco catch.
try {
// código que pode gerar uma exceção
} catch (TipoDaExceção e) {
// tratamento da exceção
}try{
String[] vect = sc.nextLine().split(" ");
int position = sc.nextInt();
System.out.println(vect[position]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Invalid position! ");
}
catch (InputMismatchException e) {
System.out.println("Input error! ");
}model
entities
enums
exceptions
servicesColocar o throws Exception na assinatura do método indica que o método pode lançar uma exceção, e que o chamador do método deve estar preparado para lidar com essa exceção. Ou seja, o método não trata a exceção internamente, mas sim propaga a responsabilidade de tratamento para o código que o chamou.
Quando você está desenvolvendo um método e quer indicar que uma condição específica é um erro, você pode usar a instrução throw new para lançar uma exceção personalizada. Isso permite que você crie suas próprias exceções para situações específicas no seu código.
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}Assim você pode tratar essa exceção no método que chamou esse método, utilizando o try-catch.
Construtores também podem lançar exceções utilizando o throw new, caso alguma condição não seja atendida durante a criação do objeto.
public Product(String name, double price) {
if (price < 0) {
throw new IllegalArgumentException("Price cannot be negative");
}
this.name = name;
this.price = price;
}Você pode criar suas próprias classes de exceção personalizadas em Java, estendendo a classe Exception ou RuntimeException. Isso é útil quando você deseja representar erros específicos do seu domínio de aplicação.
public class DomainException extends RuntimeException {
private static final long serialVersionUID = 1L;
public DomainException(String msg) {
super(msg);
}
}Depois você pode lançar essa exceção personalizada em qualquer lugar do seu código usando a instrução throw new DomainException("mensagem de erro").
Para ler arquivos você pode usar o Scanner. O arquivo deve ser importado com o caminho completo em uma variavel do tipo File.
File file = new File("C:\\temp\\files\\aa.txt");
Scanner sc = null;
try {
sc = new Scanner(file);
while (sc.hasNextLine()) {
System.out.println(sc.nextLine());
}
} catch (IOException e) {
System.out.println("Erro ao ler o arquivo: " + e.getMessage());
} finally {
if (sc != null) {
sc.close();
}
}O FileReader é usado para ler caracteres de um arquivo, enquanto o BufferedReader é usado para ler texto de forma eficiente, armazenando os caracteres em um buffer.
String path = "C:\\temp\\files\\aa.txt";
FileReader fr = null;
BufferedReader br = null;
try{
fr = new FileReader(path);
br = new BufferedReader(fr);
// br = new BufferedReader(new FileReader(path));
String line = br.readLine();
while(line != null){
System.out.println(line);
line = br.readLine();
}
}
catch(IOException e){
System.out.println(e.getMessage());
}
finally{
try {
if(br != null){
br.close();
}
if(fr != null){
fr.close();
}
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}O FileWriter é usado para escrever caracteres em um arquivo, enquanto o BufferedWriter é usado para escrever texto de forma eficiente, armazenando os caracteres em um buffer antes de escrevê-los no arquivo.
String[] lines = new String[]{"Good morning", "Good afternoon", "Good evening"};
String path = "C:\\temp\\files\\out.txt";
try (BufferedWriter bw = new BufferedWriter(new FileWriter(path))) {
for (String line : lines) {
bw.write(line); // Escreve a linha no arquivo
bw.newLine(); // Adiciona uma nova linha / quebra de linha
}
} catch (IOException e) {
e.printStackTrace();
}BufferedWriter bw = new BufferedWriter(new FileWriter(path)) Essa linha cria um novo arquivo no caminho especificado por path. Se o arquivo já existir, ele será sobrescrito. Agora se adicionar true como segundo parâmetro em FileWriter, o conteúdo será adicionado ao final do arquivo existente. Dessa forma:
BufferedWriter bw = new BufferedWriter(new FileWriter(path, true)) System.out.println("Enter a folder path: ");
String strPath = sc.nextLine();
File path = new File(strPath);
File[] folders = path.listFiles(File::isDirectory);
System.out.println("Folders: ");
for (File folder : folders) {
System.out.println(folder);
} System.out.println("Enter a folder path: ");
String strPath = sc.nextLine();
File[] files = path.listFiles(File::isFile);
System.out.println("Files: ");
for (File file : files) {
System.out.println(file);
} boolean sucess = new File(strPath + "\\subdir").mkdir();
System.out.println("Folder created: " + sucess); System.out.println("GetName: " + path.getName()); // Nome do arquivo ou pasta
System.out.println("GetParent: " + path.getParent()); // Caminho do diretório pai
System.out.println("GetPath: " + path.getPath()); // Caminho completo do arquivo ou pastaExercício de Interfaces é o 024.
Interfaces são contratos que definem um conjunto de métodos que uma classe deve implementar. Elas permitem a criação de código mais flexível e reutilizável.
Voce faz um codigo onde consoma a interface e depois cria classes que implementam essa interface. Assim você pode trocar as implementações sem alterar o código que consome a interface, por exemplo, em um sistema de pagamento onde você pode ter diferentes métodos de pagamento (cartão de crédito, PayPal, boleto bancário), você pode definir uma interface PaymentMethod com um método processPayment(). Cada método de pagamento pode ser implementado em uma classe separada que implementa a interface PaymentMethod. Ou uma interface que tenha o banco de dados e depois você pode colocar qualquer banco de dados que implemente essa interface.
public interface TaxService {
double tax(double amount);
}public class BrazilTaxService implements TaxService {
public double tax(double amount) {
// Regras de negócio
}
}public class RentalService {
private Double pricePerHour;
private Double pricePerDay;
private TaxService taxService; // Onde vc iria usar a class diretamente, vc agora usa a interface
// Construtor e outros métodos
}Esse problema acontece quando há uma ambiguidade na herança múltipla, onde uma classe herda de duas classes que possuem um método com o mesmo nome. Por isso a herança múltipla de classes não é permitida em Java.
Uma classe pode implementar múltiplas interfaces, o que permite que uma classe reutilize código de várias fontes sem enfrentar o problema do diamante.
public interface A {
void method();
}
public interface B extends A {
void method();
}
public class C implements B, C {
public void method() {
}
}Uma classe que implementa a interface Comparable deve sobrescrever o método compareTo, que é usado para comparar.O método compareTo deve retornar um valor negativo se o objeto atual for menor que o outro objeto, um valor positivo se for maior, e zero se forem iguais.
public class Product implements Comparable<Product> {
@Override
public int compareTo(Product other) {
return this.price.compareTo(other.getPrice());
}
}public interface Comparable<T> {
int compareTo(T o);
}Collections.sort(nomeDaLista); Essa função ordena a lista em ordem crescente, desde que o tipo da lista implemente a interface Comparable.
São metodos que ficam dentro da interface, mas que possuem uma implementação padrão. Assim, o metodo não ira ter que ser sobrescrito nas classes que implementam a interface.
public interface MyInterface {
default void myDefaultMethod() {
System.out.println("This is a default method.");
}
}Serve para criar classes que funcionam com diferentes tipos de dados, na classe você define um tipo genérico usando <T>, e na instanciação da classe, você especifica o tipo que deseja usar.
public class Services<T> {
private List<T> list = new ArrayList<>();
public void setItem(T item) { }
public T getItem() { }
}Box<String> stringBox = new Box<>();Tipos coringa (wildcard types) em Java são usados em generics. Eles são representados pelo símbolo ? e podem ser usados para indicar que um tipo genérico pode ser qualquer tipo.
Mas tem algumas restrições, por exemplo, você não pode adicionar elementos.
Caso você queira alguma lista que as informações sejam de um tipo específico ou subtipos, você pode usar o coringa delimitado com extends.
List<? extends Shape> list = new ArrayList<>();No exemplo acima, a lista pode conter objetos do tipo Shape ou qualquer subtipo de Shape, como Circle ou Rectangle.
Covariância permite que você use um tipo mais específico do que o tipo genérico declarado. Contravariância permite que você use um tipo mais genérico do que o tipo genérico declarado.
Pode fazer o get, mas não o put.
List<Integer> intList = new ArrayList<Integer>();
intList.add(10);
intList.add(5);
List<? extends Number> list = intList;
Number x = list.get(0);
list.add(20); // erro de compilacaoPode fazer o put, mas não o get.
List<Object> myObjs = new ArrayList<Object>();
myObjs.add("Maria");
myObjs.add("Alex");
List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);
Number x = myNums.get(0); // erro de compilacaoAbaixo um exemplo de método que copia elementos de uma lista para outra, utilizando tipos coringa delimitados para permitir a cópia de listas de diferentes tipos numéricos. Assim, podemos montar uma lista com dois tipos diferentes de números (Integer e Double).
Tambem temos um método para imprimir a lista, utilizando o coringa simples ?, que permite imprimir listas de qualquer tipo.
public static void main(String[] args) {
List<Integer> myInts = Arrays.asList(1, 2, 3, 4);
List<Double> myDoubles = Arrays.asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();
copy(myInts, myObjs);
printList(myObjs);
copy(myDoubles, myObjs);
printList(myObjs);
}
public static void copy(List<? extends Number> source, List<? super Number> destiny) {
for (Number number : source) {
destiny.add(number);
}
}
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.print(obj + " ");
}
System.out.println();
}Os métodos equals e hashCode são usados para comparar objetos em Java. O método equals verifica se dois objetos são iguais, enquanto o método hashCode retorna um valor inteiro que representa o objeto.
O hashCode é mais rapido do que o equals, por isso é usado o hashCode para fazer uma comparação inicial, e se os hashCodes forem iguais, ai sim é feito o equals para verificar se os objetos são realmente iguais.
Para criar esses métodos automaticamente no IntelliJ, clique com o botão direito dentro da classe, selecione "Generate" e escolha "equals() and hashCode()". Depois selecione os atributos que você quer considerar na comparação.
O Set é uma coleção que não permite elementos duplicados. Ele é usado quando você quer garantir que cada elemento seja único dentro da coleção.
O HashSet é uma implementação da interface Set que utiliza uma tabela hash para armazenar os elementos. Ele é o mais rápido, mas não mantém a ordem dos elementos.
Set<String> set = new HashSet<>();
set.add("Maria");
set.remove("Maria");
set.removeIf(x -> x.length() > 3);Ele é o mais lento, mas mantém os elementos ordenados. Ele é ordenado pelo método compareTo da classe dos objetos armazenados, ou por um Comparator fornecido no momento da criação do TreeSet.
Set<String> set = new TreeSet<>();
set.add("Maria");
set.add("Alex");Mais lento que o HashSet, mas mais rápido que o TreeSet. Mantém a ordem de inserção dos elementos.
Set<String> set = new LinkedHashSet<>();
set.add("Maria");
set.add("Alex");add("item") // Adiciona um elemento no conjunto
remove("item") // Remove um elemento do conjunto
contains("item") // Verifica se o conjunto contém o elemento
clear() // Remove todos os elementos do conjunto
size() // Retorna o número de elementos no conjunto
removeIf(predicate) // Remove elementos que satisfazem o predicado
addAll(other) // União: adiciona no conjunto os elementos do outro conjunto, sem repetição
retainAll(other) // Interseção: remove do conjunto os elementos não contidos em other
removeAll(other) // Diferença: remove do conjunto os elementos contidos em otherO método contains no Set usa o hashCode e o equals para verificar se um elemento está presente no conjunto. Caso seja uma classe personalizada, é importante sobrescrever esses métodos para garantir o funcionamento correto do contains.
O Map é uma coleção que armazena pares de chave-valor. Ele é usado quando você quer associar um valor a uma chave específica. Ele não permite chaves duplicadas, mas permite valores duplicados.
Uso comum: cookies, local storage, qualquer modelo chave-valor
Map<Integer, String> map = new HashMap<>();
map.put(1, "Maria");put(chave, valor) // Adiciona um par chave-valor ao mapa
get(chave) // Retorna o valor associado à chave
remove(chave) // Remove o par chave-valor do mapa
containsKey(chave) // Verifica se o mapa contém a chave
containsValue(valor) // Verifica se o mapa contém o valor
clear() // Remove todos os pares chave-valor do mapa
size() // Retorna o número de pares chave-valor no mapa
keySet() // Retorna um conjunto com todas as chaves do mapa
values() // Retorna uma coleção com todos os valores do mapa
// forEach
map.forEach((key, value) -> {
System.out.println("Key: " + key + ", Value: " + value);
});
for (String key : map.keySet()) {
System.out.println(key + ", Value: " + map.get(key));
}Ao inves de colocar Collectons.sort(nomeDaLista);, voce pode criar uma classe que implemente a interface Comparator, e passar essa classe como parametro para o list.sort(). Assim a ordenação sera feita de acordo com o que voce definiu na classe Comparator.
public class MyComparator implements Comparator<Product> {
@Override
public int compare(Product p1, Product p2) {
return p1.getName().toUpperCase().compareTo(p2.getName().toUpperCase());
}
} list.sort(new MyComparator());Podemos também criar a classe Comparator como uma classe anônima, sem a necessidade de criar um arquivo separado para ela.
Comparator<Product> comp = new Comparator<Product>() {
@Override
public int compare(Product p1, Product p2) {
return p1.getName().toUpperCase().compareTo(p2.getName().toUpperCase());
}
}; list.sort(comp);Podemos também usar uma expressão lambda para criar o Comparator de forma mais concisa.
Comparator<Product> comp = (p1, p2) -> p1.getName().toUpperCase().compareTo(p2.getName().toUpperCase()); list.sort(comp); list.sort((p1, p2) -> p1.getName().toUpperCase().compareTo(p2.getName().toUpperCase()));Expressões lambda são uma forma concisa de representar funções anônimas em Java. Elas são usadas principalmente para implementar interfaces funcionais, que são interfaces com um único método abstrato. Elas basicamente são arrow functions.
Interfaces funcionais são interfaces que possuem apenas um método abstrato. Elas são usadas como alvos para expressões lambda e referências de método em Java.
A interface Predicate representa uma função que recebe um argumento e retorna um valor booleano. Ela é usada para testar se um objeto satisfaz uma determinada condição.
public interface Predicate<T> {
boolean test(T t);
}Ela pode ser usada dentro de uma classe de serviço para filtrar uma lista de objetos com base em uma condição específica.
public class ProductService implements Predicate<Product> {
@Override
public boolean test(Product p) {
return p.getPrice() >= 100.0;
}
} list.removeIf(new ProductService());Ou pode ser um método estático dentro da classe de serviço.
public class ProductService {
public static boolean staticProductPredicate(Product p) {
return p.getPrice() >= 100.0;
}
} list.removeIf(ProductService::staticProductPredicate);Ou pode ser uma expressão lambda diretamente no removeIf.
list.removeIf(p -> p.getPrice() >= 100.0);Ele tambem pode ser feito de diferentes formas, como uma classe que implementa a interface Consumer, um método estático, ou uma expressão lambda, mas o mais comum é usar a expressão lambda diretamente no forEach.
list.forEach(p -> p.setPrice(p.getPrice() * 1.1)); list.stream().map(p -> p.getName().toUpperCase()).collect(Collectors.toList());Você pode criar métodos que recebem funções como parâmetros, usando interfaces funcionais como Predicate, Function, Consumer, etc. Isso permite que você crie métodos mais genéricos e reutilizáveis.
public double filter(List<T> list, Predicate<T> predicate) {
double sum = 0.0;
for (T item : list) {
if (predicate.test(item)) {
sum += item.getPrice();
}
}
return sum;
} double sum = ps.filteredSum(products, p -> p.getPrice() >= 100.0);Streams são sequências de elementos que suportam operações funcionais para processamento de dados. Eles permitem que você processe coleções de forma declarativa, usando operações como map, filter, reduce, etc.
- filter: filtra os elementos com base em uma condição
- map: aplica uma função a cada elemento, transformando-os
- flatMap: transforma os elementos em uma stream de outro tipo e "achata" a estrutura
- peek: permite inspecionar os elementos sem modificá-los
- distinct: remove elementos duplicados
- sorted: ordena os elementos
- skip: pula os primeiros n elementos
- limit: limita o número de elementos (short-circuiting)
- forEach: executa uma ação para cada elemento
- forEachOrdered: executa uma ação para cada elemento, mantendo a ordem
- toArray: converte a stream em um array
- reduce: reduz os elementos a um único valor
- collect: coleta os elementos em uma coleção
- min: retorna o menor elemento
- max: retorna o maior elemento
- count: conta o número de elementos
- anyMatch: verifica se algum elemento satisfaz a condição
- allMatch: verifica se todos os elementos satisfazem a condição
- noneMatch: verifica se nenhum elemento satisfaz a condição
- findFirst: retorna o primeiro elemento
- findAny: retorna qualquer elemento
Stream<T> stream = list.stream(); // Cria uma stream a partir de uma coleção
Stream<String> stream = Stream.of("Maria", "Alex", "Bob"); // Cria uma stream a partir de valores
Stream<Integer> stream = Stream.iterate(0, x -> x + 2).limit(10); // Cria uma stream infinita e limita a 10 elementos
Stream<Double> stream = Stream.generate(() -> Math.random()).limit(5); // Cria uma stream infinita de números aleatórios e limita a 5 elementosStrem<Long> fibo = Stream.iterate(new Long[]{0L, 1L}, p -> new Long[]{p[1], p[0] + p[1]}).map(p -> p[0]);List<Integer> newList= list.stream()
.filter(x-> x% 2 == 0)
.map(x-> x* 10)
.collect(Collectors.toList());É a sigla para Java Database Connectivity, que é uma API padrão para conectar e interagir com bancos de dados relacionais em Java.
Para utilizar o JDBC é necessário adicionar alguns arquivos.
- Adicionar o driver JDBC do banco de dados que você está utilizando (por exemplo, MySQL Connector/J para MySQL).
-
No IntelliJ, vá para File > Project Structure (ou pressione Ctrl+Alt+Shift+S).
-
Selecione Libraries na barra lateral esquerda.
-
Clique no ícone
+e selecione Java. -
Navegue até o arquivo mysql-connector-j-x.x.x.jar que você acabou de baixar e selecione-o.
-
Clique em OK e aplique as alterações.
-
Adicionar um arquivo chamado
db.propertiesna pasta raiz do projeto, com as informações de conexão com o banco de dados. Que seria tipo o.env.
user=dev
password=1234567
dburl=jdbc:mysql://localhost:3306/coursejdbc
useSSL=false-
Criar um pacote chamado
dbpara colocar a classe de conexão com o banco de dados. -
Criar uma classe chamada
DbExceptiondentro do pacotedb, que será responsável por tratar as exceções relacionadas ao banco de dados.
public class DbException extends RuntimeException {
private static final long serialVersionUID = 1L;
public DbException(String message) {
super(message);
}
}- Criar uma classe chamada
DBdentro do pacotedb, que será responsável por gerenciar a conexão com o banco de dados.
public class DB {
private static Connection conn = null;
public static Connection getConnection() {
if (conn == null) {
try {
Properties props = loadProperties();
String url = props.getProperty("dburl");
conn = DriverManager.getConnection(url, props);
} catch (SQLException e) {
throw new DbException(e.getMessage());
}
}
return conn;
}
public static void closeConnection() {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
throw new DbException(e.getMessage());
}
}
}
private static Properties loadProperties() {
try (FileInputStream fs = new FileInputStream("db.properties")) {
Properties props = new Properties();
props.load(fs);
return props;
} catch (IOException e) {
throw new DbException(e.getMessage());
}
}
}O Statement é uma interface que representa uma instrução SQL que pode ser executada contra o banco de dados. Ele permite que você execute consultas SQL e atualizações no banco de dados.
O ResultSet é uma interface que representa o resultado de uma consulta SQL. Ele permite que você navegue pelos dados retornados pela consulta e acesse os valores das colunas.
O PreparedStatement é uma subinterface do Statement que permite que você crie instruções SQL pré-compiladas. Ele é usado para executar consultas SQL com parâmetros, o que ajuda a prevenir ataques de injeção SQL e melhora o desempenho das consultas.
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = DB.getConnection();
st = conn.createStatement();
rs = st.executeQuery("SELECT * FROM department");
while (rs.next()) {
System.out.println(rs.getInt("Id") + ", " + rs.getString("Name"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DB.closeResultSet(rs);
DB.closeStatement(st);
DB.closeConnection();
}SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
Connection conn = null;
PreparedStatement st = null;
try {
conn = DB.getConnection();
st = conn.prepareStatement(
"INSERT INTO seller "
+ "(Name, Email, BirthDate, BaseSalary, DepartmentId) "
+ "VALUES (?, ?, ?, ?, ?)"
);
st.setString(1, "Carlos");
st.setString(2, "carlos@gmail.com");
st.setDate(3, new java.sql.Date(sdf.parse("22/04/1982").getTime()));
st.setDouble(4, 3000.00);
st.setInt(5, 4);
int rowsAffected = st.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
finally {
DB.closeStatement(st);
DB.closeConnection();
} SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
Connection conn = null;
PreparedStatement st = null;
try {
conn = DB.getConnection();
st = conn.prepareStatement(
"INSERT INTO seller "
+ "(Name, Email, BirthDate, BaseSalary, DepartmentId) "
+ "VALUES (?, ?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS
);
st.setString(1, "Carlos");
st.setString(2, "carlos@gmail.com");
st.setDate(3, new java.sql.Date(sdf.parse("22/04/1982").getTime()));
st.setDouble(4, 3000.00);
st.setInt(5, 4);
int rowsAffected = st.executeUpdate();
if (rowsAffected > 0) {
ResultSet rs = st.getGeneratedKeys();
while (rs.next()) {
int id = rs.getInt(1);
System.out.println("Done! Id = " + id);
}
DB.closeResultSet(rs);
} else {
System.out.println("No rows affected!");
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
finally {
DB.closeStatement(st);
DB.closeConnection();
} Connection conn = null;
PreparedStatement st = null;
try {
conn = DB.getConnection();
st = conn.prepareStatement(
"UPDATE seller "
+ "SET BaseSalary = BaseSalary + ? "
+ "WHERE (DepartmentId = ?)"
);
st.setDouble(1, 200);
st.setInt(2, 2);
int rowsAffected = st.executeUpdate();
System.out.println("Done" + rowsAffected);
} catch (SQLException e) {
e.printStackTrace();
} finally {
DB.closeStatement(st);
DB.closeConnection();
} Connection conn = null;
PreparedStatement st = null;
try {
conn = DB.getConnection();
st = conn.prepareStatement(
"DELETE FROM seller WHERE "
+ "Id = ?"
);
st.setInt(1, 8);
int rowsAffected = st.executeUpdate();
System.out.println("Done! Rows affected: " + rowsAffected);
} catch (SQLException e) {
throw new DbIntegrityException(e.getMessage());
} finally {
DB.closeStatement(st);
DB.closeConnection();
}Esse é um exemplo de como criar uma exceção personalizada para tratar erros de integridade referencial ao tentar deletar um registro que está sendo referenciado por outro registro no banco de dados. ou seja, quando tem um FK.
public class DbIntegrityException extends RuntimeException {
private static final long serialVersionUID = 1L;
public DbIntegrityException(String msg) {
super(msg);
}
}Transações são usadas para garantir que um conjunto de operações no banco de dados sejam executadas de forma atômica. Ou seja, ou todas as operações são concluídas com sucesso, ou nenhuma delas é aplicada.
Connection conn = null;
Statement st = null;
try {
conn = DB.getConnection();
conn.setAutoCommit(false); // Desativa o auto-commit, ou seja, as mudanças não serão salvas automaticamente
st = conn.createStatement();
int rows1 = st.executeUpdate("UPDATE seller SET BaseSalary = 2090 WHERE DepartmentId = 1");
if (1 < 2) {
throw new SQLException("Erro falso");
}
int rows2 = st.executeUpdate("UPDATE seller SET BaseSalary = 3090 WHERE DepartmentId = 2");
conn.commit(); // Se tudo der certo, confirma as mudanças no banco de dados
System.out.println("Done" + rows1 + rows2);
} catch (SQLException e) {
try {
conn.rollback();
throw new DbException("Transaction rolled back" + e.getMessage());
} catch (SQLException e1) {
throw new DbException("Error trying to rollback" + e1.getMessage());
}
} finally {
DB.closeStatement(st);
DB.closeConnection();
}O padrão DAO é uma abordagem para separar a lógica de acesso a dados da lógica de negócios em uma aplicação. Ele envolve a criação de classes DAO que encapsulam as operações de banco de dados, como CRUD (Create, Read, Update, Delete). Ou seja, é aquele service que eu geralmente faço no RN.
A interface Serializable em Java é usada para indicar que uma classe pode ser serializada, ou seja, convertida em uma sequência de bytes para armazenamento ou transmissão. Isso é útil quando você deseja salvar o estado de um objeto em um arquivo, enviá-lo pela rede ou armazená-lo em um banco de dados.
Java Persistence API (JPA) é a especificação padrão da plataforma Java EE (pacote javax.persistence) para mapeamento objeto-relacional e persistência de dados. Para trabalhar com JPA é preciso incluir no projeto uma implementação da API (ex: Hibernate).
O Maven é um gerenciador de dependências e um sistema de para realizar o building. Ele gerencia as bibliotecas necessárias para o projeto, facilitando a construção, teste e implantação de aplicativos Java. Ele seria equivalente ao npm no ecossistema JavaScript.
Link de instalação Repositório com todas as dependencias
A maioria das dependências, você pega o código Maven (XML) e adiciona no arquivo pom.xml do projeto.
O Spring Boot é um framework baseado no Spring que simplifica o processo de criação de aplicativos Java.
Podemos criar um projeto Spring Boot rapidamente utilizando o Spring Initializr. Ou também podemos criar o projeto manualmente, no VS Code, apertando Ctrl + Shift + P e digitando Spring Initializr: Generate a Maven Project, selecionando as opções desejadas.
No Spring Initializr, temos que colocar algumas informações básicas do projeto, como o gerenciador de dependências (Maven), a linguagem (Java), a versão do Spring Boot, o grupo (geralmente o domínio invertido da empresa ou organização, ou seja, app.vercel.vinicius-porto), o artefato (nome do projeto), a descrição, o pacote base e a versão do Java.
Também podemos adicionar dependências ao projeto, como estamos vendo como criar uma API REST para a internet, precisamos do Spring Web, assim já temos a dependência necessária para criar controladores REST e o Apache Tomcat, que é o servidor web embutido que executa a aplicação.
Documentação oficial do devtools
O Devtools é uma ferramenta que ajuda no desenvolvimento de aplicações Spring Boot, ele adiciona muitas funcionalidades úteis, mas o principal é o hot reload, que permite que as mudanças no código sejam refletidas na aplicação sem precisar reiniciar o servidor manualmente.
Para adicionar o Devtools ao projeto, precisamos adicionar a seguinte dependência no pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>Podemos rodar pelo terminal, na pasta do projeto, com o comando:
mvn spring-boot:runPodemos também rodar pelo VS Code, apertando F5 ou indo em Run and Debug e clicando em Run.
Após criar o projeto, teremos o src (onde fica o código fonte do projeto) e o pom.xml (arquivo de configuração do Maven, onde ficam as dependências do projeto). Também temos a pasta target, que é onde o Maven coloca os arquivos compilados do projeto.
Na pasta src/main/resources, temos o arquivo application.properties, que é onde podemos colocar configurações específicas da aplicação, como a porta do servidor, configurações de banco de dados, entre outras, ou seja, seria equivalente ao arquivo .env em projetos JavaScript.
Os arquivos Java que ficam na pasta src/main/java e tambem do test, são organizados em pastas que seguem a estrutura do grupo definido no Spring Initializr.
A pasta com os controllers tem que ficar dentro da pasta do pacote base definido no Spring Initializr, ou seja, na pasta todolist.
Há duas maneiras de criar endpoints em uma aplicação Spring Boot: utilizando a anotação @Controller ou a anotação @RestController.
O @Controller é usado para criar controladores que retornam views (páginas HTML), enquanto o @RestController é usado para criar controladores que retornam dados (geralmente em formato JSON) para APIs RESTful.
GET: Busca uma informação.POST: Adiciona uma nova informação/dado.PUT: Atualizar um recurso existente no servidor.DELETE: Remove um dado.PATCH: Atualiza parcialmente uma informação/dado.
O controller precisa de uma rota para ser acessado. Podemos definir a rota base do controller utilizando a anotação @RequestMapping na classe do controller. E depois, podemos definir os métodos HTTP utilizando as anotações específicas, como @GetMapping, @PostMapping, @PutMapping, @DeleteMapping e @PatchMapping acima dos métodos.
Além dessas notações, também podemos usar a anotação @RequestMapping para definir o método HTTP, passando o método como parâmetro, por exemplo: @RequestMapping(method = RequestMethod.GET).
@RestController
@RequestMapping("/primeiraRota")
public class MeuPrimeiroController {
@GetMapping("/")
public String primeiraMensagem() {
return "Hello, World!";
}
}Podemos criar metodos para um controller que receba parametros, para isso ficar mais organizado, podemos criar uma classe modelo (Model). E o RequestBody serve para definir que os parametros serão passados no corpo da requisição HTTP.
@RestController
@RequestMapping("/users")
public class UserController {
public void create(@RequestBody UserModel user) {
System.out.println("Usuário criado: " + user.userName);
}
}
public class UserModel {
public String userName;
public String name;
public String password;
}Ao ter o RequestBody definido, já terá os setters para cada propriedade do modelo, mas teremos que criar os getters manualmente, ou utilizar o Lombok para gerar automaticamente.
Site da Documentação oficial O Lombok é uma biblioteca Java que ajuda a reduzir o código repetitivo e desnecessário, como getters, setters, construtores, entre outros.
Podemos utilizar a anotação @Data na classe do modelo para gerar automaticamente os getters e setters para todas as propriedades da classe.
import lombok.Data;
@Data
public class UserModel {
private String userName;
private String name;
private String password;
}Se quisermos apenas os getters, podemos utilizar a anotação @Getter, e para os setters, a anotação @Setter. E podemos inserir essas anotações em propriedades específicas, se não quisermos que todas as propriedades tenham getters e setters.
Documentação oficial O Spring Data JPA é um projeto do Spring que facilita a implementação de bancos de dados.
Documentação oficial
O H2 Database é um banco de dados relacional escrito em Java, que pode ser executado em modo embutido ou servidor. Ele é leve e fácil de usar, sendo ideal para desenvolvimento e testes.
Para ele funcionar, precisamos adicionar a dependência do H2 Database no pom.xml e configurar a URL do banco de dados no application.properties.
# DATASOURCE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=
# H2 CLIENT
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# JPA, SQL
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.defer-datasource-initialization=true
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=trueE para acessar o console do H2 Database, podemos acessar a URL http://localhost:8080/h2-console no navegador.
Para criar uma entidade, precisamos criar uma classe modelo e utilizar a anotação @Entity na classe. Também precisamos definir a chave primária da entidade utilizando a anotação @Id em uma propriedade da classe. E podemos utilizar a anotação @GeneratedValue para definir que o valor da chave primária será gerado automaticamente.
Também podemos utilizar a anotação @Column para definir propriedades específicas de uma coluna no banco de dados, como o nome da coluna, se ela pode ser nula, se é única, entre outros. Mas isso é opcional, se não utilizarmos a anotação @Column, o JPA irá criar a coluna com o nome da propriedade da classe.
@Data
@Entity(name = "tb_users")
public class UserModel {
@Id
@GeneratedValue(generator = "UUID")
private UUID id;
@Column(name = "user_name", nullable = false, unique = true, length = 50)
private String userName;
private String name;
private String password;
} @CreationTimestamp
private LocalDateTime createdAt; @ManyToOne
@JoinColumn(name = "department_id")
private Department department; @OneToMany(mappedBy = "department")
private List<Seller> sellers = new ArrayList<>();O JsonIgnore é uma anotação da biblioteca Jackson que é usada para ignorar uma propriedade durante a serialização e desserialização de objetos JSON. Isso é útil quando queremos evitar ciclos infinitos ou quando não queremos expor certas informações na API.
@JsonIgnore
@OneToMany(mappedBy = "department")
private List<Seller> sellers = new ArrayList<>();public enum OrderStatus {
WAITING_PAYMENT(1),
PAID(2),
SHIPPED(3),
DELIVERED(4),
CANCELLED(5);
private int code;
private OrderStatus(int code) {
this.code = code;
}
public int getCode() {
return code;
}
public static OrderStatus valueOf(int code) {
for (OrderStatus value : OrderStatus.values()) {
if (value.getCode() == code) {
return value;
}
}
throw new IllegalArgumentException("Invalid OrderStatus code");
}
}As interfaces no Java são usadas para definir contratos que as classes podem implementar. Elas podem conter métodos abstratos (sem implementação) e constantes. A interface pode ter o extends do JpaRepository, mas você precisa passar a entidade (classe) e o tipo da chave primária como generics.
public interface IUserRepository extends JpaRepository<UserModel, UUID> {}E no controller, podemos injetar a interface utilizando a anotação @Autowired, assim podemos utilizar os métodos do JpaRepository para realizar operações no banco de dados.
@Autowired
private IUserRepository userRepository;Assim podemos utilizar métodos como save(), findById(), findAll(), deleteById(), entre outros, para realizar operações no banco de dados de forma simples e rápida.
Mas caso precisemos de um método personalizado, podemos criar um método na interface facilmente.
UserModel findByUserName(String userName);É um tipo para os metodos dos controllers que representa toda a resposta HTTP, incluindo o status code, os headers e o corpo da resposta.
O return do metodo pode ser algo como:
return ResponseEntity.status(HttpStatus.CONFLICT).body("Usuário já existe!");
// ou
return ResponseEntity.status(409).body("Usuário já existe!"); @PostMapping
public ResponseEntity<User> insert(@RequestBody User obj) {
obj = service.insert(obj);
URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(obj.getId()).toUri();
return ResponseEntity.created(uri).body(obj);
} @DeleteMapping(value = "/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
service.delete(id);
return ResponseEntity.noContent().build();
}No service:
public User update(Long id, User obj) {
User entity = repository.getReferenceById(id);
updateData(entity, obj);
return repository.save(entity);
}
private void updateData(User entity, User obj) {
entity.setName(obj.getName());
entity.setEmail(obj.getEmail());
entity.setPhone(obj.getPhone());
}No controller:
@PutMapping(value = "/{id}")
public ResponseEntity<User> update(@PathVariable Long id, @RequestBody User obj) {
obj = service.update(id, obj);
return ResponseEntity.ok().body(obj);
}Podemos criar uma classe de configuração que implemente a interface CommandLineRunner, assim o método run será executado quando a aplicação iniciar. Podemos usar essa classe para popular o banco de dados com dados de teste.
@Configuration
@Profile("test")
public class TestConfig implements CommandLineRunner {
@Autowired
private UserRepository userRepository;
@Override
public void run(String... args) throws Exception {
User u1 = new User(null, "Maria Brown", "maria@gmail.com", "988888888", "123456");
User u2 = new User(null, "Alex Green", "alex@gmail.com", "977777777", "123456");
userRepository.saveAll(Arrays.asList(u1,u2));
}
}A anotação @Transient é usada para indicar que uma propriedade de uma entidade não deve ser persistida no banco de dados. Ou seja, essa propriedade não será mapeada para uma coluna na tabela do banco de dados.
O relacionamento Many-to-Many (muitos-para-muitos) é usado quando uma entidade pode estar associada a várias instâncias de outra entidade, e vice-versa. Por exemplo, um usuário pode ter vários papéis (roles), e um papel pode ser atribuído a vários usuários.
@ManyToMany
@JoinTable(name = "tb_user_role",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();Documentação oficial Igual ao bcrypt do Node.js, o Bcrypt é uma biblioteca para hashing de senhas em Java.
var passwordHash = BCrypt.withDefaults().hashToString(12, userModel.getPassword().toCharArray());Você precisa passar a senha inserida e depois a senha armazenada, ambas em char array para o verifyer do Bcrypt.
var passwordVerify = BCrypt.verifyer().verify(password.toCharArray(), user.getPassword());E o passwordVerify terá a propriedade verified, que será true se a senha estiver correta, ou false se estiver incorreta.
if (!passwordVerify.verified) {
response.sendError(401);
}Os Filters ficam numa pasta separada chamada filter dentro do pacote base do projeto.
Eles são usados para interceptar requisições HTTP antes que elas cheguem aos controllers, permitindo que possamos realizar ações como autenticação, logging, entre outros.
@Component
public class FilterTaskAuth implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
}
}ou também podemos extender a classe OncePerRequestFilter, que garante que o filtro será executado apenas uma vez por requisição. (Mais usado e facil de implementar)
@Component
public class FilterTaskAuth extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(request, response);
}
}Temos o filterChain.doFilter(request, response); que é usado para passar a requisição e a resposta para o próximo filtro na cadeia, ou para o controller, caso não haja mais filtros. Ele é chamado no final de todas as verificações do filtro.
@Component
public class FilterTaskAuth extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Pega o user e a senha
var authorization = request.getHeader("Authorization");
// Remover o "Basic " do começo
var authEncoded = authorization.substring("Basic ".length()).trim();
// Decodificar o Base64
byte[] authDecode = Base64.getDecoder().decode(authEncoded);
// Converter para String
var authString = new String(authDecode);
// Separar user e senha
String[] credentials = authString.split(":");
String userName = credentials[0];
String password = credentials[1];
System.out.println("user: " + userName);
System.out.println("password: " + password);
filterChain.doFilter(request, response);
}
}Colocamos o if para verificar se a rota da requisição começa com /tasks ou se é a rota /tasks/, assim só iremos validar a autenticação para as rotas de tarefas.
Jeito que o curso ensinou:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
var servletPath = request.getServletPath();
if (servletPath.equals("/tasks/")) {
// Validação de autenticação aqui
}
}Jeito mais simples de fazer a verificação:
if (request.getRequestURI().startsWith("/tasks")) {
// Validação de autenticação aqui
}Para pegar o ID do usuário autenticado na requisição, podemos adicionar um atributo na requisição dentro do filtro, assim conseguimos pegar esse atributo nos controllers.
request.setAttribute("idUser", user.getId());E para recuperar esse atributo no controller, podemos fazer assim:
public Model create(@RequestBody Model model, HttpServletRequest request) {
request.getAttribute("idUser");
}var currentDate = LocalDateTime.now();
if (currentDate.isAfter(taskModel.getStartAt()) || currentDate.isAfter(taskModel.getEndAt())) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("A data de início ou fim não pode ser no passado.");
} @PutMapping("/{id}")
public void update(@RequestBody Model model, HttpServletRequest request, @PathVariable UUID id) {
}Podemos criar uma pasta chamada utils dentro do pacote base do projeto, para colocar classes utilitárias que podem ser usadas em várias partes do projeto.
Esse util copia apenas as propriedades não nulas de um objeto para outro, ignorando as propriedades que são nulas.
public class Utils {
public static void copyNonNullProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
}
public static String[] getNullPropertyNames(Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for (PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) {
emptyNames.add(pd.getName());
}
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
} if (!task.getIdUser().equals(idUser)) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Tarefa não pertence ao usuário.");
}Caso tenha alguma regra de negócio para uma propriedade, podemos inserir essa validação no setter da propriedade, assim toda vez que tentarmos setar um valor para essa propriedade, a validação será feita.
public void setTitle(String title) throws Exception {
if(title.length() > 50) {
throw new Exception("O título não pode ter mais de 50 caracteres.");
}
this.title = title;
}É uma anotação usada para definir uma classe que irá tratar exceções de forma global na aplicação Spring Boot.
Nesse caso, estamos tratando a exceção HttpMessageNotReadableException, que é lançada quando o corpo da requisição HTTP não pode ser lido ou convertido para o objeto esperado.
@ControllerAdvice
public class ExceptionHandlerController {
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<String> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMostSpecificCause().getMessage());
}
}É recomendado construir um Docker para fazer o deploy da aplicação em um servidor com as configurações adequadas.
FROM maven:3.8.4-openjdk-17 AS build
WORKDIR /app
COPY . .
RUN mvn clean install -DskipTests
FROM eclipse-temurin:17-jre-alpine
EXPOSE 8080
COPY --from=build /app/target/todolist-1.0.0.jar app.jar
ENTRYPOINT [ "java", "-jar", "app.jar" ]Link de instalação do Mongo DB Após instalar o MongoDB, precisamos iniciar o serviço do MongoDB. Podemos abrir o terminal dentro da pasta bin do MongoDB que fica em program files e executar o comando:
mongodIsso irá iniciar o serviço do MongoDB na porta padrão 27017.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>A anotação @Document é usada para indicar que uma classe é um documento do MongoDB. Ela é equivalente à anotação @Entity do JPA, mas para o MongoDB. Pode definir o nome da coleção com @Document(collection = "nome_da_colecao"). Se o nome da coleção não for definido, o Spring Data MongoDB irá usar o nome da classe como nome da coleção.
O DTO serve para transferir dados entre camadas da aplicação, como entre o controller e o service, ou entre o service e o repository. Ele muitas vezes tem menos propriedades do que a entidade, ou seja, ele é uma representação simplificada da entidade, contendo apenas as informações necessárias para a transferência de dados.
O Spring Data JPA e o Spring Data MongoDB possuem uma funcionalidade chamada query methods, que permite criar consultas personalizadas apenas definindo o nome do método na interface do repositório. O Spring Data irá interpretar o nome do método e gerar a consulta SQL ou MongoDB correspondente.
Por exemplo, se quisermos buscar um usuário pelo nome, podemos criar um método na interface do repositório com o nome findByName, e o Spring Data irá gerar a consulta SQL ou MongoDB para buscar o usuário pelo nome. E também há outras variações, como findByNameContaining, findByNameStartingWith, findByNameEndingWith, entre outros, que permitem criar consultas mais específicas apenas definindo o nome do método.
@Repository
public interface PostRepository extends MongoRepository<Post,String> {
List<Post> findByTitleContaining(String title);
}A anotação @Query é usada para definir uma consulta personalizada em um método de repositório. Ela pode ser usada para criar consultas mais complexas ou para usar uma sintaxe de consulta diferente da gerada pelos query methods.
Documentação oficial
@Query("{ 'title': { $regex: ?0, $options: 'i' } }")
List<Post> searchTitle(String text);
@Query("{ $and: [ { date: {$gte: ?1} }, { date: { $lte: ?2} } , { $or: [ { 'title': { $regex: ?0, $options: 'i' } }, { 'body': { $regex: ?0, $options: 'i' } }, { 'comments.text': { $regex: ?0, $options: 'i' } } ] } ] }")
List<Post> fullSearch(String text, Date minDate, Date maxDate);

