An open API service indexing awesome lists of open source software.

https://github.com/isaacalves7/java

☕ It's a repository of Java programming language and his content.
https://github.com/isaacalves7/java

android gradle java java-programming javaee javafx jboss jpa jsf jsp junit5 kotlin maven oci oracle-database oracle-db primefaces spring spring-boot

Last synced: 15 days ago
JSON representation

☕ It's a repository of Java programming language and his content.

Awesome Lists containing this project

README

          

> Versículo chave: "Consagre ao Senhor tudo o que você faz, e os seus planos serão bem-sucedidos." - Provérbios 16:3

# It's a repository of Java language ☕
> ☕ **Preparação**: Para este conteúdo, o aluno deverá dispor de um computador com acesso à internet, um web browser com suporte a HTML 5 (Google Chrome, Mozilla Firefox, Microsoft Edge, Safari, Opera etc.), um editor de texto ou IDE (VSCode etc.) e o software JDK, com a versão mais recente, instalado na sua máquina local.

Sou um especialista em desenvolvimento de software com foco no ecossistema Java/Kotlin, atuando com frameworks robustos como Spring Boot, Jakarta EE e Quarkus.

Tenho sólida experiência na aplicação de boas práticas de engenharia de software, como os princípios SOLID, DRY, KISS, TDA e SoC, com domínio na aplicação de design patterns (criacionais, estruturais e comportamentais), além da adoção dos princípios de Clean Code e Clean Architecture para garantir legibilidade, manutenibilidade e escalabilidade do código.

No front-end, colaboro em projetos com SPAs desenvolvidas em React, Angular e Vue, integradas com back-ends Java via APIs REST ou GraphQL. Também participo de aplicações SSR com frameworks como Next.js ou Thymeleaf em projetos fullstack Java. Tenho domínio de HTML/CSS, Flexbox e CSS Grid, com foco em design responsivo baseado em protótipos UI/UX elaborados no Figma.

No back-end, desenvolvo APIs RESTful e GraphQL utilizando Spring Boot (com Spring Web, Spring Data, Spring Security e graphql-java). Tenho experiência com Git e colaboração em times ágeis com pipelines CI/CD (Jenkins, GitLab CI, GitHub Actions), análise estática e linting com ferramentas como Checkstyle, PMD e SonarQube, além da escrita de testes unitários e de integração com JUnit, Mockito, Testcontainers e REST-assured. Trabalho em arquiteturas distribuídas e baseadas em microsserviços com comunicação via REST e mensageria (RabbitMQ, Apache Kafka), com observabilidade garantida por Prometheus, Grafana e logs estruturados com Logback, Logstash ou Elastic Stack.

Também aplico ferramentas de analytics como Google Analytics 4 e Segment, monitorando a usabilidade de componentes e o comportamento dos usuários em ambientes de produção. Tenho experiência com deploy e escalabilidade de aplicações em ambientes PaaS (Heroku, Azure App Service) e uso de recursos em nuvens públicas como AWS (EC2, ECS, RDS, S3), Azure e Google Cloud (App Engine, GKE).

# ☕ The History of Java language

O **Java** é o ambiente computacional, ou plataforma, criada pela empresa estadunidense Sun Microsystems, e vendida para a Oracle depois de alguns anos. A plataforma permite desenvolver programas utilizando a linguagem de programação Java. A tecnologia Java foi desenvolvida na década de 1990, a partir de um projeto pessoal de um funcionário da Sun Microsystems. A ideia inicial estava ligada à criação de uma linguagem de programação que pudesse ser utilizada em diferentes sistemas, alterando o paradigma de que uma aplicação só poderia ser desenvolvida para uso em um único ambiente de hardware e sistema operacional, como era bastante comum na época.

As grandes empresas desenvolviam suas aplicações voltadas para seu ambiente de hardware e software (sistema operacional - SO), e estas aplicações não eram capazes de serem executadas em diferentes plataformas, principalmente de outros fabricantes. Se analisarmos a linguagem C, criada junto com o sistema operacional UNIX, temos uma biblioteca muito vasta de funções, mas poucas são consideradas padrão para atender a diferentes sistemas; e, mesmo assim, uma aplicação compilada em um sistema operacional (ambiente) não pode ser executada em outro.

A linguagem Java rompeu este paradigma e passou a permitir que uma aplicação desenvolvida em um ambiente - hardware + software (SO) - possa ser executada em outro sem necessidade de qualquer outro procedimento. A Sun Microsystems, ao tomar conhecimento desta ideia, deu total apoio ao seu desenvolvimento e criou um grupo com 13 membros, liderado por James Gosling, que passaram a trabalhar exclusivamente neste projeto. A equipe foi batizada de “Green Team” e o grupo passou a trabalhar em um conjunto de escritórios fora das dependências físicas da Sun, e sem qualquer tipo de comunicação com a matriz, durante 18 meses para a concretização desta ideia.

Com a tecnologia Java, as aplicações passaram a ser portáveis de um sistema para o outro, sem nenhuma necessidade de alteração. Por isso, afirmamos que a portabilidade é uma das mais importantes características da linguagem Java.

Ainda naquela época, o grupo já havia antecipado uma nova onda na computação, na convergência entre dispositivos controlados digitalmente e computadores. Hoje em dia, percebemos bem isso quando analisamos um smartphone, um dispositivo digital que possui inúmeras funções de computadores; entre elas, podemos destacar a execução de aplicativos. Inicialmente, a linguagem foi batizada de **Oak**, pois o grupo tinha como vista da janela do escritório *um carvalho*. Posteriormente, a linguagem foi rebatizada como **Java**, em função do *gosto do grupo pelo tipo de café*. Por isso, temos como ícone da linguagem uma xícara de café com sua fumaça característica.

A linguagem é muito poderosa para o desenvolvimento de aplicações, seja para o desenvolvimento de aplicações menos sofisticadas ou para uso em dispositivos menos complexos que computadores, conhecidos como dispositivos inteligentes, tais como cafeteiras, micro-ondas, geladeiras e uma gama de outros dispositivos que possam ser controlados por software. A linguagem ainda é muito eficiente no desenvolvimento de sistemas de entretenimento doméstico, dando suporte a streaming de vídeo e televisão digital, que ainda não era tão desenvolvida na época.

A tecnologia Java permite ainda o desenvolvimento de todos os tipos de aplicações, indo do mais simples controle de um eletrodoméstico, passando por aplicações domésticas, comerciais, de automação, até o desenvolvimento de aplicações mais complexas, com comunicação de dados e aplicações para supercomputadores.

A linguagem Java teve início ao incorporar a tecnologia Java ao navegador de internet *Netscape navigator*, em sua versão de 1995. A tecnologia ganhou a aceitação do mercado e dos desenvolvedores, sendo uma das mais importantes linguagens de programação para o desenvolvimento de sistemas. São dezenas de milhões de desenvolvedores Java no mundo e, atualmente, esta tecnologia é encontrada em supercomputadores, servidores, desktops, notebooks, máquinas de cartões de crédito e débito, robôs, automóveis, jogos eletrônicos, bem como uma gama de dispositivos digitais, redes e demais tecnologias de programação. A linguagem Java ainda é a linguagem nativa para o desenvolvimento de aplicações para o Android (sistema operacional para smartphones).

A tecnologia Java foi totalmente gratuita por muito tempo, mas recentemente a Oracle, que passou a deter os direitos da linguagem após adquirir a Sun Microsystems, está licenciando o uso para empresas com custos. A empresa deve permitir o licenciamento gratuito somente para desenvolvedores avulsos que criam aplicações pessoais sem custo ou para simples aprendizado.

Principais características e vantagens da tecnologia Java:

- Orientada a objetos, com uma grande diversidade de bibliotecas de classes disponível;
- Independe de plataforma: write once, run everywhere ;
- Segurança - Mecanismos para sistemas livres de vírus, pacotes para criptografia;
- Simplicidade;
- Sintaxe dos comandos básicos segue o padrão do C;
- Sintaxe da parte OO bem mais simples que o C++;
- Internacionalização;
- Unicode: padrão que permite manipular textos de qualquer sistema de escrita;
- Robustez;
- Tratamento de exceções;
- JVM (Java Virtual Machine) impede que uma aplicação mal comportada paralise o sistema;
- Distribuída e multitarefa;
- Os programas podem utilizar recursos da rede com a mesma facilidade que acessam arquivos locais;
- Trabalha com diversos protocolos (TCP/IP, HTTP, FTP);
- Execução simultânea de múltiplas threads;
- Gerenciamento de memória;
- Memória virtual gerenciada pela JVM (Java Virtual Machine);
- Garbage collection (limpeza de memória);
- Desempenho;
- Mais rápida que linguagens de script, porém mais lenta que as linguagens compiladas puras;
- Hoje, os problemas de desempenho são resolvidos com compilação just-in-time.

![623784202_1546511484145091_5190546318736151751_n](https://github.com/user-attachments/assets/494faef1-075c-4d00-aa33-dd494874d850)

A **plataforma Java** é uma plataforma de software desenvolvida pela Sun Microsystems (agora parte da Oracle), projetada para fornecer um ambiente para o desenvolvimento e execução de aplicações independentes de sistema operacional e hardware, mais especificamente ela é o sistema de implementação do Java. Isso é possível graças ao conceito de *Write Once, Run Anywhere (WORA)*, onde um programa Java pode ser escrito uma vez e executado em qualquer dispositivo que tenha a **Java Virtual Machine (JVM)** instalada. De forma geral, entendemos que plataforma (ambiente de execução) é composta por hardware + software básico (sistema operacional). A plataforma Java é puramente baseada em software, e sua estrutura modular (JVM, API, JRE e JDK) permite que aplicações Java sejam portáveis, seguras e eficientes. Essa arquitetura fez do Java uma das linguagens mais utilizadas no mundo, especialmente em sistemas empresariais, aplicações web e desenvolvimento para dispositivos embarcados.

A plataforma Java é definida apenas em software e possui dois componentes:

- Máquina Virtual Java (JVM - Java Virtual Machine);
- Conjunto de bibliotecas que disponibilizam classes comuns.

Detalhando mais sobre a estrutura da Plataforma Java, ela é composta por quatro camadas principais, cada uma com um papel fundamental:

Máquina Virtual Java (JVM - Java Virtual Machine):

A **JVM** é o coração da plataforma Java e atua como um **interpretador** para o código Java compilado (bytecode). Ela é responsável por:
- Executar programas Java de forma independente do sistema operacional.
- Gerenciar memória automaticamente via **Garbage Collector (GC)**.
- Otimizar o desempenho através da **compilação Just-In-Time (JIT)**.
- Fornecer segurança ao isolar o código executado.

A JVM traduz o **bytecode Java** para instruções específicas da máquina onde está rodando.

Biblioteca de Classes (Java API - Application Programming Interface):

A **API do Java** é um conjunto de bibliotecas padrão que oferece funcionalidades essenciais para o desenvolvimento de aplicações. Ela contém:
- **Pacotes básicos** (`java.lang`, `java.util`, `java.io`) → Manipulação de strings, coleções, entrada/saída, etc.
- **Bibliotecas de rede** (`java.net`) → Comunicação via HTTP, sockets e WebSockets.
- **Bibliotecas de concorrência** (`java.util.concurrent`) → Threads, sincronização, paralelismo.
- **Acesso a bancos de dados** (`java.sql`, `javax.persistence`) → JDBC, JPA.
- **Interface gráfica** (`javax.swing`, `javafx`) → Desenvolvimento de GUIs.

Ambiente de Execução (JRE - Java Runtime Environment):

O **JRE** é o ambiente necessário para executar aplicações Java e contém:
- A **JVM**.
- As **bibliotecas de classes da API Java**.
- Ferramentas básicas para rodar aplicações Java.

Se um usuário final quiser rodar um programa Java, basta ter o **JRE** instalado. No entanto, para desenvolvimento, o JRE sozinho não é suficiente.

Kit de Desenvolvimento Java (JDK - Java Development Kit):

O **JDK** é um **superconjunto do JRE** e inclui ferramentas para **desenvolver** aplicações Java. Ele contém:
- **Compilador (javac)** → Transforma código-fonte Java em **bytecode**.
- **Depuradores e ferramentas de monitoramento** (`jdb`, `jconsole`, `jvisualvm`).
- **Bibliotecas adicionais** para desenvolvimento avançado.
- **Ferramentas para modularização** (desde o Java 9 com `jlink` e `jmod`).

Se um desenvolvedor deseja programar em Java, ele precisa do **JDK**.

Exemplo de Fluxo de Execução na Plataforma Java:

1. Um desenvolvedor escreve um programa em **Java** (`.java`).
2. O código-fonte é compilado pelo **`javac`** e transformado em **bytecode** (`.class`).
3. A **JVM** lê o bytecode e o executa na máquina usando o **JRE**.

API Java:

"Diferentemente das linguagens convencionais, que são compiladas para código nativo, a linguagem Java é compilada para "bytecode" (gerando o .class ou .jar), que é executado por uma máquina virtual Java (JVM - Java Virtual Machine)."

O modelo inicial era interpretado. Já o atual trocou a etapa do interpretador por uma 2ª compilação (compilador JIT, isto é, just-in-time).

A tecnologia Java é composta por três plataformas:

- **J2SE** ou **Java SE (Java Standard Edition)**: base da plataforma, inclui o ambiente de execução e as bibliotecas comuns;

- **J2EE** ou **Java EE (Java Enterprise Edition)**: versão voltada para o desenvolvimento de aplicações corporativas e aplicações web;

- **J2ME** ou **Java ME (Java Micro Edition)**: versão voltada para o desenvolvimento de aplicações móveis ou embarcadas.

A sigla JSE pode referir-se a diferentes tecnologias dependendo do contexto, mas comumente tem dois significados principais:

1. **Java Standard Edition (Java SE)**: Este é um conjunto de especificações e tecnologias fornecidas pela Oracle para o desenvolvimento de aplicativos Java. O Java SE inclui a linguagem de programação Java, a Máquina Virtual Java (JVM) e bibliotecas padrão para criar aplicações de desktop e servidor.

2. **JavaScript Engine**: Este é um motor que executa o código JavaScript, comumente usado em navegadores web para executar scripts em páginas web. Exemplos de motores JavaScript incluem o V8 do Google Chrome, o SpiderMonkey do Mozilla Firefox e o Chakra do Microsoft Edge.

Portanto, a sigla JSE pode se referir tanto ao Java Standard Edition, relacionado à linguagem de programação Java, quanto a um motor JavaScript, relacionado ao JavaScript. O significado exato depende do contexto em que a sigla é utilizada.

![1752755951665](https://github.com/user-attachments/assets/677e64be-31d6-4a07-a535-a3e412f39aae)

## [Java] Ambiente de desenvolvimento

Existem os ambientes JDK e JRE para construir e executar uma aplicação Java.

O **Java Development Kit (JDK)** é uma coleção de programas para, dentre outras tarefas, compilar e executar aplicações Java. Este é o kit necessário para o desenvolvedor, pois contém todo o suporte para a criação de aplicações em Java.

Exemplo:
- Javac (compilador Java);
- Javadoc (utilitário para documentação);
- Java;
- Outros.

Kit com todos os programas necessários para executar aplicações Java. Faz parte do JDK, mas pode ser instalado separadamente para execução em máquinas clientes, uma vez que o JDK é voltado para os desenvolvedores. O JRE pode ser instalado separadamente e dá suporte somente a execução de aplicações ou jogos como o Minecraft, por isso é a versão mais indicada para instalação nas máquinas clientes que apenas executarão aplicações, não sendo responsáveis pelo seu desenvolvimento.

![img01](https://user-images.githubusercontent.com/61624336/112771689-6c3a6500-9003-11eb-8436-1bda07a25ea3.jpg)

O código de um programa Java é compilado apenas uma vez, gerando um código intermediário, o **bytecode**, que pode ser executado quantas vezes forem necessárias em qualquer ambiente que possua uma máquina virtual Java (JVM) disponível.

Inicialmente a tecnologia Java realizava uma interpretação completa do bytecode, mas atualmente o interpretador realiza uma compilação **just-in-time** (compila o bytecode para o ambiente onde ocorrerá a execução), permitindo aumentar o desempenho da aplicação.

Para o desenvolvimento de aplicações em Java é comum o uso de ferramentas IDEs (Integrated Development Environment), que facilitam a codificação e a realização de testes, sendo as mais conhecidas:

- Eclipse;
- NetBeans;
- IntelliJ;
- BlueJ.

Você pode usar qualquer tipo de ambiente, se preferir um editor de texto como um bloco de notas ou um VSCode você precisa usar a extensão `.java`.

# ☕ [Java] Ambiente de programação

Existem várias ferramentas para o desenvolvimento de sistemas utilizando a linguagem Java, mas os desenvolvedores têm preferência pelos IDEs **Netbeans** e **Eclipse**. Ambos são gratuitos e podem ser adquiridos pela internet através de download.

É importante que você já tenha instalado o JDK antes de instalar o seu IDE escolhido (Netbeans ou Eclipse). Assista o vídeo:

Existem dois arquivos diferentes: o primeiro, com o source, contém os códigos fonte no Netbeans e não é o ideal para trabalharmos o desenvolvimento. A versão adequada para nós é a versão bin, que contém todos os códigos já compilados e prontos para a execução e desenvolvimento de projetos e aplicações Java.

Você não precisa instalar as duas, pois ambas são concorrentes e desempenham as mesmas funcionalidades. A preferência é a critério do programador.

A linguagem Java possui uma base de construção semelhante à linguagem C e, por isso, boa parte de sua estrutura e sintaxe se assemelha a ela. Desta forma, programadores com conhecimento nesta linguagem tem grande facilidade com a sintaxe da linguagem Java. Outra importante semelhança está nas estruturas de controle de fluxo, que são construídas da mesma forma em ambas as linguagens.

> Cuidado com as diferenças de versões no sistema operacional: se instalar o Java para 64 bits, você deverá usar um IDE (Netbeans ou Eclipse) de 64 bits. O mesmo para a versão de 32 bits: tanto o Java quanto o IDE deverão ser para 32 bits.

## [Java] `Hello, World!`
Veja abaixo o passo a passo para criar sua primeira aplicação em Java, imprimindo na tela o `Hello, World!`, você pode usar qualquer editor de texto e salvar a extensão do arquivo `.java`, mas nesse exemplo, eu estarei utilizando a IDE Netbeans:








O projeto `Exemplo` foi criado e automaticamente teremos uma **classe inicial** para execução da aplicação.

![img07](https://user-images.githubusercontent.com/61624336/112783806-89ccf600-9026-11eb-9a35-7d2d9b04774e.jpg)

O ambiente está pronto para digitarmos o código da aplicação: preencha o código conforme o exemplo a seguir.

![img08](https://user-images.githubusercontent.com/61624336/112783867-ac5f0f00-9026-11eb-824b-76e8d7f981c5.jpg)

[![.JAVA](https://img.shields.io/badge/-Exemplo.java-000000?style=social&logo=OpenJdk&logoColor=chocolate)](#)

```java
import java.util.Scanner;

public class Exemplo {

// identação do bloco da classe Exemplo (a classe sempre possui o nome do arquivo, os dois andam bem alinhados!)
public static void main(String[] args) {

// identação do bloco do método main

// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
double media, nota1, nota2;

System.out.println("Digite a nota 1:");
nota1 = Double.parseDouble(sc.nextLine());

System.out.println("Digite a nota 2:");
nota2 = Double.parseDouble(sc.nextLine());

media = (nota1 + nota2) /2.0;

System.out.println("A sua média é:" + media);
sc.close();

} // encerramento da identação do bloco do método main
} // encerramento da identação da classe Exemplo
```

Após o código estar pronto e sem erros, podemos executar a aplicação clicando sobre o “arquivo da classe” com o botão direito, e em seguida clicar sobre a opção `Run file`.

![img10](https://user-images.githubusercontent.com/61624336/112784797-e7624200-9028-11eb-8739-7c214e90e4f0.jpg)

A aplicação executará na parte inferior do Netbeans:

![img11](https://user-images.githubusercontent.com/61624336/112784856-03fe7a00-9029-11eb-93f7-7da23c1e5735.jpg)

Crie um programa em java e execute os seguintes comandos para abri-lo como saída pelo terminal:

[![.JAVA](https://img.shields.io/badge/-HelloWorld.java-000000?style=social&logo=OpenJdk&logoColor=chocolate)](#)

```java
public class HelloWorld {
public static void main(String[] args)
{
System.out.println("Hello, World!");
}
}
```

Configurando a saída do programa pela interface de linha de comando (CLI) do terminal:

```sh
# Compilar o App na JVM
javac app.java
java app

# Compilar o arquivo .JAR na JVM
java -jar app.jar

# Compilar o arquivo .JAR na JVM pelo Powershell usando versão específica
& "C:\Program Files\Java\jre1.8.0_471\bin\java.exe" -jar app.jar --installServer
```

A linguagem **Java** tem boa parte de suas características herdadas da linguagem **C**. Muitos dos seus operadores, formação de identificadores, comandos de controle de fluxo e várias outras características são compartilhados entre estas duas linguagens.

Todas as instruções da linguagem Java devem terminar por um símbolo de ponto e vírgula “;”. Você não usará o ponto e vírgula quando a instrução for uma codificação que irá continuar com um bloco de comandos.

Vejamos um exemplo:

```java
System.out.println(“Mensagem do sistema”);
```

Os blocos de comandos em Java são delimitados por { (abrir) e } (fechar) chaves, em que a instrução anterior define que todos os comandos do bloco farão parte desta. Isso irá ocorrer em classes, métodos e instruções de controle de fluxo.

Exemplo:

```java
if(nota>10.0) {

System.out.println(“Nota inválida”);

}
```

Como usar a endentação? Quando desenvolvemos um programa em qualquer linguagem, é comum que utilizemos um conjunto de espaços na frente das instruções de forma a facilitar a visualização de blocos. Sempre que iniciamos um bloco, devemos começar na próxima linha com um deslocamento de pelo menos quatro espaços em brando ou uma tabulação (normalmente quatro espaços). Isso permite que identifiquemos rapidamente que certo conjunto de instruções faz parte de um conjunto que será executado em bloco.

Exemplo:

```java
import java.util.Scanner;

public class Exemplo {
// identação do bloco da classe Exemplo (a classe sempre possui o nome do arquivo, os dois andam bem alinhados!)
public static void main(String[] args) {
// identação do bloco do método main
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
double media, nota1, nota2;

System.out.println("Digite a nota 1:");
nota1 = Double.parseDouble(sc.nextLine());

System.out.println("Digite a nota 2:");
nota2 = Double.parseDouble(sc.nextLine());

media = (nota1 + nota2) /2.0;

System.out.println("A sua média é:" + media);
sc.close();

} // encerramento da identação do bloco do método main
} // encerramento da identação da classe Exemplo
```

Em todas as linguagens de programação, devemos identificar variáveis, programas, funções, métodos, parâmetros etc. O ato de nomear algo em uma linguagem de programação é uma forma de identificação da linguagem. Em Java são permitidos identificadores que comecem com letras (maiúsculas ou minúsculas), ou um símbolo de “$” (dólar) ou “_” (underscore /underline). Números podem ser usados, mas não para iniciar um identificador.

**Java** é uma linguagem de programação sensível à caixa (alta ou baixa ou **case sensitive**). Desta forma, a linguagem faz distinção entre letras maiúsculas e minúsculas. Mas isso não quer dizer que podemos utilizar qualquer nome como um identificador, pois existem algumas palavras reservadas que não podem ser utilizadas para tal.

Exemplos de identificadores válidos em Java:

- `identificador`
- `nomeCompleto`
- `NomeCompleto`
- `nota1`
- `_sys_path`
- `$user`

Observe que os exemplos 2 e 3 possuem a mesma grafia, mas, como existe mudança entre caixa alta e baixa, para a linguagem Java são dois diferentes identificadores.


Palavras reservadas da linguagem Java (não podem ser usadas como identificadores), dentre elas podemos destacar:

abstract
arguments
await
boolean

break
byte
case
catch


char
class
const
continue

debugger
default
delete
do

double
else
enum
eval

export
extends
false
final

finally
float
for
function

goto
implements
if
import

in
instanceof
int
interface

let
long
native
new

null
package
private
protected

public
return
short
static

super
switch
synchronized
this

throw
throws
tdrow
transient - true

try
typeof
var
void

volatile
while
with
yield

> OBS: Todas as palavras reservadas começam por letras minúsculas e são palavras do idioma inglês.

## [Java] Comentários
O uso de comentários em Java é semelhante ao usado na linguagem C, mas apenas dois destes tipos são iguais nas duas linguagens, sendo o terceiro tipo somente disponibilizado na linguagem Java.

Vejamos:

// comentário de uma linha

Quando usamos duas barras em uma linha de código, todo o seu conteúdo, após as duas barras, é desconsiderado pelo compilador, o que quer dizer que podemos escrever qualquer conteúdo que o mesmo não será compilado. É muito usado para deixar informações e avisos do programador no código.

```java
// TODO Auto-generated method stub
```

/**/ comentário de duas ou mais linhas (bloco)

Ao usarmos o comentário de bloco, podemos comentar não apenas um trecho de uma linha, mas todo um conjunto de linhas. É utilizado quando temos longos trechos de textos com avisos e informações, ou para a depuração do código. Para a depuração do código, podemos comentar um conjunto de linhas para realizar um conjunto de testes. Neste caso, podemos comentar um conjunto de instruções ou porque estas instruções estão com problemas e queremos verificar as demais. Ou, ao contrário, onde temos um conjunto de instruções já testadas e corretas e queremos apenas testar as demais. Seja como for, o uso do comentário de bloco é muito usual e comum entre os programadores.

/***/ comentário de documentação

O comentário de documentação se difere do comentário de bloco por possuir um asterisco a mais no início, mas ambos encerram da mesma forma. Existe uma ferramenta na linguagem Java responsável por extrair de um projeto (com várias classes) todos os comentários de documentação e montar um documento com todo este conteúdo.

Neste caso, usamos este tipo de comentário apenas para descrever avisos e informações das classes, de forma a realizar a documentação do sistema ainda durante sua fase de criação. Isto permite que o desenvolvedor descreva todo a documentação no próprio projeto, facilitando a descrição e a manutenção do sistema. Assim, ao terminar um projeto ou realizar algum tipo de modificação, basta gerar novamente a documentação do sistema que tudo estará atualizado.

# ☕ [Java] Tipos de dados
A linguagem Java possui nove tipos de dados básicos, sendo oito deles primitivos e um tipo especial. Os **tipos primitivos** (armazenam apenas valores) são os tipos básicos de variáveis que armazenam valores simples, sem a necessidade de objetos ou referências. São fundamentais para armazenar valores simples e são divididos em oito categorias:

**Tipo lógico** (`boolean`), o tipo lógico só permite dois estados, verdadeiro (true) ou falso (false); em Java ainda é permitido o uso de on e off, ou yes e no.

Exemplo:

```java
boolean status = true;
```

**Tipo caractere** (`char`), o tipo `char` permite que seja armazenado na memória apenas um caractere e se difere do texto (`String`) por ser definido entre `‘e’`. Quando usamos aspas simples ou dupla determinamos apenas um caractere.

Exemplo:

```java
char letra = ‘A’;
```

Também é possível armazenar caracteres de controle:


Caractere Especial
Representação


’\n’
nova linha.


’\r’
enter.


’\u????’
especifica um caractere Unicode o qual é representado na forma Hexadecimal.


’\t’
tabulação.


’\\’
representa um caractere \ (barra invertida).


’\” ’
representa um caractere (aspas)

> OBS: A barra invertida na frente indica que é um caractere especial.

**Tipos inteiros** (`byte`, `short`, `int` e `long`), são quatro diferentes tipos de inteiros, que se diferenciam pela quantidade de bits que cada um ocupa em memória para armazenar um valor. Isto faz com que, quanto menor a quantidade de bits, maior seja a limitação do valor a ser armazenado. Entretanto, em ocasiões onde a memória é pouca, devemos trabalhar muito bem com estas diferenças para reduzir o espaço de memória necessário. O uso mais comum é do int, mas, para números muito grandes ou muito pequenos, devemos usar o long. Já para economizar memória podemos usar byte ou short, de acordo com o valor que será armazenado.


Tipo de dado
Quantidade de bits
Quantidade de Bytes
Escopo (valores que podem ser armazenados)


byte
8
1
-2⁷ . . . . 2⁷ - 1


short
16
4
-2³¹ . . . . 2³¹ - 1


long
64
8
-2⁶³ . . . . 2⁶³ - 1

**Tipos reais** (`float` e `double`) são dois diferentes tipos de valores reais, sendo um de precisão simples (float), que ocupa menos espaço de memória, e o de dupla precisão, que ocupa mais memória. Quanto maior o número de bits para armazenar um valor real, maior será a precisão deste número dentro do sistema. O uso do float é comum quando necessitamos economizar espaço de memória. Em Java, todo tipo de dado numérico é convertido para double automaticamente por coerção (força a conversão de tipo). Por isso, é mais indicado, quando não houver falta de espaço de memória, a utilização de double para armazenamento de valores reais.


Tipo de dado
Quantidade de bits
Quantidade de Bytes


float
32
4


double
64
8

**Tipo especial** pode se referir a estruturas que vão além dos tipos primitivos e de referência comuns. Alguns exemplos incluem, `Enum` (enumeração) que define um conjunto fixo de constantes nomeadas:

```java
enum DiaDaSemana { SEGUNDA, TERÇA, QUARTA, QUINTA, SEXTA, SÁBADO, DOMINGO }
```

**Tipo texto** (String) não é um tipo primitivo, mas um tipo especial. Na verdade, o tipo String é uma classe e por isso começa com letra maiúscula, ao contrário dos tipos primitivos, que sempre começam por minúsculas. Este tipo de dado armazena um conjunto de caracteres, formando palavras ou frases de tamanhos variados. Como classe, veremos mais tarde que elementos do tipo String possuem métodos que podem realizar ações específicas sobre o seu conteúdo.

Exemplo:

```java
String nome = “João da Silva”;
```

# ☕ [Java] Constantes e variáveis
**Variáveis** e **constantes** em Java devem obrigatoriamente possuir um tipo. Isso ocorre porque Java é uma linguagem de programação fortemente tipada. Linguagens de programação fortemente tipadas Obrigam que todas as variáveis e constantes sejam definidas por um tipo de dado.

Linguagens de programação fracamente tipadas permitem que variáveis sejam usadas a qualquer momento, sem a necessidade de terem um tipo predefinido. Isso quer dizer que o tipo de dado pode variar em diferentes partes do programa.

**Variáveis** são declaradas por meio de um tipo e um identificador, sem que sejam necessárias outras informações. A boa prática em programação Java determina que todas as variáveis comecem por letras minúsculas e, somente se tiver mais de uma palavra, o inicial da segunda palavra em diante deverá começar por letras maiúsculas.

Exemplos:

```java
int c;
double nota1 = 0; // indica que a variável será inicializada com 0 (zero)
String nomeCompleto;
```

A definição de constantes precisam do modificador final, que indica que, uma vez que ocorreu uma atribuição a variável, seu conteúdo não poderá ser mudado. Em Java, constantes podem ser criadas em nomes em minúsculas ou maiúsculas, mas a boa prática de programação determina que sua identificação deve ser toda em maiúsculas.

Exemplos:

```java
final int IDADEMINIMA = 15;
final double VALORDOLAR = 3.96;
final NOMEEMPRESA = “Super Empreendimentos”;
```

# ☕ [Java] Operadores e expressões


Operador
Descrição


=
Atribuição


+
Soma


-
Subtração


*
Multiplicação


/
Divisão


%
Resto da divisão

**Operadores aritméticos** em Java são usados para realizar operações matemáticas básicas entre valores numéricos. Eles incluem:

> OBS: Java sempre realizará a operação inteira quando os operandos forem inteiros, e a operação real ocorrerá caso um ou mais operando seja real.

Exemplos:

```java
// o valor atribuído será 3 e não 3.5, porque ambos os operandos são inteiros
int v = 7 / 2;

// o valor atribuído será 3.5, porque o primeiro operando é real.
double v = 7.0 / 2;
```

A mesma lógica serve para variáveis:

```java
int a, b=7, c=2;

a = b / c; // será armazenado 3 em a.

double a, b=7.0, c=2.0;

a = b / c; // será armazenado 3.5 em a.
```

Podemos alterar o tipo de um operando em uma expressão utilizando o **cast**, que nada mais é do que informar que o valor armazenado na variável terá o seu valor em função do tipo alterado.

Exemplo:

```java
int b=7, c=2;

double a=0;

a = (double) b / c; // o valor de b será convertido para double antes da operação

// e isso fará com que o primeiro operando seja real e desta

// forma a operação será real, armazenado 3.5 em a.
```

```java
+= -= *= /= %=
```

Exemplo:

```java
int alturaParede = 2.85; // declaração da variável alturaParede

alturaParede += 0.15; // a variável alturaParede terá o valor

// acrescido (somado) em 0.15, sendo

// equivalente a:

// alturaParede = alturaParede + 0.15;
```

Desta forma, não precisamos colocar o nome da variável duas vezes.

Em Java temos os operadores de incremento ++ e de decremento --, que sempre adicionam uma unidade (++) ou subtraem uma unidade (--). Eles podem ser ainda divididos em **pré-incremento** e **pós-incremento**, e **pré-decremento** e **pós-decremento**.

O **pré-incremento** determina que primeiro seja realizada a operação de incremento e depois é realizada a operação de atribuição.

Exemplo:

```java
int a = 20, b=0;

b = ++a; // primeiro a variável a será incrementada de uma unidade, valendo 21,

// depois b receberá o valor de a e assim, também valerá 21.
```

O **pós-incremento** determina que antes seja realizada a atribuição para só então ser realizada a operação de incremento.

Exemplo:

```java
int a = 20, b=0;

b = a++; // primeiro b receberá o valor de a, que é 20 (antes do incremento),

// depois a será incrementado e assim, o valor de a será 21 e o de b será 20.
```

O **pré-decremento** determina que primeiro seja realizada a operação de decremento e depois é realizada a operação de atribuição.

Exemplo:

```java
int a = 20, b=0;

b = --a; // primeiro a variável a será decrementada de uma unidade, valendo 19,

// depois b receberá o valor de a e assim, também valerá 19.
```

O **pós-decremento** determina que antes seja realizada a atribuição para só então ser realizada a operação de decremento.

Exemplo:

```java
int a = 20, b=0;

b = a--; // primeiro b receberá o valor de a, que é 20 (antes do incremento),

// depois a será decrementado e, assim, o valor de a será 19 e o de b será 20.
```

**Operadores de Relacionais** são usados para definir condições.


Operador
Descrição


==
Igualdade/ Comparação


!=
Negação/ Diferente


>
Maior que


<
Menor que


>=
Maior ou igual a


<=
Menor ou igual a

Exemplos:

```java
/*1)*/ if(a > b) { ... }

/*2)*/ while (a <=100) { ... }

/*3)*/ for (int c =0; c<50; c++) { ... }
```

Operadores de em Expressões Lógicas


Operador
Descrição


!
NÃO lógico


&&
E lógico


||
OU lógico

São os determinantes das tabelas-verdade.

Ordem de precedência: `!, &&, ||`

Exemplos:

```java
if(a > b && c < d) { ... }

while (a <=100 || b == 10) { ... }

if( !a == 15 && b >= 10) { ... }

if( !a == 15 || c > d && b >= 10) { ... }
```

Pela ordem de precedência: `if( (!a == 15) || (c > d && b >= 10))`

Primeiro será executada a negação (`!`); depois o e lógico (`&&`) e por último o ou lógico (`||`).

Operadores de bits


Operador
Descrição


&
E entre bits


^
OU EXCLUSIVO entre bits


|
OU entre bits

Ordem de precedência:


  1. &

  2. ^

  3. |

# ☕ [Java] Estruturas de programação
Comandos de controle de fluxo Servem para determinar se as condições são verdadeiras ou controlar uma determinada ordem lógica de eventos para o código.

## [Java] Estrutura condicional

Se (`if`):

```java
if (condição) {

// instruções;

}

// A cláusula else é opcional.
```

`if ... else`:

```java
if (condição) {

// instruções;

}

else {

// instruções;

}
```

`if ... else if ... else`

```java
if (condição1) {

instruções;

}

else if (condição2) {

instruções;

}

else if (condição3) {

instruções;

}

else {

instruções;

}
```

> A cláusula `if` deve ocorrer apenas uma vez; As cláusulas `else if` podem ocorrer: nenhuma, uma ou várias vezes; A cláusula `else` só pode ocorrer uma única vez.

switch … case

Estruturas de decisão caracterizadas pela possibilidade de uma variável possuir vários valores diferentes em uma determinada situação.

Uma única estrutura switch pode analisar vários diferentes valores para a variável de controle. A variável de controle em Java pode ser do tipo: inteiro, caractere, ou String.

A cláusula case pode ocorrer de uma a várias vezes, e a cláusula default é opcional.

https://medium.com/javarevisited/re-write-this-java-if-else-code-block-or-ill-reject-your-pull-request-953f20c0f544

## [Java] Estrutura de Laços de Repetição (Loops)
`for` é uma estrutura de repetição (Laços de repetição ou Loop) controlada por uma ou mais variáveis contadoras e caracterizada pela existência de três parâmetros, sendo todos eles opcionais:

**1** - Inicialização da(s) variável(is) de controle

**2** - Condição sobre a(s) variável(is) de controle para parada das iterações

**3** - Passo da(s) variável(is) de controle a cada iteração

```java
for((1)inicialização; (2)condição de controle; (3)passo) {

// instruções

}
```

Exemplo:

1) Repetição controlada por uma variável:

```java
for (int c=1; c<=limite; i++) {

instruções;

}
```

2) Repetição controlada por duas variáveis:

```java
for (a=1, b=2; a*b<limite; a++, b+=2) {

instruções;

}
```

3) Repetição sem fim

```java
for ( ; ; ) {

instruções;

}
```

`while`, esta estrutura realiza a repetição de um conjunto de instruções enquanto a condição determinada for verdadeira; caso a condição seja falsa no primeiro teste, nenhuma instrução será executada.

```java
// realiza o teste da condição no início da estrutura

while (condição) {

instruções;

}
```

`do...while` esta estrutura de repetição é semelhante à anterior, mas com o diferencial de que as condições devem ser verificadas apenas no final da estrutura, obrigando que as instruções sejam sempre executadas pelo menos uma vez.

```java
// Teste de condição no final

do

{

instruções;

} while (condição);
```

# ☕ [Java] Entrada e Saída de dados
Em Java temos muitas formas de **entrada de dados** (input), inclusive de forma gráfica. Inicialmente trabalharemos com a *classe* Scanner, responsável pela entrada de dados em formato texto, com perguntas diretas ao usuário e a inclusão da resposta em variáveis do programa.

Para realizarmos esta tarefa, é necessário que seja criado um objeto da classe `Scanner`.

Para isso, devemos **importar** a *classe* Scanner antes do início da programação da classe:

```java
import java.util.Scanner;
```

Depois é necessário criar o **objeto** para realizar as entradas de dados:

```java
public class EntradaDados {
public static void main (String[] args) {
Scanner entrada = new Scanner(System.in);
}
}
```

Existem vários métodos associados a classe `Scanner` para a entrada de dados, mas para evitarmos problemas futuros podemos usar sempre a entrada de dados de texto (nextLine()) e converter o texto para o tipo desejado.

Exemplo:

1) Para entrada de texto (String):

```java
String nome;

Nome = entrada.nextLine(); // não precisa de conversão, apenas da entrada.
```

2) Para entrada de valor real:

```java
double nota1;

nota1=Double.parseDouble(entrada.nextLine());

// a entrada de dados em texto precisa de conversão para double.
```

3) Para entrada de valor inteiro:

```java
int idade;

idade = Integer.parseInt(entrada.nextLine());

// a entrada de dados em texto precisa de conversão para int
```

É aconselhável evitar o uso de métodos como:

- `entrada.nextDouble();`
- `entrada.nextFloat();`
- `entrada.nextInt();`

Estes métodos, quando usados em conjunto, podem fazer com que a aplicação pule alguma entrada de dados, sendo necessário que seja realizada uma “limpeza de buffer”. Este tipo de problema pode ser contornado ao usar sempre o método “`nextLine()`” e a conversão de tipos.

A **saída de dados** (output) em modo texto pode ser realizada pela *classe* System, e o método out.print (não pula linha), out.println (pula linha) ou outros métodos:

1) Apenas uma mensagem:

```java
System.out.println("Entre com a Nota A1.........: ");
```

2) Mensagem e conteúdo de variáveis:

```java
System.out.println(" Nome: " + nome + " Idade: " + idade + " Nota 1: " + nota1);
```

A seguir temos o exemplo completo de um programa que recebe duas notas e apresenta a média.

```java
public class Exemplo {

public static void main(String[] args) {

// TODO Auto-generated method stub

Scanner sc = new Scanner(System.in);

double media, nota1, nota2;

System.out.println("Digite a nota 1:");

nota1 = Double.parseDouble(sc.nextLine());

System.out.println("Digite a nota 2:");

nota2 = Double.parseDouble(sc.nextLine());

media = (nota1 + nota2) /2.0;

System.out.println("A sua média é:" + media);

sc.close();

}

}
```

## [Java] Conversão de tipos
A conversão de tipos em Java pode ser feita por cast ou com o uso de conversão por classes. Ambos já foram vistos em exemplos anteriores:

1) Por cast: Usado para converter valores de um tipo para outro; com cast basta indicar o tipo que você quer que a variável, ou valor, seja convertida, colocando o tipo desejado entre parênteses:

2) Por uso de classes para conversão de textos em valores:

```java
double nota1 = Double.parseDouble(“7.8”);

int idade = Integer.parseInt(“34”);

float valor = Float.parseFloat(“2.15”);

long valor2 = Long.parseLong(“3456789”);

Pode-se usar ainda:

Byte.parseByte() / Short.parseShort()
```

# ♨️ [Java] A boa prática em programação Java

A boa prática em programação Java leva em conta um conjunto de regras que facilitam o desenvolvimento de aplicações e melhoram bastante o trabalho em conjunto realizado por equipes. Ao seguir estas regras, projetos podem ser desenvolvidos em paralelo por diferentes programadores, sem que seja necessário que cada componente precise esperar que outros terminem suas tarefas. Estas regras foram utilizadas na construção da linguagem permitindo que não seja necessário decorar as sintaxes de instruções Java. Não são obrigatórias, mas permitem a codificação melhor de nossas aplicações. Vamos ver algumas dessas regras na prática:

1 - Variáveis auxiliares, atributos, métodos e objetos devem ser identificados iniciando por letras minúsculas. Quando houver mais de uma palavra, deve-se começar cada nova palavra com uma letra maiúscula.

Exemplos:

```java
int idade;

int maiorIdade;

String nome;

String nomeCompleto;
```

2 - Constantes devem ser identificadas por letras maiúsculas em todo o seu nome; mesmo quando temos mais de uma palavra, todo o identificador deve ficar em maiúsculas.

Exemplos:

```java
final int idade;

final int maiorIdade;

final String nome;

final String nomeCompleto;
```

3 - Classes e interfaces (tipo especial de classe) devem iniciar por letras maiúsculas. Quando houver mais de uma palavra, deve-se começar cada nova palavra com uma letra maiúscula.

Exemplos:

```java
public class Carro { ... }

public class Carro Hibrido { ... }

public interface Basico { ... }

public interface MetodosBasicos { ... }
```

4 - Técnicas Básicas de Economia de Memória para Programação em Java: Melhores Práticas para Otimizar Código Java e Reduzir o Uso de Memória. Java é uma linguagem de programação popular amplamente utilizada para desenvolver aplicações complexas. No entanto, um dos problemas comuns dos programas Java é o alto uso de memória, que pode levar a problemas de desempenho e até mesmo travamentos. Portanto, é importante usar técnicas de economia de memória para otimizar o código Java e reduzir o uso de memória.

Uso de tipos de dados primitivos: Ao usar tipos de dados primitivos em vez de seus wrappers de objetos, podemos economizar memória evitando a sobrecarga da criação de objetos.

```java
int x = 42; // use int instead of Integer
double d = 3.14; // use double instead of Double
boolean b = true; // use boolean instead of Boolean
```

Evite a criação desnecessária de objetos:

```java
String s = "Hello" + " World"; // use StringBuilder instead
```

Essa linha de código cria um objeto de `new string` para a `string` concatenada `"Hello World"`. Em vez disso, podemos usar um `StringBuilder` para anexar as strings e evitar criar um novo objeto para cada concatenação:

```java
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" World");
```

# ☕ [Java] Classes e objetos

A **programação orientada a objetos (OOP)** tem como principal conceito representar, em um sistema computacional, um objeto da vida real. Esta representação deve descrever o objeto quanto às suas características e ações que poderá realizar dentro do sistema. Não devemos nos preocupar com todas as características presentes no objeto, mas com aquelas que serão necessárias ao sistema (requisitos).

Exemplo: A placa de um automóvel é importante para um sistema de estacionamento, assim como a hora de chegada e saída.

Em alguns casos, o `fabricante`, `modelo` e a `cor` do automóvel poderão ser importantes, mas dificilmente iremos cadastrar o número do `chassi` do mesmo. Como o número do `chassi` não é facilmente visto e seu cadastramento dependeria da documentação do automóvel ou de uma análise para a identificação, que seria difícil, uma vez que é um identificador com muitas letras e números, acabaria por gerar filas e insatisfação dos clientes.

Entretanto, para o sistema de cadastramento do `DETRAN`, por exemplo, o número do `chassi` é uma das informações mais importantes. Dessa forma, identificarmos a `placa` é importante como descritor do automóvel para o sistema de estacionamento, já o `chassi` não.

Por isso, devemos analisar cada objeto separadamente e quais são as características importantes para o sistema em que o objeto será utilizado. Como outro exemplo, podemos notar que a `matrícula`, `nome` e `CR` de um `aluno` são importantes para o sistema acadêmico, mas o time para o qual o `aluno` torce ou sua religião não são. Por isso, os descritores time e religião não são importantes para o objeto `Aluno` em um sistema acadêmico.

As **classes** Java são responsáveis pelo **conjunto de códigos para a criação de objetos e aplicações**. Uma classe Java deve descrever as *características e ações* que o objeto possui ao ser representado em um sistema computacional, levando em consideração as caracteristicas (atributos) e as ações (métodos) juntamente ou não com seus argumentos (parâmetros).

**Atributo** é conceitualmente um descritor do objeto e deve representar uma **característica**, dele. O **conjunto de atributos** do objeto deve representar todas as *características importantes* do objeto para o sistema.

Exemplo:

```java
String matricula; // atributo para armazenamento da matrícula

String nome; // atributo para armazenamento do nome

double cr; // atributo para armazenamento do cr
```

**Método** é uma **ação**, um conjunto de instruções a serem executadas por um objeto para realizar uma determinada tarefa.

O **conjunto de métodos** de um objeto deve descrever **todas as ações (tarefas ou funções)** que o objeto poderá realizar dentro do sistema.

Exemplo:

```java
public int soma(int n1, int n2){

int soma;
soma = n1 + n2;

return soma;

}

public void imprimeAumento(double salario, int percentual){

double aumento;
aumento = salario + salario * percentual / 100.0;
System.out.println("O salário com aumento é: " + aumento);

}
```

A classe modela o **objeto** de acordo com as necessidades do sistema para a sua descrição e suas ações. A partir de uma mesma classe, vários objetos diferentes, mas com características semelhantes, podem ser criados em um mesmo sistema ou em diferentes sistemas.

Se consideramos a classe Aluno, podemos criar a partir desta classe dezenas, centenas ou mesmo milhares de objetos Alunos com características semelhantes, tais como matrícula, nome e CR, mas com propriedades (valores próprios nos atributos de cada objeto) diferentes.

Os *objetos* só existem durante a execução do sistema, pois estes só existirão como referência na memória do computador neste momento. Dizemos também que os objetos só existem “em tempo de execução”, uma vez que o sistema ao ser encerrado terá toda a sua memória apagada. Consequentemente, todas as suas variáveis e objetos não existirão mais.

Exemplo: `Aluno.java` (objeto com o nome da classe)

```java
// Classe Aluno
public class Aluno { // declaração e início da classe

// Atributos devem ser identificados começando por letras minúsculas
String matricula, nome;
double cr;

// Métodos devem ser identificados começando por letras minúsculas
public void imprimir() {
System.out.println("Matrícula: " + matricula);
System.out.println("Nome: " + nome);
System.out.println("CR: " + cr);
}
} // termino da classe
```

# ☕ [Java] Aplicações Java

**Aplicações em Java** são classes especiais que possuem um método main(). O método `main` é responsável por criar os objetos e realizar a combinação de diferentes classes para atender às necessidades de um sistema.

Em cada sistema, temos apenas uma aplicação, que será responsável pela lógica de criação e uso das classes. A comunicação entre os objetos ocorre por meio de trocas de mensagens, que são expressas com o uso de métodos. Uma aplicação, então, cria objetos a partir de uma ou mais classes e usa os métodos dos objetos para realizar as ações que atenderão às necessidades dos usuários.

Exemplo: `AppAluno.java` (aplicação)

```java
// Aplicação para uso da Classe Aluno
public class AppAluno { // Declaração e início da classe
public static void main(String[] args){ // Método inicial da App
Aluno aluno1 = new Aluno();
Aluno aluno2 = new Aluno();
Aluno aluno3 = new Aluno();

// Definindo valores para os atributos do aluno1
aluno1.matricula = "1001";
aluno1.nome = "André";
aluno1.cr = 6.7;

// Definindo valores para os atributos do aluno2
aluno2.matricula = "1002";
aluno2.nome = "Maria";
aluno2.cr = 7.5;

// Definindo valores para os atributos do aluno3
aluno3.matricula = "1003";
aluno3.nome = "João";
aluno3.cr = 7.0;

// Exibindo os valores dos atributos de cada aluno
aluno1.imprimir();
aluno2.imprimir();
aluno3.imprimir();
}
}
```

> [!Note]
> 1. Cada classe pública (`public`) deve ser criada em um arquivo próprio e o nome da classe deve ser o mesmo do arquivo. Ou seja, a classe Aluno deve ser criada no **arquivo** Aluno.java e a **classe da aplicação** "`AppAluno`" deve ser criada no arquivo AppAluno.java. No projeto, seja no Eclipse ou no Netbeans, deverão ser criadas duas classes, uma para o `Aluno` e outra para a aplicação;
>
> 2. Foi criada apenas uma classe `Aluno`, mas a partir dela poderemos criar quantos objetos quisermos;
>
> 3. Na aplicação foram criados três diferentes objetos do tipo `Aluno`. Isso faz com que cada **objeto** `Aluno` (`aluno1`, `aluno2` e `aluno3`) seja criado na memória em locais diferentes (endereços) e possuam espaço de alocação de memória diferentes para cada atributo de cada objeto;

4. Cada *objeto* criado é independente do outro e possui valores próprios para os seus atributos (propriedades). Como a ação é realizada pelo objeto, cada método fará a ação sobre os atributos do objeto indicado, evitando que haja qualquer tipo de alteração indevida nos valores de cada um.

![img02](https://user-images.githubusercontent.com/61624336/119909388-16166f00-bf2b-11eb-88c1-15bbf2b15e8e.jpg)

5. Operador new serve para criar um novo objeto e instancia-lo com (), portanto, é um método construtor.

```java
nome_da_classe nome_do_objeto = new método_construtor();
```

A classe `Aluno` passou a ser uma **biblioteca de classes**, e esta classe pode ser reutilizada em diversas outras aplicações. Esse conceito é um dos mais importantes na programação orientada a objetos, pois reduz o trabalho. Qualquer classe criada poderá ser reaproveitada inúmeras vezes por diversas aplicações, poupando esforço de desenvolvimento e facilitando a manutenção.

Cada classe criada se torna uma parte da sua biblioteca de classes e, conforme você vai criando novas classes, a sua biblioteca tende a aumentar. Dessa forma, quando você for criar novas aplicações, terá à sua disposição uma séria de classes já prontas e disponíveis para reaproveitar, sem precisar de novas.

Se você precisar realizar qualquer melhoria em uma classe da sua biblioteca, você poderá realizar sem problemas, pois qualquer inclusão não afetará o uso desta classe nas aplicações antigas, mantendo a compatibilidade entre todas as aplicações.

Com base no reaproveitamento de código da programação orientada a objetos, podemos realizar alterações de melhoria, atualização ou qualquer manutenção em uma classe. Isso fará com que todas as aplicações sejam atualizadas quando forem recompiladas.

# ☕ [Java] Métodos Getters e Setters

Por questões de segurança e falta de controle, não é comum realizar acessos diretos aos atributos de um objeto, por isso são criados métodos específicos para receber o valor e realizar a **atribuição (Setters)**, ou para a **recuperação (Getters)** de um valor armazenado nos atributos de um objeto. Este processo pode evitar que valores incorretos sejam atribuídos sem qualquer chance de análise.

**Métodos Setters** são métodos especiais que recebem o valor do atributo e, por serem métodos, podem analisar se são válidos, sendo responsáveis pela atribuição. Quando o atributo é protegido (privado), é necessário um método para realizar a atribuição.

Características dos métodos Setters:

- São sempre do tipo void, pois métodos Setters não devem retornar nada;

- Devem ser públicos para que a aplicação tenha acesso ao método;

- Devem começar pela palavra set e o nome do atributo: como tem mais de uma palavra, cada nova palavra no nome deve começar por letra maiúscula;

- Possui sempre um parâmetro do mesmo tipo do atributo que receberá o valor, pois ambos (parâmetro e atributo) devem ser do mesmo tipo.

A verificação do valor a ser atribuído não pode ser realizada quando efetuamos uma atribuição direta:

```java
Aluno a = new Aluno();

a.cr = -5.0;
```

O uso de um método Setter neste caso evitará que seja atribuído um valor inválido para o CR, no caso `-5.0`;

Exemplo: `Aluno.java` Setter = `Aluno.class`

```java
// Classe Aluno
public class Aluno { // Declaração e início da classe
// Atributos devem ser identificados começando por letras minúsculas
String matricula, nome;
double cr;

// Métodos devem ser identificados começando por letras minúsculas
public void imprimir() { // Método imprimir(os dados do Aluno)
System.out.println("Matrícula: " + matricula);
System.out.println("Nome: " + nome);
System.out.println("CR: " + cr);
}

// Setter
public void setCr(double c){
if(c >= 0.0 && c <= 10.0){
cr = c;
}
}
}
```

Exemplo: `AppAluno.java` (aplicação chamando o Setter) - `AppAluno.class`

```java
// Aplicação para uso da Classe Aluno
public class AppAluno { // Declaração e início da classe

public static void main(String[] args){ // Método inicial da App
Aluno aluno1 = new Aluno();
Aluno aluno2 = new Aluno();
Aluno aluno3 = new Aluno();
Aluno a = new Aluno();

// Definindo valores para os atributos do aluno1
aluno1.matricula = "1001";
aluno1.nome = "André";
aluno1.cr = 6.7;

// Definindo valores para os atributos do aluno2
aluno2.matricula = "1002";
aluno2.nome = "Maria";
aluno2.cr = 7.5;

// Definindo valores para os atributos do aluno3
aluno3.matricula = "1003";
aluno3.nome = "João";
aluno3.cr = 7.0;
a.cr = -5.0;

// Exibindo os valores dos atributos de cada aluno
aluno1.imprimir();
aluno2.imprimir();
aluno3.imprimir();
a.setCr(-5.0); // setter
}
}
```

- Note que o parâmetro c recebe o valor a ser atribuído ao CR (`-5.0`), mas antes de atribuir é realizada uma verificação do valor para averiguar se o mesmo é válido. No caso, o valor do parâmetro é menor do que zero.

- Como sabemos que um CR não pode ser negativo, a atribuição não será realizada, assim como a tentativa de realizar a atribuição de um CR maior do que `10` (dez) também não permitirá que a atribuição ocorra.

Apenas atribuições com valores válidos poderão ser realizadas neste caso.

O **Métodos Getters** são métodos especiais que retornam o valor armazenado no atributo, evitando acesso direto a ele pela aplicação. Assim como visto no método Setter, a proteção do atributo (`private`) fará com que a aplicação não tenha acesso direto a ele, fazendo com que seja necessário um método público para recuperar o valor atribuído ao mesmo.

Características dos métodos Getters:

- São sempre do mesmo tipo do atributo que será retornado, nunca do tipo void;

- Devem ser públicos para que a aplicação tenha acesso ao método;

- Devem começar pela palavra get e o nome do atributo: como tem mais de uma palavra, cada nova palavra no nome deve começar por letra maiúscula;

- Não possui parâmetro: esses métodos nunca receberão parâmetros, uma vez que não farão atribuições ou ações com parâmetros, realizando apenas o retorno do valor armazenado no atributo.

Exemplo: `Aluno.java` Getter

```java
// Classe Aluno
public class Aluno { // Declaração e início da classe
// Atributos devem ser identificados começando por letras minúsculas
String matricula, nome;
double cr;

// Métodos devem ser identificados começando por letras minúsculas
public void imprimir() { // Método imprimir(os dados do Aluno)
System.out.println("Matrícula: " + matricula);
System.out.println("Nome: " + nome);
System.out.println("CR: " + cr);
}

// Setter
public void setCr(double c){
if(c >= 0.0 && c <= 10.0){
cr = c;
}
}

// Getter
public double getCr() {
return cr;
}

}
```

Note que não existe parâmetro, o método apenas deve retornar o valor armazenado e por isso não pode ser void, sendo o tipo de retorno do mesmo tipo do atributo que será retornado, e a ação é a de retorno (`return`).

No futuro, os atributos das nossas classes serão protegidos contra acesso direto (privado), impedindo que a aplicação possa acessar diretamente um atributo. Dessa forma, é necessário que usemos os métodos *Setters* e *Getters* para atribuir e recuperar os valores do atributo.

Exemplo: `Aluno.java` (versão com métodos Setters e Getters.)

```java
public class Aluno {
// Atributos devem ser identificados começando por letras minúsculas
String matricula, nome;
double cr;

// Métodos também
public void setMatricula(String m){
if(!m.isEmpty()) { // se o parâmetro m NÃO (!) estiver vazio
matricula = m; // será feita a atribuição
}
}

public String getMatricula() {
return matricula; // retorna a matricula
}

public void setNome(String n){
if(!n.isEmpty()){ // se o parâmetro n NÃO (!) estiver vazio
nome = n; // será feita a atribuição
}
}

public String getNome(){
return nome; // retorna o nome
}

public void setCr(double c){
if(c >= 0 && c <= 10) {
cr = c;
}
}

public double getCr() {
return cr; // retorna o CR
}

public void imprimir() { // Método imprimir(os dados do Aluno)
// Os Getters usados aqui!
System.out.println("Matrícula: " + getMatricula());
System.out.println("Nome: " + getNome());
System.out.println("CR: " + getCr());
}
}
```

Exemplo: `AppAluno.java` (nova versão)

```java
// Aplicação para o uso da Classe Aluno
public class Aluno {
public static void main(String[] args) {
Aluno aluno1 = new Aluno(); // Criação da nova instância do objeto aluno1 (instanciação)
Aluno aluno2 = new Aluno(); // Criação da nova instância do objeto aluno2 (instanciação)
Aluno aluno3 = new Aluno(); // Criação da nova instância do objeto aluno3 (instanciação)

// Definindo valores para os atributos do aluno1
aluno1.setMatricula("1001");
aluno1.setNome("André");
aluno1.setCr(6.7);

// Definindo valores para os atributos do aluno2
aluno2.setMatricula("1002");
aluno2.setNome("Isaac");
aluno2.setCr(7.7);

// Definindo valores para os atributos do aluno3
// Valores vazios e inválidos, não serão atribuídos
aluno3.setMatricula("");
aluno3.setNome("");
aluno3.setCr(12);

// Exibição dos valores dos atributos de cada aluno
aluno1.imprimir();
aluno2.imprimir();
aluno3.imprimir();
}
}
```

> [!Note]
>
> 1. Os valores dos atributos dos alunos `1` e `2` serão atribuídos normalmente, mas os valores do `aluno3` não, porque a `matrícula` e o `nome` estão vazios e o `CR` não é válido;
>
> 2. Os valores foram atribuídos utilizando os métodos Setters, que verificaram se os valores eram válidos para só então realizar as atribuições;
>
> 3. Os métodos Getters foram usados na própria classe `Aluno` para buscar os valores armazenados nos atributos do objeto no método `imprimir`.

Exemplo: A classe `Carro` possui os atributos e métodos a seguir, crie a classe `Carro` e a aplicação `AppCarro`, realize a entrada de dados na aplicação através do teclado, e ao final imprima os dados dos respectivos carros (através do método imprimir()).

Classe `Carro`:

Atributos
Métodos

Fabricante: texto
- Setters para todos os atributos

Modelo: texto
- Getters para todos os atributos

Cor: texto
- Imprimir() // imprime todos os atributos

Placa: texto

Valor: real

Número de Portas: inteiro

Ano de fabricação: inteiro

Ano do Modelo: inteiro


`Carro.java`

```java
public class Carro {
// Attributes
String fabricante, modelo, cor, placa;
double valor;
int numeroPortas, anoFabricacao, anoModelo;

// Setter methods
public void setFabricante (String fab){
if(!fab.isEmpty()) {
fabricante = fab;
}
}
public void setModelo (String mad){
if(!mod.isEmpty()) {
modelo = mod;
}
}
public void setCor (String c){
if(!c.isEmpty()) {
cor = c;
}
}
public void setPlaca (String p){
if(!p.isEmpty()) {
placa = p;
}
}
public void setValor (String val){
if(!val.isEmpty()) {
valor = val;
}
}
}
```

`AppCarro.java`

```java
import java.util.Scanner;
// Scanner para imprimir os dados

public class AppCarro {
public static void main (String[] args){
Scanner entrada = new Scanner(System.in);
Carro car1 = new Carro();

// Entrada de Dados
System.out.println("---------- Entrada de Dados - Carro ----------");

// Fabricante
System.out.println("Digite o Fabricante do Carro: ");
car1.setFabricante(entrada.nextLine());

// Modelo
System.out.println("Digite o Modelo do Carro: ");
car1.setModelo(entrada.nextLine());

// Placa
System.out.println("Digite a Placa do Carro: ");
car1.setPlaca(entrada.nextLine());

// Cor
System.out.println("Digite a Cor do Carro: ");
car1.setCor(entrada.nextLine());

// Valor
System.out.println("Digite o Valor do Carro: ");
car1.setValor(Double.parseDouble(entrada.nextLine()));

// Número de Portas
System.out.println("Digite o Nº de Portas do Carro: ");
car1.setNumeroPortas(Integer.parseInt(entrada.nextLine()));

// Ano de Fabricação
System.out.println("Digite o Ano de Fabricação do Carro: ");
car1.setAnoFabricacao(Integer.parseInt(entrada.nextLine()));

// Ano do Modelo
System.out.println("Digite o Ano do Modelo do Carro: ");
car1.setAnoModelo(Integer.parseInt(entrada.nextLine()));

// Saída de Dados
System.out.println("--------------- Saída de Dados - Carro ---------------");
car1.imprimir();
}
}
```

> [!Note]
> Você pode ver que temos algumas repetições de código para realizar a entrada de dados de cada objeto. Se aumentarmos o número de objetos, aumentaremos consideravelmente o tamanho do código. Para resolver este problema e evitarmos a redundância de códigos, vamos incluir um novo método na classe `Carro`, um método para a entrada de dados. Desta forma, evitamos a redundância dos códigos de entrada de dados.

Classe `Carro`


Atributos
Métodos


Fabricante: texto
- Setters para todos os atributos


Modelo: texto
- Getters para todos os atributos


Cor: texto
Imprimir() // imprime todos os dados do carro


Placa: texto
- EntradaDados () // realiza a entrada de dados do carro


Valor: real


Valor: real


NumeroPortas: inteiro


AnoFabricacao: inteiro


AnoModelo: inteiro

Nova solução do exercício prático, com a inclusão do método `entradaDados` na classe `Carro`:

`Carro.java`

```java
import java.util.Scanner;

public class Carro {
// Usando regras de boas práticas em Java
// Para identificadores da classe, dos atributos e dos métodos

String fabricante, modelo, cor, placa;
double valor;
int numeroPortas, anoFabricacao, anoModelo;

// Getters e Setters Fabricante
public String getFabricante(){
return fabricante;
}
}
```

`AppCarro.java`

```java
public class AppCarro {
public static void main (String[] args){
// TODO Auto-generated method stub
Carro car1 = new Carro();
car1.entradaDados();
car1.imprimir();

Carro car2 = new Carro();
car2.entradaDados();
car2.imprimir();

Carro car3 = new Carro();
car3.entradaDados();
car3.imprimir();
}
}
```

> [!Note]
> Você pode perceber agora que existe um método para a entrada de dados na classe `Carro`, e que ele está sendo usado por cada carro para realizar a entrada de dados pelo teclado, evitando que os códigos das entradas de dados fiquem redundantes.
> Além disso, a aplicação ficou muito mais simples. Caso você tenha vários objetos carros, você não terá redundância, portanto sua aplicação ficará mais simples.
> Faça um teste executando a nova aplicação e analise o resultado. Inclua mais dois objetos carros e teste novamente: você verá que a aplicação terá uma pequena mudança, mas a classe Carro ficará inalterada.
> A partir deste momento, todas as classes deverão sempre conter o método entradaDados().

## [Java] Métodos construtores

A programação orientada a objetos permite que possamos controlar a criação de um objeto através dos chamados **métodos construtores**. Tal característica permite que um método especial, o método construtor, seja executado no momento em que ocorre a criação do objeto (objeto é instanciado) e um conjunto de ações (instruções) podem ser programadas para serem realizadas neste momento.

Entre essas ações, pode-se destacar o recebimento de dados iniciais para serem atribuídos e/ou preparar o objeto para que este esteja apto a atender às necessidades para qual foi criado.

Um método construtor pode ainda ser usado para determinar o tamanho de um vetor que será usado pelo objeto, assim como *pré-configurar estruturas de dados de suporte* ao objeto que está sendo criado.

São métodos especiais executados apenas uma vez por cada *objeto* criado, pois somente são executados no momento da **instanciação / criação do objeto**, sendo responsáveis por realizar as ações necessárias para a sua criação (controlar a criação do objeto).

Características dos métodos construtores:

1. São sempre públicos (`public`, característica de encapsulamento – veremos mais adiante), não podendo ter nenhum tipo de restrição;

2. Não existe definição de tipo de retorno, pois métodos construtores não podem retornar valores com a instrução “`return`”, são sem tipo;

3. Devem ser identificados sempre com o mesmo nome da classe;

4. São executados exclusivamente durante o processo de criação / instanciação do objeto, não podendo ser usados pelo objeto após a sua criação.

`Pessoa.java`

```java
public class Pessoa {
// Attributes
String nome, identidade;
int idade;

// Constructor method
public Pessoa(String nome, String identidade, int idade) {
// Setters e Attributes
setNome(nome);
setIdentidade(identidade);
setIdade(idade);
}

// Getters and Setters - Nome
public String getNome(){
return nome;
}

public void setNome(String no) {
if(!no.isEmpty()) {
nome = no;
}
}

// Getters and Setters - Identidade
public String getIdentidade(){
return identidade;
}

public void setIdentidade(String iden) {
if(!iden.isEmpty()) {
identidade = iden;
}
}

// Getters and Setters - Idade
public String getIdade(){
return identidade;
}

public void setIdade(String ida) {
if(ida > 0) {
idade = ida;
}
}

// Método imprimir()
public void imprimir() {
System.out.println("Pessoa: " + nome);
System.out.println("RG: " + identidade);
System.out.println("Idade: " + idade);
}
}
```

`AppPessoa.java`

```java
public class AppPessoa {
public static void main(String[] args) {
Pessoa p1 = new Pessoa("Isaac", "7", 24);

p1.imprimir();
}
}
```

1. Na classe `Pessoa`, o método construtor:

```java
public Pessoa(String nome, String identidade, int idade)
```

O método é público, não possui tipo de retorno antes no nome identificador do método, seu identificador é igual ao nome da classe, por isso começou por letra maiúscula e só será usado para criar o objeto (instanciar);

2. A partir do momento em que um método construtor é criado, a classe só poderá ser instanciada se usarmos um método construtor existente. Por isso, o objeto Pessoa p1 não pode ser criado e sua criação foi comentada na aplicação, pois este método tenta utilizar um método construtor que não existe na classe;

3. O objeto `p2` usa um método construtor existente e por isso pode ser criado;

4. Com o uso do método construtor, os dados recebidos como parâmetros puderam ser utilizados para realizar as atribuições nos atributos do objeto, determinando os valores de suas propriedades no momento da criação do objeto;

5. Os métodos `setIdentidade` (`String id`) e `setIdade` (`int id`) podem ter o mesmo identificador para o parâmetro porque o parâmetro `id` é declarado em diferentes métodos e, sendo assim, ele é válido internamente em cada um dos métodos separadamente.

O processo de compilação de uma classe cria um método construtor vazio quando não for encontrado nenhum método construtor. Desta forma, nos exemplos anteriores, as classes `Aluno` e `Carro` não tinham métodos construtores, então o compilador criou respectivamente os métodos a seguir para as classes `Aluno` e `Carro`:

```java
public Aluno ( ) { }

// e

public Carro ( ) { }
```

Quando não temos um construtor em uma classe, um construtor VAZIO é criado no processo de compilação.

# ☕ [Java] Polimorfismo

**Polimorfismo** quer dizer muitas formas. O **polimorfismo de sobrecarga** permite o emprego de operadores e identificadores de várias formas, sendo então necessária uma contextualização para que seja realizada a operação adequada. Este contexto está ligado ao emprego do operador, método etc., de acordo com uma situação.

Polimorfismo de sobrecarga de operadores:

```java
int b=5, c=3;

int a = b + c;
// o símbolo de + neste contexto está realizando uma operação

// de soma entre valores inteiros, no caso b + c (inteiros)

double x=3.5, y=2.2;

// temos um novo contexto, pois a operação será realizada entre dois valores reais, no caso x+y
double z = x + y;
```

A mudança de contexto faz com que as operações a serem realizadas sejam diferentes, pois toda linguagem de programação possui diferentes formas de realizar as operações de soma inteira e real. Desta forma, a expressão aritmética a seguir utiliza as duas operações conjuntamente:

```java
double z = ( 2 + 5) / (3.5 + 1.5);
```

Na primeira operação de soma, os operandos são inteiros, então a operação a ser realizada será de uma soma inteira, para somente depois ser realizada a operação de soma real. Desta forma, teremos em um instante a seguinte situação:

```java
double z = ( 7) / (5.0);
```

Assim, a operação de divisão será real e não inteira porque existe um operando real.

Em Java, todas as operações aritméticas serão realizadas em função dos tipos dos operandos, e a operação será inteira apenas se ambos os operandos foram inteiros. Caso contrário (um operando inteiro e outro real ou dois operandos reais), a operação será real.

O operador + é um dos mais usados, sendo um bom exemplo de **sobrecarga de operadores**, pois pode ser utilizado de várias e diferentes formas em função do contexto:

1. Concatenação: String nome = "João" + " da " + "Silva";
2. Soma inteira: int a = 3 + 4;
3. Soma real: double b = 1.3 + 2.7;
4. Incremento: x++; ou ++x;
5. Concatenação entre textos e valores: System.out.println("Idade" + p2.getIdade());

Agora imagine a seguinte instrução:

```java
System.out.println("Valor =" + (( 3 + 4) + (1.3 + 2.7) + (++x)));
```

A *sobrecarga de operadores* está sendo usada de diferentes formas em uma mesma instrução. Cada contexto será avaliado para que seja executada a operação adequada em cada caso.

Toda expressão é avaliada sintaticamente, assim como cada contexto será avaliado individualmente no momento da execução.

O **Polimorfismo de sobrecarga de métodos** permite que possamos ter mais de um método com o mesmo identificador em uma mesma classe. Isso só é possível em razão da avaliação do contexto no momento da execução. Vamos levar em consideração que eu desejo realizar o cálculo da área de um quadrado e de um retângulo em uma mesma classe.

Para realizar o cálculo da área do quadrado, eu preciso apenas do valor da base do quadrado. Assim, o método área ficaria da seguinte forma:

```java
public int area (int base){
return ((int) Math.pow(base,2)); // Math.Pow calcula a base elevada a 2
}
```

Já para realizar o cálculo da área do retângulo, eu preciso do valor da base e da altura do quadrado. Assim, o método área ficaria da seguinte forma:

```java
public int area(int base, int altura){
return(base * altura);
}
```

## [Java] Assinaturas

Esses dois métodos podem conviver na mesma classe, uma vez que eles possuem diferentes *assinaturas*. A **assinatura de um método** é determinada pelo tipo de parâmetros e pela ordem em que estes foram declarados. Desta forma, a assinatura do primeiro método é:

```java
area ( int );
```

e do segundo:

```java
area ( int , int );
```

Diante da diferença de assinaturas, podemos ter dois diferentes contextos para o uso do método de cálculo da área:

```java
System.out.println("Área = " + area( 5 ));
```

1. No primeiro contexto, é chamado para executar o método área com um único parâmetro e neste caso a avaliação em tempo de execução irá determinar que deve ser usado o cálculo da área do quadrado. Ou seja, aquele que recebe um valor inteiro como parâmetro, e a resposta será: 25.

```java
System.out.println("Área = " + area( 5, 6 ));
```

2. No segundo contexto, é chamado para executar o método área com um dos parâmetros e, neste caso, a avaliação em tempo de execução irá determinar que deve ser usado o cálculo da área do retângulo. Ou seja, aquele que recebe dois valores inteiros como parâmetro, e a resposta será: 30.

> [!Warning]
> Com o uso da sobrecarga de métodos você poderá criar quantos métodos com o mesmo identificador (nome) quiser em uma mesma classe, desde que eles **não possuam a mesma assinatura de método**.

## [Java] Métodos

Se quisesse incluir um método para calcular a área de uma circunferência, você não poderia incluir nesta classe, pois ele teria a mesma assinatura do método do cálculo da área do quadrado:

```java
public static int area(int raio) {
return ((int) Math.PI * Math.pow(raio,2));
// Math.Pow calcula o raio elevado a 2
}
```

O método teria a mesma assinatura do método área do quadrado e, no momento da execução, não haveria como saber qual dos dois deveria seria executado, pois ambos teriam o mesmo contexto:

```java
System.out.println("Área Quadrado = " + area( 5 ));
System.out.println("Área Circunferência = " + area( 4 ));
```

A linguagem Java não teria como definir qual método executar, já que ambos têm a mesma assinatura e a linguagem não é suficientemente inteligente para tentar buscar isso em algum outro lugar que não o contexto da chamada do método:

```java
area( 5 )

// e

area( 4 )
```

Como ambos possuem o mesmo contexto, os métodos com a mesma assinatura não podem compartilhar a mesma classe.

Exemplos de polimorfismo de sobrecarga válidos para uma mesma classe:

```java
meuMetodo(int a, double b, String c){
return 1;
}

meuMetodo(double b, String c, int a){
return 2;
}

meuMetodo(String c, int a, double b){
return 3;
}

meuMetodo(String c, double b, int a){
return 4;
}
```

As assinaturas são respectivamente:

```java
meuMetodo(int, double, String)

meuMetodo(double, String, int)

meuMetodo(String, int, double)

meuMetodo(String, double, int)
```

Todos os métodos acima, apesar de possuírem a mesma quantidade de parâmetros, têm assinaturas diferentes que serão executadas em função de contexto diferentes, respectivamente:

```java
int g = meuMetodo(2, 2.25, "Casa");

int h = meuMetodo(2.25, "Casa", 2);

int i = meuMetodo("Casa", 2, 2.25);

int j = meuMetodo("Casa", 2.25, 2);
```

Os valores armazenados em `g`, `h`, `i` e `j` serão respectivamente: `1, 2, 3 e 4`.

## [Java] A sobrecarga de métodos construtores

**Métodos construtores** são métodos e também podem ser sobrecarregados. Uma classe que possui mais de um método construtor é uma classe que oferece diferentes formas de criação para os seus objetos.

Outra forma de uso de *mais de um construtor* é para manter a compatibilidade de uma classe com suas aplicações antigas.

Se analisarmos a classe Carro, vista como exemplo anteriormente, podemos notar que ela não possui nenhum método construtor. Podemos então criar alguns métodos construtores para esta classe e preservar a aplicação antiga, criando e analisando uma classe nova (evoluída) e as duas aplicações, a antiga e a nova:

Classe: `Carro` (atualizada com cinco métodos construtores)

```java
import java.util.Scanner;

public class Carro{
// Use as regras da boa prática em programação Java
// para os identificadores da classes, dos atributos e dos métodos

// Atributos

String fabricante, modelo, cor, placa;
double valor;
int numeroPortas, anoFabricacao, anoModelo;

// Construtor Methods

public Carro() { }

public Carro(String placa, double valor){
this.placa = placa;
this.valor = valor;
}

public Carro(String madelo, String cor, String placa, double valor){
this.modelo = modelo;
this.cor = cor;
this.placa = placa;
this.valor = valor;
}

public Carro(String fabricante, String madelo, String cor, String placa, double valor){
this.fabricante = fabricante;
this.modelo = modelo;
this.cor = cor;
this.placa = placa;
this.valor = valor;
}

public Carro(String fabricante, String madelo, String cor, String placa, double valor,
int numeroPortas, int anoFabricacao, int anoModelo){
this.fabricante = fabricante;
this.modelo = modelo;
this.cor = cor;
this.placa = placa;
this.valor = valor;
this.numeroPortas = numeroPortas;
this.anoFabricacao = anoFabricacao;
this.anoModelo = anoModelo;
}

// Getters and Setters Methods

// Fabricante - Getters and Setters

public String getFabricante() {
return fabricante;
}

public void setFabricante (String fab){
if(!fab.isEmpty()) {
fabricante = fab;
}
}

// Modelo - Getters and Setters

public String getModelo() {
return modelo;
}

public void setModelo (String mad) {
if(!mod.isEmpty()) {
modelo = mod;
}
}

// Cor - Getters and Setters

public String getCor() {
return cor;
}

public void setCor (String co) {
if(!co.isEmpty()) {
cor = co;
}
}

// Placa - Getters and Setters

public String getPlaca() {
return placa;
}

public void setPlaca (String pla){
if(!pla.isEmpty()){
placa = pla;
}
}

// Valor - Getters and Setters

public double getValor(){
return valor;
}

public void setValor(double val){
if(val > 0){
valor = val;
}
}

// Número de Portas - Getters and Setters

public int getNumeroPortas(){
return numeroPortas;
}

public void setNumeroPortas (int nportas){
if(nportas > 0){
numeroPortas = nportas;
}
}

// Ano de Fabricação - Getters and Setters

public int getAnoFabricacao(){
return anoFabricacao;
}

public void setAnoFabricacao(int anofab){
if(anofab > 0){
anoFabricacao = anofab;
}
}

// Ano de Modelo - Getters and Setters

public int getAnoModelo(){
return anoModelo;
}

public void setAnoModelo(int anomod){
anoModelo = anomod;
}

public void imprimir(){
System.out.println("Fabricante : " + getFabricante() );
System.out.println("Modelo : " + getModelo() );
System.out.println("Cor : " + getCor() );
System.out.println("Placa : " + getPlaca() );
System.out.println("Valor : " + getValor() );
System.out.println("Número de Portas : " + getNumeroPortas() );
System.out.println("Ano de Fabricação : " + getAnoFabricacao() );
System.out.println("Ano do Modelo : " + getAnoModelo() );
}

public void entradaDados(){
Scanner entrada = new Scanner(System.in);
// o objeto Scanner deve ficar local aos métodos
// o objetivo do Scanner para entrada de dados em um atributo do carro
// vamos utilizar o objeto para entrada de dados

System.out.println("Digite o Fabricante do carro :");
setFabricante( entrada.nextLine() );

System.out.println("Digite o Modelo do carro :");
setModelo( entrada.nextLine() );

System.out.println("Digite a Cor do carro :");
setCor( entrada.nextLine() );

System.out.println("Digite a Placa do carro :");
setPlaca( entrada.nextLine() );

System.out.println("Digite o Valor do carro :");
setValor( Double.parseDouble( entrada.nextLine() ));

System.out.println("Digite o Número de Portas do carro :");
setNumeroPortas( Integer.parseInt( entrada.nextLine() ));

System.out.println("Digite o Ano de fabricação do carro :");
setAnoFabricacao( Integer.parseInt( entrada.nextLine() ));

System.out.println("Digite o Ano do Modelo do carro :");
setAnoModelo( Integer.parseInt( entrada.nextLine() ));
}
}
```

Aplicação antiga `AppCarro`

```java
public class AppCarro {
public static void main(String[] args) {
Carro car1 = new Carro();
car1.entradadeDados();
car1.imprimir();

Carro car2 = new Carro();
car2.entradadeDados();
car2.imprimir();

Carro car3 = new Carro();
car3.entradadeDados();
car3.imprimir();
}
}
```

A execução da aplicação não foi afetada pelas mudanças na classe porque foi criado o construtor vazio public Carro ( ) { } que garantiu a compatibilidade:

Aplicação usando diferentes construtores para criar os objetos: `AppCarro`

```java
public class AppCarroNovo{
public static void main(String[] args){
Carro car1 = new Carro();
car1.entradaDados();
car1.imprimir();

Carro car2 = new Carro("AAA1A00", 25000);
car2.imprimir();

Carro car3 = new Carro("Logan", "Azul", "ABC1E00", 32000);
car3.imprimir();

Carro car4 = new Carro("Audi", "A5", "Prata", "AUD0I00", 123000);
car4.imprimir();

Carro car5 = new Carro("Fiat", "Argo", "Verde", "ABB1I00", 42000, 5, 2018, 2019);
car5.imprimir();
}
}
```

> [!Note]
>
> 1. O primeiro método construtor criado foi o vazio, para garantir a compatibilidade com a aplicação antiga;
> 2. Foram incluídos mais quatro métodos construtores seguindo o conceito da sobrecarga de métodos;
> 3. Na nova aplicação, foram criados cinco diferentes objetos, cada um usando um construtor diferente;
> 4. Ambas as aplicações funcionaram apesar da alteração;

## [Java] Polimorfismo de sobrecarga e a evolução das classes

Com o **polimorfismo de sobrecarga** podemos criar diferentes implementações para métodos com o mesmo identificador (nome) em uma mesma classe.

Vamos imaginar que uma classe chamada `Login` fosse usada por vários de seus sistemas:

`Login.java`

```java
public class Login {
// Attributes
String nome;
String nomeLogin;
String senha;
Int nivelAcesso; // nível de acesso do usuário ao sistema

// Constructor Methods
public Login(String n1, String s){
setNivelAcesso(verificaLogin(n1, s));
}

public int verificaLogin(String nolog, String sem){
int na = 0;
if(nolog.equals("carneiro5") && sen.equals("123456")){
na = 10;
setNome("André");
}
else {
na = 0;
}
return na;
}

// Getters & Setters
public String getNome(){
return nome;
}

public void setNome(String no){
if(!no.isEmpty()){
nome = no;
}
}

public String getIdentidade(){
return identidade;
}

public void setIdentidade(String iden){
if(!iden.isEmpty()){
identidade = iden;
}
}

public String getIdade(){
return idade;
}

public void setIdade(int ida){
if(ida > 0){
idade = ida;
}
}

// Imprimir() method
public void imprimir(){
System.out.println("Pessoa: " + nome);
System.out.println("Identidade: " + identidade);
System.out.println("Idade: " + idade);
}
}
```

`AppLogin.java`

```java
public class AppLogin {
public static void main(String[] args){
// TODO Auto-generated method stub
Login lg1 = new Login("carneiro5", "123456");
System.out.println("O seu nome é: " + lg1.getNome());

System.out.println("O seu nivel de acesso é: " + lg1.getNivelAcesso());
Login lg2 = new Login("kkj", "000000");

System.out.println("O seu nome é: " + lg2.getNome());
System.out.println("O seu nivel de acesso é: " + lg2.getNivelAcesso());
}
}
```

Imagine a situação: você tem um novo cliente, e seus funcionários fazem o `login` não apenas com o nome de `login` e a `senha`, mas também utilizando um dispositivo eletrônico para geração de senhas (`token`).

A sua classe não iria funcionar com este novo contexto. Neste caso, a programação orientada a objetos nos ajuda muito, pois faremos uma atualização na classe `Login` e ela será capaz, não só de atender a esta nova demanda, mas de continuar a atender os antigos clientes.

Classe Atualizada: `Login`

```java
public class Login {

String nome;
String nomeLogin;
String senha;
int nivelAcesso; // nível de acesso do usuário ao sistema

public Login(String nL, String s) {
setNivelAcesso(verificaLogin(nL, s));
}

public Login(String nL, String s, int token) {
setNivelAcesso(verificaLogin(nL, s, token));
}

public int verificaLogin(String noLog, String sen) {
int na = 0;
if (noLog.equals("carneiro5") && sen.equals("123456")) {
na = 10;
setNome("André");
} else {
na = 0;
}
return na;
}

public int verificaLogin(String noLog, String sen, int tk) {
int na = 0;
if (noLog.equals("pereira") && sen.equals("246810") &&
verificarToken(tk)) {
na = 8;
setNome("Maria");
} else {
na = 0;
}
return na;
}

public boolean verificarToken(int tk) {
if (tk == 1000 || tk == 2000 || tk == 3000) {
return true;
} else {
return false;
}
}

public String getNome() {
return nome;
}

public void setNome(String no) {
if (!no.isEmpty()) {
nome = no;
}
}

public String getNomeLogin() {
return nomeLogin;
}

public void setNomeLogin(String nL) {
if (!nL.isEmpty()) {
nomeLogin = nL;
}
}

public String getSenha() {
return senha;
}

public void setSenha(String sem) {
if (!sem.isEmpty()) {
senha = sem;
}
this.senha = senha;
}

public int getNivelAcesso() {
return nivelAcesso;
}

public void setNivelAcesso(int na) {
if (na >= 0) {
nivelAcesso = na;
}
}
}
```

Aplicação: `AppLogin` com um objeto usando o novo construtor

```java
public class AppLogin{
public static void main(String[] args){
// TODO Auto-generated method stub
Login lg1 = new Login("carneiro5","123456");
System.out.println("O seu nome é: " + lg1.getNome());
System.out.println("O seu nível de acesso é: " + lg1.getNivelAcesso());

Login lg2 = new Login("kkj","000000");
System.out.println("O seu nome é: " + lg2.getNome());
System.out.println("O seu nível de acesso é: " + lg2.getNivelAcesso());

Login lg3 = new Login("pereira","246810", 2000);
System.out.println("O seu nome é: " + lg3.getNome());
System.out.println("O seu nível de acesso é: " + lg3.getNivelAcesso());
}
}
```

> [!Note]
>
> 01. A classe `Login` agora possui dois diferentes construtores e dois diferentes métodos `verificaLogin`, ambos sobrecarregados. A versão anterior foi preservada porque ainda é usada pelos sistemas dos antigos clientes. Entretanto, com a inclusão dos novos métodos, a classe foi atualizada e também passou a atender ao cliente novo;
>
> 02. Foi incluído ainda o método `verificarToken`, que só é usado pelo cliente novo. Sua inclusão na classe não atrapalha em nada os sistemas dos clientes antigos;
>
> 03. A aplicação agora pode instanciar (criar) objetos das duas formas, com e sem o token, sem que uma atrapalhe a outra.

As classes na programação orientada a objetos evoluem conforme precisamos de mais atributos e métodos.

Entretanto, se mantivermos os métodos necessários para os sistemas mais antigos, essa evolução não afetará os outros sistemas e teremos uma melhoria na classe, facilitando a sua evolução e sua manutenção, uma vez que, ao realizar qualquer melhoria em uma classe, basta recompilar as aplicações que estas se tornarão atualizadas.

Exercício: Analisando os métodos abaixo (para uma mesma classe), podemos afirmar que foi aplicado o conceito de polimorfismo de sobrecarga?

```java
double calculo(double p, double a) {

return p+a;

}

double calculo(double p, double a) {

return a*p;

}

double calculo(double p, double a, double t) {

return p-a+t;

}
```

Resolução: Não, apesar dos métodos possuírem o mesmo nome, eles não possuem a mesma assinatura.

Assinaturas:

```java
double calculo(double, double)

double calculo(double, double)

double calculo(double, double, double)
```

# ☕ [Java] Herança

A programação orientada a objetos tem como um dos principais pilares o "reaproveitamento de código". Reaproveitar o código significa menos esforço em seu desenvolvimento e mais facilidade na manutenção do sistema. Ao evitarmos a redundância de código, fica mais fácil gerar alterações, uma vez que não precisaremos modificá-lo em vários locais diferentes.

A **herança** é um conceito muito importante que possibilita identificar duas ou mais classes que possuam semelhanças. Estas podem ser definidas através de uma hierarquia, em que os membros comuns às duas ou mais classes passam para uma nova classe, conhecida como **Superclasse** ou **classe “mãe”**.

Já as **classes originais** permanecerão apenas com os membros não comuns, sendo denominadas **Subclasses** ou **classes “filhas”**. Ao aplicar este conceito, podemos trabalhar com uma hierarquia entre as classes, em que as de maior hierarquia aglutinam os membros comuns e as de menor hierarquia possuem apenas membros distintos entre elas.

Em algumas linguagens de programação no conceito de POO se trabalha muito com os dois tipos de herança. Entretanto, a linguagem Java não trabalha com Heranças múltiplas, somente com herança simples.

## [Java] Herança Simples
Em Java, temos apenas a implementação da herança simples. A **herança simples** se caracteriza por cada classe herdar sempre de apenas uma outra classe por vez. Devemos observar que, mesmo que tenhamos uma sequência de classes herdando, em que uma herda da outra, ainda assim, temos a herança simples, que pode ser observada nos exemplos das figuras a seguir:

![img02](https://user-images.githubusercontent.com/61624336/120694581-c979e900-c480-11eb-8366-93a82d90a124.jpg)

![img03](https://user-images.githubusercontent.com/61624336/120694670-deef1300-c480-11eb-8e90-7ed645c85526.jpg)

![img04](https://user-images.githubusercontent.com/61624336/120694709-e9111180-c480-11eb-93f3-6f8bed85d319.jpg)

![img05](https://user-images.githubusercontent.com/61624336/120694981-32616100-c481-11eb-81ec-ad1f071fa66f.jpg)

> [!Note]
> Em todos os casos, temos sempre a *herança simples*, pois cada classe sempre herda **apenas** da sua *classe superior*, mesmo se incluirmos novos níveis.

## [Java] Herança Múltipla

A **herança múltipla** se caracteriza quando uma mesma classe herda de duas ou mais classes ao mesmo tempo. Java não permite a implementação da herança múltipla; mesmo este sendo um conceito da programação orientada a objetos, algumas linguagens de programação não implementam este conceito. A linguagem C permite a implementação de herança múltipla, mas Java e C#, por exemplo, não permitem esta implementação.

A herança múltipla pode ser observada nos exemplos das figuras a seguir:

![img06](https://user-images.githubusercontent.com/61624336/120702327-54131600-c48a-11eb-8e60-336c6f8a3191.jpg)

![img07](https://user-images.githubusercontent.com/61624336/120708698-482b5200-c492-11eb-97e7-d2ea8ed6125e.jpg)

Vejamos um exemplo: Sejam as três classes a seguir referentes a uma empresa:

Classe
Atributos
Atributos

Gerente


  • Identidade: texto

  • Nome: texto

  • Matrícula: texto

  • Salário: real

  • PercentualVenda: real

  • NívelGerente: inteiro



  • Setters [para todos os atributos]

  • Getters [para todos os atributos]

  • Construtores [mínimo 5]

  • Imprimir [para exibir os atributos]

  • EntradaDados [para todos os atributos]

  • Cadastrar [atribui a todos os atributos]

Funcionário


  • Identidade: texto

  • Nome: texto

  • Matrícula: texto

  • Salário: real

  • Setor: texto

  • NomeGerente: texto





  • Setters [para todos os atributos]

  • Getters [para todos os atributos]

  • Construtores [mínimo 5]

  • Imprimir [para exibir os atributos]

  • EntradaDados [para todos os atributos]

  • Cadastrar [atribui a todos os atributos]


Cliente


  • Identidade: texto

  • Nome: texto

  • CódigoCliente: texto

  • Idade: inteiro

  • Telefone: texto





  • Setters [para todos os atributos]

  • Getters [para todos os atributos]

  • Construtores [mínimo 5]

  • Imprimir [para exibir os atributos]

  • EntradaDados [para todos os atributos]

  • Cadastrar [atribui a todos os atributos]


Ao analisar as classes, podemos afirmar que teremos que repetir a declaração dos atributos `Identidade` e `Nome` em todas elas, bem como os métodos **Setters** e **Getters** destes atributos em todas as três classes, sem contar que ainda teremos que repetir parte da codificação dos métodos `cadastrar`, `imprimir` e `entradaDados`.

Neste caso, podemos aplicar os conceitos de **herança** e teremos a seguinte estrutura para atender ao problema descrito acima:

![img08](https://user-images.githubusercontent.com/61624336/120713682-a1967f80-c498-11eb-9a70-f98b89632c26.jpg)

- Como os atributos `Identidade` e `Nome` são comuns às três classes, eles ficarão na Superclasse de maior hierarquia;
- Como os atributos `Matrícula` e `Salário` pertencem somente às classes `Gerente` e `Funcionário`, teremos uma classe intermediária chamada `PessoaEmpresa`, que herdará da classe `Pessoa`, mas esta também será uma Superclasse para as classes `Gerente` e `Funcionário`;
- A classe `Cliente` herdará da classe `Pessoa`;
- As classes `Gerente` e `Funcionário` herdarão da classe `PessoaEmpresa`.

Desta forma, não teremos redundância de códigos, escreveremos menos linhas e teremos maior facilidade na manutenção das classes da nossa biblioteca de classes.

Vamos analisar agora as diferenças entre as versões das nossas classes com e sem a aplicação da herança:

Classe `Gerente` sem o uso do conceito de herança:

```java
import java.util.Scanner;

public class Gerente {
// Attributes
String identidade, nome, matricula;
double salario, percentualVenda;
int nivelGerente;

// Getters and Setters \\

// Identidade
public String getIdentidade() {
return identidade;
}

public void setIdentidade(String id) {
if (!id.isEmpty()) {
identidade = id;
}
}

// Nome
public String getNome() {
return nome;
}

public void setNome(String no) {
if (!no.isEmpty()) {
nome = no;
}
}

// Matricula
public String getMatricula() {
return matricula;
}

public void setMatricula(String ma) {
if (!mo.isEmpty()) {
matricula = mo;
}
}

// Salario
public double getSalario() {
return salario;
}

public void setSalario(double sa) {
if (sa >= 0) {
salario = sa;
}
}

public double getPercentualVenda() {
return percentualVenda;
}

public void setPercentualVenda(double pv) {
if (pv >= 0) {
percentualVenda = pv;
}
}

public int getNivelGerente() {
return nivelGerente;
}

public void setNivelGerente(int ng) {
if (ng >= 0) {
nivelGerente = ng;
}
}

// Constructor Methods
public Gerente() { }

public Gerente(String id) {
setIdentidade(id);
}

public Gerente(double sa) {
setSalario(sa);
}

public Gerente(String id, double sa) {
setIdentidade(id);
setSalario(sa);
}

public Gerente(double sa, String id) {
setIdentidade(id);
setSalario(sa);
}

public Gerente(String id, String no, String ma, double sa, double pv, int ng) {
setIdentidade(id);
setNome(no);
setMatricula(ma);
setSalario(sa);
setPercentualVenda(pv);
setNivelGerente(ng);
}

public void cadastrar(String id, String no, String ma, double sa, double pv, int ng) {
setIdentidade(id);
setNome(no);
setMatricula(ma);
setSalario(sa);
setPercentualVenda(pv);
setNivelGerente(ng);
}

// EntradaDados() Method
public void entradaDados() {
Scanner entrada = new Scanner(System.in);

System.out.println("Identidade :");
setIdentidade(entrada.nextLine());

System.out.println("Nome: ");
setNome(entrada.nextLine());

System.out.println("Matricula: ");
setMatricula(entrada.nextLine());

System.out.println("Salário: ");
setSalario(Double.parseDouble(entrada.nextLine()));

System.out.println("Percentual de Venda: ");
setPercentualVenda(Double.parseDouble(entrada.nextLine()));

System.out.println("Nível Gerencia: ");
setNivelGerente(Integer.parseInt(entrada.nextLine()));

entrada.close();
}

// Imprimir() Method
public void imprimir() {
System.out.println("Identidade : " + getIdentidade());
System.out.println("Nome : " + getNome());
System.out.println("Matricula : " + getMatricula());
System.out.println("Salário : " + getSalario());
System.out.println("Percentual de Venda : " + getPercentualVenda());
System.out.println("Nível Gerencia : " + getNivelGerente());
}
}
```

Se quiser, posso te ajudar a criar uma classe `Main` para executar esse código.

Classe `Funcionário` sem o uso do conceito de herança:

```java
import java.util.Scanner;

public class Funcionario {
// Attributes
String identidade, nome, matricula, setor, nomeGerente;
double salario;

// Getters and Setters \\

// Identidade
public String getIdentidade() {
return identidade;
}

public void setIdentidade(String id) {
if (!id.isEmpty()) {
identidade = id;
}
}

// Nome
public String getNome() {
return nome;
}

public void setNome(String no) {
if (!no.isEmpty()) {
nome = no;
}
}

// Matricula
public String getMatricula() {
return matricula;
}

public void setMatricula(String ma) {
if (!mo.isEmpty()) {
matricula = mo;
}
}

// Salario
public double getSalario() {
return salario;
}

public void setSalario(double sa) {
if (sa >= 0) {
salario = sa;
}
}

// Setor
public String getSetor() {
return setor;
}

public void setSetor(String se) {
if (!se.isEmpty()) {
setor = se;
}
}

// Nome do Gerente
public String getNomeGerente() {
return nomeGerente;
}

public void setNomeGerente(String ng) {
if (!ng.isEmpty()) {
nomeGerente = ng;
}
}

// Constructor Methods

public Funcionario() { }

public Funcionario(String id) {
setIdentidade(id);
}

public Funcionario(double sa) {
setSalario(sa);
}

public Funcionario(String id, double sa) {
setIdentidade(id);
setSalario(sa);
}

public Funcionario(double sa, String id) {
setIdentidade(id);
setSalario(sa);
}

public Funcionario(String id, String no, String ma, double sa, String se, String ng) {
setIdentidade(id);
setNome(no);
setMatricula(ma);
setSalario(sa);
setSetor(se);
setNomeGerente(ng);
}

public void cadastrar(String id, String no, String ma, double sa, String se, String ng) {
setIdentidade(id);
setNome(no);
setMatricula(ma);
setSalario(sa);
setSetor(se);
setNomeGerente(ng);
}

// EntradaDados() Method
public void entradaDados() {
Scanner entrada = new Scanner(System.in);

System.out.println("Identidade :");
setIdentidade(entrada.nextLine());

System.out.println("Nome :");
setNome(entrada.nextLine());

System.out.println("Matricula :");
setMatricula(entrada.nextLine());

System.out.println("Salário :");
setSalario(Double.parseDouble(entrada.nextLine()));

System.out.println("Setor :");
setSetor(entrada.nextLine());

System.out.println("Nome Gerente:");
setNomeGerente(entrada.nextLine());

entrada.close();
}

// Imprimir() Method
public void imprimir() {
System.out.println("Identidade : " + getIdentidade());
System.out.println("Nome : " + getNome());
System.out.println("Matrícula : " + getMatricula());
System.out.println("Salário : " + getSalario());
System.out.println("Setor : " + getSetor());
System.out.println("Nome Gerente : " + getNomeGerente());
}
}
```

Classe `Cliente` sem o uso do conceito de herança:

Vamos a algumas perguntas:

```txt
- Se for necessário incluir um novo atributo com o `CPF` em todas as classes?
Resposta: Teremos que alterar todas as classes, dificultando a manutenção.

- Se for necessário um novo atributo para armazenar a data de admissão para `Gerentes` e `Funcionários`?
Resposta: Será necessário alterar as classes `Gerente` e `Funcionário`.

- Se for necessário incluir a data da primeira compra do cliente?
Resposta: Será necessário alterar apenas a classe `Cliente`.
```

Vamos guardar estas perguntas e repeti-las após a aplicação da herança.

Aplicando os conceitos de herança: Primeiro de tudo, vamos arquitetar o conceito de superclasses e subclasses:

- **SuperClasse**: `Pessoa`;
- **SubClasse de Pessoa**: `PessoaEmpresa`, `Cliente`;
- **SubClasse de PessoaEmpresa**: `Gerente`, `Funcionario`.

Logo, iremos aplicar esse conceito:

Classe `Pessoa` com o uso do conceito de herança (SuperClasse):

```java
import java.util.Scanner;

public class Pessoa {
// Atributos
String identidade, nome;

// Getters and Setters

// Identidade
public String getIdentidade() {
return identidade;
}

public void setIdentidade(String id) {
if (!id.isEmpty()) {
identidade = id;
}
}

// Nome
public String getNome() {
return nome;
}

public void setNome(String no) {
if (!no.isEmpty()) {
nome = no;
}
}

// Métodos Construtores
public Pessoa() { }

public Pessoa(String id) {
setIdentidade(id);
}

public Pessoa(String id, String no) {
setIdentidade(id);
setNome(no);
} // Só podemos criar 3 construtores para a classe Pessoa

public void cadastrar(String id, String no) {
setIdentidade(id);
setNome(no);
}

public void entradaDados() {
Scanner entrada = new Scanner(System.in);

System.out.println("Identidade :");
setIdentidade(entrada.nextLine());

System.out.println("Nome :");
setNome(entrada.nextLine());

entrada.close();
}

public void imprimir() {
System.out.println("Identidade :" + getIdentidade());
System.out.println("Nome :" + getNome());
}
}
```

Classe `PessoaEmpresa` com o uso do conceito de herança (SubClasse de `Pessoa`):

```java
import java.util.Scanner;

public class PessoaEmpresa extends Pessoa {
// Atributos
String matricula;
double salario;

// Getters and Setters \\

// Matricula
public String getMatricula() {
return matricula;
}

public void setMatricula(String ma) {
if (!ma.isEmpty()) {
matricula = ma;
}
}

// Salario
public double getSalario() {
return salario;
}

public void setSalario(double sa) {
if (sa >= 0) {
salario = sa;
}
}

// Constructor Methods

public PessoaEmpresa() { }

public PessoaEmpresa(String id) {
super(id);
}

public PessoaEmpresa(double sa) {
setSalario(sa);
}

public PessoaEmpresa(String id, double sa) {
super(id);
setSalario(sa);
}

public PessoaEmpresa(double sa, String id) {
super(id);
setSalario(sa);
}

public PessoaEmpresa(String id, String no, String ma, double sa) {
super(id, no);
setMatricula(ma);
setSalario(sa);
}

public void cadastrar(String id, String no, String ma, double sa) {
super.cadastrar(id, no);
setMatricula(ma);
setSalario(sa);
}

public void entradaDados() {
Scanner entrada = new Scanner(System.in);

super.entradaDados();

System.out.println("Matricula :");
setMatricula(entrada.nextLine());

System.out.println("Salário :");
setSalario(Double.parseDouble(entrada.nextLine()));

entrada.close();
}

public void imprimir() {
super.imprimir();
System.out.println("Matricula :" + getMatricula());
System.out.println("Salário :" + getSalario());
}
}
```

Classe `Gerente` com o uso do conceito de herança (SubClasse de `PessoaEmpresa`):

```java
import java.util.Scanner;

public class Gerente extends PessoaEmpresa {
// Attributes
double percentualVenda;
int nivelGerente;

// Getters and Setters \\

// Percentual de Venda
public double getPercentualVenda() {
return percentualVenda;
}

public void setPercentualVenda(double pv) {
if (pv >= 0) {
percentualVenda = pv;
}
}

public int getNivelGerente() {
return nivelGerente;
}

public void setNivelGerente(int ng) {
if (ng >= 0) {
nivelGerente = ng;
}
}

public Gerente() { }

public Gerente(String id) {
super(id);
}

public Gerente(double sa) {
super(sa);
}

public Gerente(String id, double sa) {
super(id, sa);
}

public Gerente(double sa, String id) {
super(id, sa);
}

public Gerente(String id, String no, String ma, double sa, double pv, int ng) {
super(id, no, ma, sa);
setPercentualVenda(pv);
setNivelGerente(ng);
}

public void cadastrar(String id, String no, String ma, double sa, double pv, int ng) {
super.cadastrar(id, no, ma, sa);
setPercentualVenda(pv);
setNivelGerente(ng);
}

public void entradaDados() {
Scanner entrada = new Scanner(System.in);

super.entradaDados();

System.out.println("Percentual de Venda:");
setPercentualVenda(Double.parseDouble(entrada.nextLine()));

System.out.println("Nível Gerencia :");
setNivelGerente(Integer.parseInt(entrada.nextLine()));

entrada.close();
}

public void imprimir() {
super.imprimir();
System.out.println("Percentual de Venda:" + getPercentualVenda());
System.out.println("Nível Gerencia :" + getNivelGerente());
}
}
```

Classe `Funcionário` com o uso do conceito de herança (SubClasse de `PessoaEmpresa`):

```java
import java.util.Scanner;

public class Funcionario {
// Attributes
String setor, nomeGerente;

// Getters and Setters

// Setor
public String getSetor() {
return setor;
}

public void setSetor(String se) {
if (!se.isEmpty()) {
setor = se;
}
}

// Nome do Gerente
public String getNomeGerente() {
return nomeGerente;
}

public void setNomeGerente(String ng) {
if (!ng.isEmpty()) {
nomeGerente = ng;
}
}

// Constructor Methods
public Funcionario() { }

public Funcionario(String id) {
super(id);
}

public Funcionario(double sa) {
super(sa);
}

public Funcionario(String id, double sa) {
super(id, sa);
}

public Funcionario(double sa, String id) {
super(id, sa);
}

public Funcionario(String id, String no, String ma, double sa) {
super(id, no, ma, sa);
setSetor(se);
setNomeGerente(ng);
}

public void cadastrar(String id, String no, String ma, double sa, String se, String ng) {
super.cadastrar(id, no, ma, sa);
setSetor(se);
setNomeGerente(ng);
}

// EntradaDados() Method
public void entradaDados() {
Scanner entrada = new Scanner(System.in);
super.entradaDados();

System.out.println("Setor: ");
setSetor(entrada.nextLine());

System.out.println("Nome do Gerente: ");
setNomeGerente(entrada.nextLine());
}

// Imprimir() Method
public void imprimir() {
super.imprimir();
System.out.println("Setor: " + getSetor());
System.out.println("Nome do Gerente: " + getNomeGerente());
}
}
```

Classe `Cliente` com o uso do conceito de herança (SubClasse de `Pessoa`):

```java
import java.util.Scanner;

public class Cliente {
// Attributes
String codigoCliente, telefone;
int idade;

// Getters and Setters

// Código Cliente
public String getCodigoCliente() {
return codigoCliente;
}

public void setCodigoCliente(String cc) {
if (!cc.isEmpty()) {
codigoCliente = cc;
}
}

// Telefone
public String getTelefone() {
return telefone;
}

public void setTelefone(String tel) {
if (!tel.isEmpty()) {
telefone = tel;
}
}

// Idade
public int getIdade() {
return idade;
}

public void setIdade(int id) {
if (id >= 0) {
idade = id;
}
}

// Constructor Methods

public Cliente() { }

public Cliente(String id) {
super(id);
}

public Cliente(int id) {
setIdade(id);
}

public Cliente(String id, int ida) {
super(id);
setIdade(ida);
}

public Cliente(int ida, String id) {
super(id);
setIdade(ida);
}

public Cliente(String id, String no, String cc, String tf, int ida) {
super(id, no);
setCodigoCliente(cc);
setTelefone(tf);
setIdade(ida);
}

public void cadastrar(String id, String no, String cc, String tf, int ida) {
super.cadastrar(id, no);
setCodigoCliente(cc);
setTelefone(tf);
setIdade(ida);
}

// EntradaDados() Method
public void entradaDados() {
Scanner entrada = new Scanner(System.in);

super.entradaDados();
System.out.println("Código do Cliente :");
setCodigoCliente(entrada.nextLine());

System.out.println("Telefone :");
setTelefone(entrada.nextLine());

System.out.println("Idade :");
setIdade(Integer.parseInt(entrada.nextLine()));

entrada.close();
}

// Imprimir() Method
public void imprimir() {
super.imprimir();

System.out.println("Código do Cliente :" + getCodigoCliente());
System.out.println("Telefone :" + getTelefone());
System.out.println("Idade :" + getIdade());
}
}
```

Vamos responder novamente às três perguntas feitas anteriormente:

- Se for necessário incluir um novo atributo com o `CPF` em todas as classes?
Resposta: Teremos que alterar apenas a classe `Pessoa`, uma vez que todas as demais classes irão herdar qualquer atualização nesta classe.

- Se for necessário um novo atributo para armazenar a data de admissão para `Gerentes` e `Funcionários`?
Resposta: Será necessário alterar apenas a classe `PessoaEmpresa`, já que as classes `Gerente` e `Funcionário` herdarão desta classe.

- Se for necessário incluir a data da primeira compra do cliente?
Resposta: Será necessário alterar apenas a classe `Cliente`.

> [!Warning]
> Após analisarmos as duas soluções, chegamos à conclusão de que, ao utilizar a herança, não só evitamos a **redundância de códigos (repetição)** como facilitamos a manutenção, uma vez que, para realizar qualquer atualização, deveremos sempre alterar apenas uma das classes.

No final, a classe `Gerente` é composta por todos os membros de `Pessoa`, `PessoaEmpresa` e `Gerente`, uma vez que `Gerente` estende `PessoaEmpresa`, que por sua vez estende a classe `Pessoa`:


Pessoa +
PessoaEmpresa +
Gerente

O mesmo ocorre com a classe Funcionário, composta por todos os membros de Pessoa, PessoaEmpresa e Funcionário, uma vez que Funcionário estende PessoaEmpresa, que por sua vez estende a classe Pessoa:


Pessoa +
PessoaEmpresa +
Funcionário

Já a classe `Cliente` é composta por todos os membros de `Pessoa` e `Cliente`, uma vez que `Cliente` estende a classe `Pessoa`:


Pessoa +
Cliente

## [Java] Herança de métodos construtores

Vamos analisar os métodos construtores da classe `Cliente`:

```java
// Métodos construtores
public Cliente() { }

public Cliente(String id) {
super(id);
}

public Cliente(int id) {
setIdade(id);
}

public Cliente(String id, int ida) {
super(id);
setIdade(id);
}

public Cliente(int ida, String id) {
super(id);
setIdade(id);
}

public Cliente(String id, String no, String cc, String tf, int ida) {
super(id, no);
setCodigoCliente(cc);
setTelefone(tf);
setIdade(ida);
}

public void cadastrar(String id, String no, String cc, String tf, int ida) {
super.cadastrar(id, no);
setCodigoCliente(cc);
setTelefone(tf);
setIdade(ida);
}
```

Alguns destes métodos repassam os parâmetros recebidos para a **Superclasse**, através da palavra reservada super. Esta instrução diz ao compilador que o(s) parâmetro(s) será(ão) repassado(s) a um método construtor com a mesma assinatura na SuperClasse.

Assim, o método:

```java
public Cliente(String id) {
super(id);
}
```

O `super` repassará o parâmetro `id` para o método.

```java
public Pessoa(String id) {
setIdentidade(id);
}
```

O método: repassará o parâmetro `id` para o mesmo método construtor usado no anterior.

Por final o método:

```java
public Cliente(String id, String no, String cc, String tf, int ida) {
super(id, no);
setCodigoCliente(cc);
setTelefone(tf);
setIdade(ida);
}
```

Repassará os parâmetros do `id` e no para o método construtor da SuperClasse com a assinatura equivalente a estes dois parâmetros, que será o método:

```java
public Pessoa(String id, String no) {
setIdentidade(id);
setNome(no);
}
```

Todo método construtor de uma SubClasse deve referenciar um construtor da SuperClasse, isso quer dizer que para o construtor vazio: public Cliente() { }, será obrigatório que exista um construtor vazio na SuperClasse: public Pessoa() { }.

## [Java] Sobrescrita de métodos
Métodos de uma *SuperClasse* podem ser **sobrescritos** em suas *subclasses*, implicando que um método descrito na *Superclasse* poderá ser substituído na *Subclasse*. Para isso, é importante observar que estes métodos devem possuir as mesmas assinaturas. Caso contrário, será usado o conceito de *Sobrecarga* e não de *Sobrescrita*.

Vamos analisar o método imprimir da Superclasse `Pessoa`:

```java
public void imprimir() {
System.out.println("Identidade: " + getIdentidade());
System.out.println("Nome: " + getNome());
}
```

Ele não é suficiente para atender às necessidades da classe `Cliente`, então poderíamos tê-lo substituído por:

```java
public void imprimir() {
System.out.println("Identidade: " + getIdentidade());
System.out.println("Nome: " + getNome());
System.out.println("Código do cliente: " + getCodigoCliente());
System.out.println("Telefone: " + getTelefone());
System.out.println("Idade: " + getIdade());
}
```

Esta substituição permitiria que o método imprimir fosse substituído na **Subclasse** por um método mais completo que atendesse a sua necessidade.

Note que as assinaturas dos métodos, tanto na **Superclasse Pessoa** como na **Subclasse Cliente**, são idênticas: imprimir( ); desta forma, houve uma **Sobrescrita** (`Override`) e não uma **Sobrecarga** (`Overhead`).

Como uma primeira forma de atender a demanda da classe `Cliente`, o método `imprimir` nesta nova versão seria suficiente, mas podemos melhorar nossa solução, observe que parte do código já existe no método `imprimir` da Superclasse `Pessoa`, ocorrendo uma redundância de código nas instruções destacadas na cor vermelha:

```diff
public void imprimir() {
- System.out.println("Identidade: " + getIdentidade());
- System.out.println("Nome: " + getNome());
System.out.println("Código do cliente: " + getCodigoCliente());
System.out.println("Telefone: " + getTelefone());
System.out.println("Idade: " + getIdade());
}
```

Note que as instruções na cor vermelha já existem no método `imprimir` da Superclasse e, assim, podemos reaproveitar os códigos da Superclasse ao chamar o método `imprimir` da Superclasse na Subclasse: Podemos acessar ou chamar membros/métodos da superclasse que foram herdados ou sobrescritos (mesmo que tenham sido sobrescritos).

```diff
public void imprimir() {
+ super.imprimir();
System.out.println("Código do cliente: " + getCodigoCliente());
System.out.println("Telefone: " + getTelefone());
System.out.println("Idade: " + getIdade());
}
```

Ao reaproveitar o método `imprimir` da Superclasse, temos dois ganhos muito importantes:

1. Não haverá redundância de códigos, as instruções do método `imprimir` da Superclasse serão reaproveitadas na Subclasse;

2. Caso haja a necessidade de incluir um novo atributo no método `imprimir`, só precisaremos realizar a alteração em apenas uma classe, pois, se for um atributo específico da classe `Cliente`, só precisaremos incluir a instrução no método da classe `Cliente`. Caso contrário, se o atributo for comum às demais classes, a instrução deverá ser incluída apenas na Superclasse `Pessoa`.

A **herança** é um conceito importantíssimo da programação orientada a Objetos, permitindo que reaproveitemos membros Superclasse, que serão herdados pelas Subclasses, evitando redundância de códigos, além de facilitar a manutenção das nossas classes, uma vez que qualquer necessidade de mudança implicará na alteração de apenas uma classe.

# ☕ [Java] Particionamento


Classe
Atributos


Desktop

MarcaPlacaMae : texto

ModeloPlacaMae : texto

PrecoPlacaMae: real

TipoProcessador : texto

MarcaHD : texto

ModeloHD : texto

PrecoHD : real

TipoHD : texto

CapacidadeHD : inteiro

MarcaPlacaVideo : texto

ModeloPlacaVideo : texto

PrecoPlacaVideo : real

Padrao : texto

MarcaMemoria : texto

ModeloMemoria : texto

PrecoMemoria: real

TipoMemoria : texto

CapacidadeMemoria : inteiro

O **particionamento** é a decomposição de classes extensas em classes menores, que podem ser mais bem reaproveitadas em outras classes, além de permitir melhor controle e manutenção.

O particionamento de classes (ou decomposição/compartimentalização de responsabilidades) nos permite criar objetos menores e mais simples, que poderão ser reunidos em conjunto, capazes de criar novas classes, maiores e mais complexas. O particionamento de classes em Java pode ter diferentes significados dependendo do contexto. É uma técnica essencial no design orientado a objetos, e existem diversas boas práticas, padrões de projeto e avisos importantes relacionados a essa abordagem.

Geralmente, ele se refere a técnicas de organização, modularização e otimização de código, incluindo:

1. Divisão de classes em pacotes (Organização modular)
2. Uso de classes aninhadas e internas
3. Particionamento em sistemas distribuídos (Microservices, RMI, etc.)
4. Divisão em múltiplos arquivos e JARs (Modularização e encapsulamento)

Vamos imaginar um computador do tipo `Desktop`, que é um objeto bem complexo, com diferentes partes. Muitas dessas partes são usadas por outros objetos também, tal como `Notebooks` e `Servidores`.

Um `HD` (HardDisk), por exemplo, pode ser usado por cada um deles, assim como a placa de vídeo, a placa-mãe, o vídeo, a memória, além de outros dispositivos.

Se formos criar uma classe para representar um `Desktop`, teremos uma classe com muitos atributos, o que a tornaria grande e complexa, com muitas linhas de código e de difícil manutenção. Isso porque, se fosse necessário realizar alguma mudança, teríamos que trabalhar em uma classe altamente complexa.

Outro ponto importante seria a criação das classes `Notebook` e `Servidor`, que seriam igualmente complexas, sem contar que, para realizar uma alteração em um único componente que fosse, teríamos que fazê-la em todas as três classes (`Desktop`, `Notebook` e `Servidor`).

Inicialmente, nossa classe `Desktop` ficaria com os seguintes atributos:

```java
public class Desktop {
public String marcaPlacaMae, modeloPlacaMae;
public double precoPlacaMae;
public String tipoProcessador, marcaHD, modeloHD;
public double precoHD;
public String tipoHD;
public int capacidadeHD;
public String marcaPlacaVideo, modeloPlacaVideo;
public double precoPlacaVideo
public String padrao, marcaMemoria, modeloMemoria;
public double precoMemoria;
public String tipoMemoria;
public int capacidadeMemoria;
}
```

A classe ficou extensa, complexa, sem contar que precisaremos ainda incluir os métodos de acesso (Setters e Getters), construtores, entradaDados, imprimir, cadastrar, além de outros métodos que possam se fazer necessários, sem contar a grande quantidade de atributos.

Observe que muitos dos atributos são comuns a todas as classes e seria necessário diferenciar os atributos que possuem o mesmo nome, tal como: `modeloPlacaMae`; `modeloHD`, `modeloPlacaVideo`, `modeloMemoria` etc.

O problema ainda poderia ser maior, se tivéssemos mais slots de memória, com modeloMemoria0, modeloMemoria1, modeloMemoria2, modeloMemoria3, para quatro slots, por exemplo. Essas dificuldades aumentam bastante o tamanho da classe e a sua complexidade; além disso, neste exemplo, começaríamos com 18 atributos, sem levar em consideração a questão dos diferentes slots de memória.

Entretanto, se pensarmos segundo a ótica do particionamento, podemos dividir a classe `Desktop`, grande e complexa, em classes menores e mais simples. Como sugestão, poderíamos criar as classes que foram apresentadas como exemplo no parágrafo anterior em:

- Placa-mãe (`PlacaMae`);
- Disco Rígido (`HD`);
- Placa de vídeo (`PlacaVideo`);
- Memória (`Memoria`).

A classe `Desktop` seria então decomposta (particionada) da seguinte forma:

Vamos determinar poucos atributos para cada uma delas, apenas para entendermos melhor o conceito.


Classe
Atributos


PlacaMae


  • Marca : texto

  • Modelo : texto

  • Preco : real

  • TipoProcessador : texto






HD


  • Marca : texto

  • Modelo : texto

  • Preco : real

  • Tipo : texto

  • Capacidade : inteiro






PlacaVideo


  • Marca : texto

  • Modelo : texto

  • Preco : real

  • Padrão : texto





Memoria


  • Marca : texto

  • Modelo : texto

  • Preco : real

  • Tipo : texto

  • Capacidade : inteiro





Assim, vamos criar as classes separadamente, dividindo a classe `Desktop` conforme proposto:

```java
public class PlacaMae {
public String marca, modelo, tipoProcessador;
public double preco;
}

public class HD {
public String marca, modelo, tipo;
public double preco;
public int capacidade;
}

public class Processador {
public String marca, modelo;
public double preco;
public int capacidade;
}

public class PlacaVideo {
public String marca, modelo;
public double preco;
public int capacidade;
}

public class Memoria {
public String marca, modelo, tipo;
public double preco;
public int capacidade;
}
```

> [!Warning]
> 1. A decomposição da classe `Desktop` foi feita em quatro classes mais simples;
> 2. Os nomes dos atributos puderam ser mantidos na forma original, sem que um interfira no outro;
> 3. Cada classe mais simples se torna mais fácil de codificar;
> 4. As classes criadas serão mais fáceis para se realizar qualquer tipo de manutenção.

Outro ponto muito importante é que todas essas classes criadas através do particionamento poderão ser reaproveitadas para as classes `Notebook` e `Servidor`. Dessa forma, a nossa biblioteca de classes poderá criar novos objetos quando reunida em conjunto.

# ☕ [Java] Agregação

A programação orientada a objetos nos ajuda a resolver de forma mais simples problemas com alta complexidade. A **agregação de classes** é um conceito voltado a facilitar a solução de problemas muito complexos. Podemos dividir uma classe em classes menores, particionando esta classe em diversas outras classes mais simples, para posteriormente as reunirmos em conjunto, formando uma classe maior e mais complexa.

A reunião de uma ou mais classes para formar novas classes é chamada de agregação. Uma nova classe pode ser formada por um conjunto de diferentes objetos. Seguindo nosso exemplo, poderíamos reaproveitar as classes `PlacaMae`, `HD`, `PlacaVideo`, e `Memória` para criar novas classes, como `Desktop`, `Notebook` e `Servidor`, como nos exemplos a seguir:

```java
public class PlacaVideo {
public String tipoCooler;

public PlacaMae pm = new PlacaMae();
public PlacaVideo pv = new PlacaVideo();

public HD hd = new HD();
public Memoria me = new Memoria();
}

public class Notebook {
public double peso;

public PlacaMae pm = new PlacaMae();
public PlacaVideo pv = new PlacaVideo();
public HD hd = new HD();
public Memoria me = new Memoria();
}

public class Servidor {
public int numeroPlacasRede;

public PlacaMae pm = new PlacaMae();
public PlacaVideo pv = new PlacaVideo();
public HD hd = new HD();
public Memoria me = new Memoria();
}
```

> [!Warning]
> 1. Foram incluídos novos atributos para entendermos que a agregação pode incluir não apenas classes, mas também novos atributos;
>
> 2. Como toda classe é também um tipo, podemos declarar atributos como do tipo `Classe`;
>
> 3. Cada atributo criado a partir de uma classe (não sendo de tipos básicos) é uma agregação à classe principal, sendo assim, temos as quatro agregações (`PlacaMae`, `HD`, `PlacaVideo` e `Memoria`) para cada classe principal;
>
> 4. As classes `Desktop`, `Notebook` e `Servidor` foram criadas a partir de fragmentos menores, mas todos os atributos originais da classe `Desktop` estão presentes.

Uma oportunidade se apresenta com o uso da agregação, que não é possível resolver facilmente com a herança. Não confunda herança com agregação, pois são conceitos diferentes.

Imagine agora a situação dos slots de memória: na herança, só poderíamos herdar uma memória, mas, com a agregação, podemos criar quantas memórias quisermos, como no exemplo a seguir. Para a classe `Memoria` foram criados apenas os métodos de Acesso (Setters e Getters) para facilitar o entendimento:

```java
public class Memoria {

public String marca, modelo, tipo;
public double preco;
public int capacidade;

public String getMarca() {
return marca;
}

public void setMarca( String ma ) {
if(!mo.isEmpty()) {
marca = ma;
}
}

public String getModelo() {
return modelo;
}

public void setModelo( String mo ) {
if(!mo.isEmpty()) {
modelo = mo;
}
}

public String getTipo() {
return tipo;
}

public void setTipo( String tt ) {
if(!tt.isEmpty()) {
tipo = tt;
}
}

public double getPreco() {
return preco;
}

public void setPreco( double pr ) {
if(pr > 0) {
preco = pr;
}
}

public int getCapacidade() {
return capacidade;
}

public void setCapacidade(int cd) {
if(cd > 0) {
capacidade = cd;
}
}
}

public class Desktop {
// atributos da classe
public String tipoCooler;

// atributos agregados
public PlacaMae pm = new PlacaMae();
public PlacaVideo pv = new PlacaVideo();
public Hd hd = new Hd();

public Memoria slot0 = new Memoria();
public Memoria slot1 = new Memoria();
public Memoria slot2 = new Memoria();
public Memoria slot3 = new Memoria();
}

public class AppAgregacao {
public static void main(String[] args) {
Desktop desk = new Desktop();

//para usar algum método do objeto criado a partir da classe agregada,
//devemos usar o identificador do objeto:

// slot0
desk.slot0.setMarca("Samsung");
desk.slot0.setCapacidade(16);

// slot1
desk.slot1.setMarca("Kingston");
desk.slot1.setCapacidade(8);

// slot2
desk.slot2.setMarca("Sandisk");
desk.slot2.setCapacidade(4);

// slot3
desk.slot3.setMarca("Crucial");
desk.slot3.setCapacidade(2);

// total de memória:
System.out.println("Memória total: " + (desk.slot0.getCapacidade() + desk.slot1.getCapacidade() + desk.slot2.getCapacidade() + desk.slot3.getCapacidade()));
}
}
```

1. O `Desktop` possui quatro slots de memória;

2. Como cada slot é um objeto diferente, eles possuem propriedades diferentes, sem que nenhum tenha relação direta com os demais;

3. Não é comum montar um computador dessa forma, mas foram definidas marcas e capacidades diferentes para indicar que os valores armazenados não sofrem influência dos demais objetos agregados.

Agora que vimos como aplicar os conceitos de agregação e particionamento, podemos aplicar também o conceito de herança e melhorar nossas classes. Vamos aplicar o conceito de herança às nossas classes particionadas para termos uma aplicação mais concisa e mais fácil para realizar manutenções.

# ☕ [Java] Classes Particionadas

![pg14](https://user-images.githubusercontent.com/61624336/121124491-68913e80-c7fb-11eb-859a-1bd49cafeede.png)

Devemos identificar os atributos comuns às classes, sendo que os atributos `Marca`, `Modelo` e `Preço` são comuns a todas as classes. Já os atributos `Tipo` e `Capacidade` são comuns apenas às classes `HD` e `Memoria`, sendo necessária uma classe intermediária. O atributo `TipoProcessador` pertence apenas à classe `PlacaMae` e o atributo `Padrão` pertence apenas à classe `PlacaVideo`. As classes `HD` e `Memoria` não terão atributos específicos, ficarão apenas nas superclasses.

Classes Particionadas redefinidas após a aplicação da Herança:

![pg14-b](https://user-images.githubusercontent.com/61624336/121125855-9f685400-c7fd-11eb-841d-b6c0f1610e5f.png)

Classe Particionada `Identificação`:

```java
import java.util.Scanner;

public class Identificacao {

public String marca, modelo;
public double preco;

public Identificacao() { }

public Identificacao(String ma) {
setMarca(ma);
}

public Identificacao(double pr) {
setPreco(pr);
}

public Identificacao(String ma, String mo) {
setMarca(ma);
setModelo(mo);
}

public Identificacao(String ma, double pr) {
setMarca(ma);
setPreco(pr);
}

public Identificacao(String ma, String mo, double pr) {
setMarca(ma);
setModelo(mo);
setPreco(pr);
}

public String getMarca() {
return marca;
}

public void setMarca(String ma) {
if (!ma.isEmpty()) {
marca = ma;
}
}

public String getModelo() {
return modelo;
}

public void setModelo(String mo) {
if (!mo.isEmpty()) {
modelo = mo;
}
}

public double getPreco() {
return preco;
}

public void setPreco(double pr) {
if (pr > 0) {
preco = pr;
}
}

public void cadastrar(String ma, String mo, double pr) {
setMarca(ma);
setModelo(mo);
setPreco(pr);
}

public void imprimir() {
System.out.println("Marca: " + getMarca());
System.out.println("Modelo: " + getModelo());
System.out.println("Preço: " + getPreco());
}

public void entradaDados() {
Scanner entrada = new Scanner(System.in);

System.out.println("Marca: ");
setMarca(entrada.nextLine());

System.out.println("Modelo: ");
setModelo(entrada.nextLine());

System.out.println("Preço: ");
setPreco(Double.parseDouble(entrada.nextLine()));
}
}
```

Classe Particionada `Armazenamento`:

```java
import java.util.Scanner;

public class Armazenamento extends Identificacao {
public String tipo;
public int capacidade;

public Armazenamento() {
super();
}

public Armazenamento(String ti, int co) {
setTipo(ti);
setCapacidade(co);
}

public Armazenamento(String ma, String mo) {
super(ma, mo);
}

public Armazenamento(String ma, double pa) {
super(ma, pa);
}

public Armazenamento(String ma, String mo, double pa) {
super(ma, mo, pa);
}

public Armazenamento(String ma, String mo, double pa, String ti, int co) {
super(ma, mo, pa);
setTipo(ti);
setCapacidade(co);
}

public String getTipo() {
return tipo;
}

public void setTipo(String ti) {
if(!ti.isEmpty()) {
tipo = ti;
}
}

public int getCapacidade() {
return capacidade;
}

public void setCapacidade(int co) {
if(co > 0) {
capacidade = co;
}
}

public void cadastrar(String ma, String mo, String ti, double pa, int co) {
super.cadastrar(ma, mo, pa);
setTipo(ti);
setCapacidade(co);
}

public void imprimir() {
super.imprimir();
System.out.println("Processável: " + getTipo());
System.out.println("Capacidade: " + getCapacidade());
}

public void entradaDialogo() {
Scanner entrada = new Scanner(System.in);
super.entradaDialogo();
System.out.println("Tipo: ");
setTipo(entrada.nextLine());
System.out.println("Capacidade: ");
setCapacidade(Integer.parseInt(entrada.nextLine()));
}
}
```

Classe Particionada `PlacaMae`:

```java
import java.util.Scanner;

public class PlacaMae extends Identificacao {
public String tipoProcessador;

public PlacaMae() { }

public PlacaMae(String ma, String mo) {
super(ma,mo);
}
}
```

Classe Particionada `PlacaVideo`:

```java
public class PlacaVideo {

}
```

Classe Particionada `Memoria`:

```java
public class Memoria {
public String marca, modelo, tipo;
public double preco;
public int capacidade;

public String getMarca() {
return marca;
}

public void setMarca(String ma) {
if (!ma.isEmpty()) {
marca = ma;
}
}

public String getModelo() {
return modelo;
}

public void setModelo(String mo) {
if (!mo.isEmpty()) {
modelo = mo;
}
}

public String getTipo() {
return tipo;
}

public void setTipo(String ti) {
if (!ti.isEmpty()) {
tipo = ti;
}
}

public double getPreco() {
return preco;
}

public void setPreco(double pr) {
if (pr > 0) {
preco = pr;
}
}

public int getCapacidade() {
return capacidade;
}

public void setCapacidade(int ca) {
if (ca > 0) {
capacidade = ca;
}
}
}

public class Desktop {
// atributos da classe
public String tipoCooler;

// atributos agregados
public PlacaMae pm = new PlacaMae();
public PlacaVideo pv = new PlacaVideo();
public HD hd = new HD();

public Memoria slot0 = new Memoria();
public Memoria slot1 = new Memoria();
public Memoria slot2 = new Memoria();
public Memoria slot3 = new Memoria();
}

public class AppAgregacao {
public static void main(String[] args) {
Desktop desk = new Desktop();

// para usar algum método do objeto criado a partir da classe agregada,
// devemos usar o identificador do objeto:

// slot0
desk.slot0.setMarca("Samsung");
desk.slot0.setCapacidade(16);

// slot1
desk.slot1.setMarca("Kingston");
desk.slot1.setCapacidade(8);

// slot2
desk.slot2.setMarca("Sandisk");
desk.slot2.setCapacidade(4);

// slot3
desk.slot3.setMarca("Crucial");
desk.slot3.setCapacidade(2);

// total de memória:
System.out.println("Memória total: " +
(desk.slot0.getCapacidade() +
desk.slot1.getCapacidade() +
desk.slot2.getCapacidade() +
desk.slot3.getCapacidade()));
}
}
```

A definição das classes `PlacaMae`, `PlacaVideo` e `HD` (que aparecem como atributos no `Desktop`), podem ser implementadas também.

Com a evolução das classes particionadas com a aplicação do conceito de herança, foram mantidas as compatibilidades e as classes `Desktop`, `Notebook` e `Servidor` não precisam ser alteradas, assim como a aplicação, que funcionará da mesma forma que no exemplo anterior. As mudanças com a evolução das classes particionadas não afetaram as classes agregadoras, nem a aplicação.

Caso seja necessária alguma alteração específica, basta alterar a classe afetada. Entretanto, se for alguma alteração sobre as classes `Memoria` e `HD`, basta alterar a classe `Armazenamento`; caso seja uma alteração que afete ao mesmo tempo as quatro classes particionadas, basta alterar a superclasse `Identificação`.

Temos então um melhor controle sobre a aplicação e maior facilidade de manutenção de todo o sistema.

# ☕ [Java] Encapsulamento

O **encapsulamento** permite uma classe encapsular atributos e métodos, ocultando os detalhes de implementação dos objetos. Trabalharemos também os tipos de visibilidade de membros de uma classe: public, protected, private e package. Desenvolveremos uma aplicação utilizando o conceito de encapsulamento em conjunto com os conceitos de herança e agregação.

No desenvolvimento de aplicações, temos situações nas quais a segurança é muito importante. Em muitas situações os membros de uma classe (atributos e métodos) precisam ter o seu acesso restringido para que não sejam burlados por meio das aplicações. Esse processo de limitação de acesso aos membros de uma classe é chamado de **Programação Orientada a Objetos de Encapsulamento**.

“Encapsulamento trata-se de um mecanismo que possibilita restringir o acesso a variáveis e métodos da classe (ou até à própria classe). Os detalhes de implementação ficam ocultos ao usuário da classe, isto é, o usuário passa a utilizar os serviços da classe sem saber como isso ocorre internamente. Somente uma lista das funcionalidades existentes torna-se disponível ao usuário da classe.” - (FURGERI, 2015)

Encapsulamento é o processo de separação dos membros de uma classe através da restrição ao seu acesso. Pode ocultar os atributos e métodos de uma classe, evitando que dados e detalhes de implementação de métodos sejam vistos (acessados diretamente) pela aplicação ou outras classes. Uma classe encapsula atributos e métodos, ocultando os detalhes de implementação dos objetos. Como um dos princípios do desenvolvimento orientado a objetos, o encapsulamento determina que a implementação de um objeto somente deve ser acessada através de uma interface visível e bem definida. Portanto, esse tipo de conceito é o mais seguro entre todo os conceitos da POO.

Os atributos não devem ser manipulados diretamente, podendo ser alterados ou consultados somente através dos **métodos de acesso** (Setters e Getters) do objeto. Ao restringir o acesso direto aos atributos de uma classe, evitamos que eles sejam manipulados diretamente pela aplicação ou outras classes, não permitindo que possam receber um valor qualquer, principalmente, valores inválidos para o seu contexto. Dessa forma, aumentamos a segurança e a confiança sobre os valores atribuídos.

Por exemplo: Na classe `Exemplo` temos o seguinte atributo

```java
public class Encapsulamento {
int idade;
}
```

Na aplicação:

```java
public class AppEncapsulamento {
public static void main(String[] args) {
// TODO Auto-generated method stub
Encapsulamento capsula = new Encapsulamento();

capsula.idade = -20;

System.out.println("Idade = " + capsula.idade);
}
}
```

Saída:

```java
Idade = -20
```

1. Como não temos restrição sobre o atributo Idade, a aplicação poderá realizar um acesso direto ao atributo;

2. Na aplicação foi realizado um acesso direto ao atributo, sem o uso de um método de acesso (Setter), e o valor atribuído não é válido porque uma pessoa não pode ter idade negativa;

3. Acesso direto aos atributos de uma classe não permitem que sejam realizadas críticas ao valor antes da atribuição, fazendo com que o valor atribuído diretamente (acesso direto) não tenha qualquer tipo de validação.

*Métodos* podem ter sua visibilidade restrita para evitar que detalhes de implementação ou um possível uso indevido possam ser realizados por outras classes ou aplicações. É muito comum que um método aparentemente simples, tal como calcularImposto(double valor, tipoProduto) não se restrinja a um simples cálculo. Ao se definir o tipoProduto, podemos ter diversas e diferentes formas de calcular o imposto sobre esse produto. Podemos então criar um método principal calculaImposto() e, através dele, chamar diversos outros métodos, cada um responsável por calcular o imposto para um determinado tipo de produto.

Realizar o acesso diretamente aos métodos chamados por calculaImposto() pode exigir muito conhecimento por parte do desenvolvedor que irá usar a classe, mas nem sempre este desenvolvedor terá os conhecimentos técnicos necessários. Provavelmente, houve o apoio de um especialista em processos fiscais para que o desenvolvedor original da classe pudesse desenvolver os métodos previstos. Para evitar o uso indevido de métodos que possam ser usados de forma equivocada ou com restrição de segurança, alteramos a visibilidade desses métodos.

Por exemplo: A classe `Tributos`

```java
public class Tributos {
double imposto;

public double calcularImposto(double preco, int tipoProduto){
if(tipoProduto < 10){
// Exemplo: 01, tipo 0, faixa 0
imposto = calcularTipoProduto0(preco, TipoProduto);
} else {

}
}
}
```

Na aplicação: `AppTributos.java`

```java
import java.util.Scanner;

public class AppTributos {
public static void main(String[] args){
// TODO Auto-generated method stub
Scanner entrada = new Scanner(System.in);
double preco = 0, valorPago = 0;
System.out.println("Digite o preco: ");
}
}
```

Saída:


Digite o preco:

1000

Imposto incorreto:

Valor do imposto [atribuição direta] = -20.0

Imposto correto, mas método errado

A faixa estava correta por acaso, porque 00 = 0

Valor do imposto[método errado 00]= 45.0

Imposto e método errados

Valor do imposto [método errado 11]= 131.0

Imposto correto, por acaso, porque 22 foi para a última faixa

Valor a pagar [método errado 22]= 281.0

Imposto e método corretos

Valor a pagar [método correto 00]= 45.0

Imposto e método corretos

Valor a pagar [método correto 00]= 128.0

Imposto e método corretos

Valor a pagar [método correto 00]= 281.0

1. Imaginemos que tipo de produto é um valor entre os seguintes: `[00, 01, 02, 10, 11, 12, 20, 21 e 22]`, em que o primeiro dígito se refere ao tipo do produto e o segundo à faixa do imposto;

2. Primeiro, o método calculaImposto() irá determinar o método a ser usado para cada tipo de imposto, e a faixa será usada dentro do método específico;

3. O método específico será chamado pelo *método principal*, que retornará o valor correto do imposto;

4. Se um desenvolvedor sem conhecimento correto usar diretamente um dos métodos específicos, o cálculo poderá resultar em um valor errado, pois dificilmente ele irá determinar a faixa correta, pois acabará por passar como parâmetro o tipo do produto e não a faixa;

5. Se os testes forem realizados apenas com os tipos entre `00` e `02`, provavelmente o resultado estará correto porque será passada apenas a faixa, mas, para os demais casos, de `10` a `12` ou de `20` a `22`, provavelmente o resultado será `0` (zero) ou calculado pela última faixa;

6. Uma aplicação ainda poderá burlar os cálculos da classe, simplesmente determinando um valor ao atributo imposto [tributo.imposto = -20;], o que resultaria em um imposto incorreto, aumentando o preço do produto; como o imposto é negativo e a operação aritmética é de subtração, haverá um sobre preço sobre o valor.

A falta de conhecimento sobre o uso de uma classe pode gerar erros porque, mesmo realizando testes, as faixas com problemas podem não ser identificadas. Para isso, devemos ocultar parte da implementação da classe.

Atributos não devem ser visíveis por nenhum objeto que não seja instância da própria classe ou de uma classe descendente (herança).

Na linguagem Java, temos quatro diferentes tipos de encapsulamento:


Nível de restrição:
Tipo de Encapsulamento (modificador):



Menor restrição:



Maior restrição:


1. public – acesso irrestrito;

2. (vazio ou omissão) – acesso padrão (package);

3. protected;

4. private.

A relação apresentada está em ordem de nível de restrição, indo do *menos restrito* (public) até o mais *restrito* (private).

Visibilidade:

- `Public`: Uma classe definida como public pode ser acessada por qualquer classe ou aplicação, sem restrições. Seus membros são igualmente acessíveis (visíveis) por qualquer outra classe ou aplicação. Determina o nível menos restritivo de acesso e visibilidade aos membros (atributos e métodos) de uma classe;

- `Private`: Um membro definido como privado só pode ser acessado por membros da própria classe, ou seja, apenas métodos existentes na própria classe poderão ter acesso (visibilidade) aos atributos e métodos definidos como private. É o nível com maior restrição, pois nem mesmo subclasses dessa classe terão visibilidade sobre esses membros;

- `Protected`: Um membro definido como protegido pode ser acessado apenas por membros da própria classe, das suas subclasses e por outras classes ou aplicações que estejam no mesmo pacote (package);

- `Default` (padrão, omissão): Quando não é usado um modificador de encapsulamento, a visibilidade é dita padrão e os membros têm visibilidade, ou seja, só podem ser acessados por classes e aplicações que estejam no mesmo pacote.

Exemplos de visibilidade de membros:

- **Membros públicos**: é a forma normal para métodos de acesso (Setters e Getters).

```java
public class MembrosPublicos {
public String nome;

public void setNome(String no) {
if(!no.isEmpty()) {
nome = no;
}
}

public String getNome() {
return nome;
}
}
```

- **Membros com visibilidade padrão**: devemos evitar o uso do acesso padrão, para que tenhamos sempre a visibilidade definida.

```java
public class AcessoMembrosPadrao {
String nome;
void setNome(String no) {
if(!no.isEmpty()) {
nome = no;
}
}

String getNome() {
return nome;
}
}
```

- **Membros privados**: é a forma normal para os atributos de classe que *não terá subclasses*, mas não é adequada para os métodos de acesso (Setters e Getters).

```java
public class AcessoMembrosPrivados {
private String nome;

public void setNome(String no) {
nome = analisaNome(no);
}

public String getNome() {
return nome;
}

private String analisaNome(String no) {
if(!no.isEmpty()) {
return no;
}

else {
System.out.println("Nome não preenchido!");
return "";
}
}
}
```

- **Membros protegidos**: é a forma normal para os atributos de classe que terão subclasses, mas também não é adequada para os métodos de acesso (Setters e Getters).

```java
public class AcessoMembrosProtegidos {
protected String nome;

public void setNome(String no) {
nome = analisaNome(no);
}

public String getNome(){
return nome;
}

protected String analisaNome(String no) {
if(!no.isEmpty()){
return no;
} else {
System.out.println("Nome não preenchido!");
return "";
}
}
}
```

Como vimos, o encapsulamento determina a visibilidade de classes ou de seus membros. É comum protegermos os atributos de uma classe para que eles não tenham *acesso direto*, e os *valores* a serem atribuídos possam ser analisados por um método antes da atribuição.

# ☕ [Java] Pacotes

**Pacotes**, em Java, são usados para facilitar o armazenamento e controle da biblioteca de classes. Como vimos até o momento, nossa biblioteca de classes vem crescendo e, dessa forma, várias classes foram criadas e estão em diferentes locais. É necessário organizarmos nossas classes e, para isso, podemos usar os pacotes.

Pacotes não passam de uma estrutura de diretórios em que colocamos as nossas classes por afinidade. Por afinidade devemos entender que são classes com algum tipo de aderência, similaridade ou que pertencem a um mesmo assunto.

No projeto criado para nossos exemplos dessa unidade, temos a seguinte estrutura:

![img2](https://user-images.githubusercontent.com/61624336/121275698-a0ec5780-c8a3-11eb-88a6-c28e4d0c7e00.jpg)

Note que temos apenas um pacote, o pacote default do projeto, e todas as classes estão juntas. Para criarmos novos pacotes, basta clicar sobre o projeto e escolher **New / Package**, como você pode observar na imagem a seguir.

![img3](https://user-images.githubusercontent.com/61624336/121276412-fffe9c00-c8a4-11eb-8549-4e824106555e.jpg)

Como foram criados três grupos de classes para os exemplos, vamos separar nossas classes em três pacotes, sendo eles: **parte1**, **parte2** e **parte3**.

![img4](https://user-images.githubusercontent.com/61624336/121276493-26243c00-c8a5-11eb-8a82-170795faebd5.jpg)

Basta agora arrastar as classes para seus respectivos pacotes.

![img5](https://user-images.githubusercontent.com/61624336/121276533-376d4880-c8a5-11eb-8050-f7d634527a1c.jpg)

Fisicamente, os arquivos ficarão dentro dos respectivos diretórios, podendo ainda ser criados subpacotes. A separação em pacotes permite duas ou mais classes com o mesmo nome, bastando que elas estejam em diferentes pacotes.

![img6](https://user-images.githubusercontent.com/61624336/121278098-4f929700-c8a8-11eb-921e-30b7316796c9.jpg)
![img7](https://user-images.githubusercontent.com/61624336/121278091-4e616a00-c8a8-11eb-9dc9-b60bf69da76f.jpg)

Quando temos nossas classes separadas em pacotes, sempre que precisarmos usá-las devemos importar a(s) classe(s) de seus respectivos pacotes.

Dessa forma, criamos um pacote apenas para as aplicações e transferimos as aplicações de encapsulamento para esse pacote. Note que as classes agora estão apresentando erros.

![img8](https://user-images.githubusercontent.com/61624336/121278175-751fa080-c8a8-11eb-8d52-bde6c5a6e7a6.jpg)

Isso ocorre porque as aplicações não estão encontrando as respectivas classes e, para que elas sejam encontradas, devemos importar as classes:

Para a aplicação do primeiro exemplo:

```java
import parte1.Exemplo;
```

Para a aplicação do segundo exemplo:

```java
import parte2.Tributos;
```

Outro ponto importante é que os atributos das classes `Exemplo` e `Tributos` estavam com a visibilidade padrão e, para continuar a funcionar, é necessário alterar a visibilidade dos atributos para pública (`public`), uma vez que essas classes agora estão em diferentes pacotes.

# ☕ [Java] Classes abstratas e interfaces

As **classes abstratas** são usadas como moldes para a criação de outras classes e podem encapsular atributos e comportamentos comuns. Já **interface** é um recurso muito utilizado em Java. Uma classe pode implementar várias interfaces.

Modificadores: `static` e `final`, anteriormente, conhecemos os modificadores de acesso ou de *visibilidade* (Encapsulamento). Agora iremos conhecer mais alguns modificadores que podem ser aplicados sobre classes, no caso do final e sobre membros (Atributos e Métodos) de uma classe. Esses modificadores, como o nome já diz, servem para alterar a forma de uso de classes, métodos e/ou atributos.

O uso do this em Java é para ajudar na questão das referências (endereçamento) de memória. O this é um ponteiro (variável que armazena endereço de memória) de forma implícita. Java não possui *endereçamento direto de memória* (endereçamento explícito), apenas o *endereçamento indireto* (implícito) de memória. A referência this então é uma referência implícita ao endereçamento de memória de um objeto.

A referência this altera a identificação do objeto pelo seu identificador (nome) e pelo endereço de memória do objeto, e *só pode referenciar membros da classe*, ou seja, somente faz referência a *atributos e métodos*.

Dessa forma, podemos substituir o nome do objeto pela sua referência, e também separar quando tem o mesmo nome variáveis e atributos:

```java
package biblioteca;

public class RfThis {
private String nome;
private int idade;
private double peso;

public String getNome() {
return this.nome;
}
// a referência this indica que this.nome
// faz referência ao atributo nome da classe e
// sem o this nome identifica o parâmetro nome do método
public void setNome(String nome) {
this.nome = nome;
}
public int getIdade() {
return this.idade;
}
// a referência this indica que this.nome
// faz referência ao atributo nome da classe e
// sem o this nome identifica o parâmetro nome do método
public void setIdade(int idade) {
this.idade = idade;
}
public double getPeso() {
return this.peso;
}
// a referência this indica que this.nome
// faz referência ao atributo nome da classe e
// sem o this nome identifica o parâmetro nome do método
public void setPeso(double peso) {
this.peso = peso;
}
}
```

> [!Note]
> O `this` separa os membros da classe das demais variáveis auxiliares e parâmetros da classe.

Este modificador `static` pode ser aplicado sobre atributos e métodos e transforma o atributo ou método para a forma “compartilhada”. O modificador `static` aplicado a um atributo, ao ser aplicado em um atributo de uma classe modifica este atributo de objeto (ou de instância) para um atributo de `classe1`.

Exemplo 1 - Aplicação com o compartilhamento de um valor: private static double valorDolar

Classe `Cotacao` (pacote: `biblioteca`):

```java
package biblioteca;

import java.util.Scanner;

public class Cotacao {
private static double valorDolar;
public double getValorDolar() {
return valorDolar;
}

public void setValorDolar(double vd) {
valorDolar = verificarValorDolar(vd);
}

private double verificarValorDolar(double vd) {
if(vd > 0) {
return vd;
} else {
return 0.0;
}
}
public Cotacao() { }
public Cotacao(double valorDolar) {
setValorDolar(valorDolar);
}
public void cadastrar(double valorDolar) {
setValorDolar(valorDolar);
}
public void imprimir(){
System.out.println("Valor do dólar: " + getValorDolar());
}
public void entrada() {
Scanner entrada = new Scanner(System.in);
System.out.println("Qual é o valor do dólar?");
setValorDolar(Double.parseDouble(entrada.nextLine()));
}
public double converterRealParaDolar(double real) {
return real / getValorDolar();
}
}
```

Classe Exemplo1 (pacote: `aplicacao`):

```java
package aplicacao;
import java.util.Scanner;
import biblioteca.Cotacao;

public class Cotacao {
private static double valorDolar;
}

public class Exemplo1 {

public static void main(String[] args) {

double valorReal;

Scanner entrada = new Scanner(System.in);

Cotacao cot_1 = new Cotacao();

cot_1.entrada();

System.out.println();

valorReal = Double.parseDouble(entrada.nextLine());

// Conversão com o objeto cot_1 de R$ 1000,00 com dólar R$ 3.82

cot_1.imprimir();

System.out.printf("Conversão de Real para dólar : U$ %.2f",
cot_1.converterRealParaDolar(valorReal));
System.out.println();

Cotacao cot_2 = new Cotacao(4.15);

// Conversão utilizando os objetos cot_1 e cot_2 com o mesmo valor de R$ 1000,00

cot_1.imprimir();
System.out.printf("Conversão de Real para dólar : U$ %.2f",
cot_1.converterRealParaDolar(valorReal));
System.out.println();

cot_2.imprimir();
System.out.printf("Conversão de Real para dólar : U$ %.2f",
cot_2.converterRealParaDolar(valorReal));
System.out.println();

}
}
```

Execução:


Qual é o Valor do dólar ?

3.82

Quantos reais para a conversão ?

1000

Valor do dólar :3.82

Coversão de Real para dólar : US$ 261,78

Valor do dólar :4.15

Coversão de Real para dólar : US$ 240,96

Valor do dólar :4.15

Coversão de Real para dólar : US$ 240,96

> [!Note]
> 1. Primeiramente foi utilizado valor de R$ 3,82 para a cotação do dólar, com a leitura através do teclado para o objeto `cot_1`, e foi calculada a conversão do valor de R$ 1000,00, resultando em: US$ 261,78;
>
> 2. Depois, foi criado o objeto `cot_2`, com o valor da cotação do dólar definido através do método construtor em R$ 4,15, e foi mantido o mesmo valor de R$ 1000,00 para a conversão;
>
> 3. Ambos os objetos deram o mesmo resultado porque o valor do dólar para a conversão era o mesmo, de R$ 4,15. Isso ocorreu pois, ao alterar o valor da cotação do dólar através do objeto `cot_2`, o objeto `cot_1` também foi afetado, já que o atributo `valorDolar` é compartilhado: private static double valorDolar;

Como o atributo valorDolar é compartilhado, o atributo deixa de ser um atributo de objeto (propriedade) com valor próprio a cada objeto, e passa a ser um atributo de classe, ou seja, passa a ser compartilhado por todas as instâncias. Internamente é criado um ponteiro implícito em que todos os atributos compartilhados da classe apontam para o mesmo endereço de memória. Por isso, qualquer objeto que realize uma alteração em um atributo compartilhado afetará todos os demais objetos criados a partir da mesma classe na aplicação.

A referência this não pode ser usada com *atributos estáticos (static)*, apenas *atributos não compartilhados*, porque a referência dos atributos compartilhados não pertence ao objeto, mas sim à classe.

Exemplo 2 - Aplicação com um contador de objetos criados: private static int contador

Classe `Teste` (pacote: `biblioteca`):

```java
package biblioteca;

public class Teste{
private static int contador;

public Teste() {
contador++;
}

public int getContador(){
return contador;
}
}
```

Classe Exemplo2 (pacote: aplicacao)

```java
package aplicacao;

import biblioteca.Teste;

public class Exemplo2 {
public static void main(String[] args) {
// TODO Auto-generated method stub

Teste t1 = new Teste();
System.out.println("Contador =" + t1.getContador());

Teste t2 = new Teste();
System.out.println("Contador =" + t2.getContador());

Teste t3 = new Teste();
System.out.println("Contador =" + t3.getContador());

Teste t4 = new Teste();
System.out.println("Contador =" + t4.getContador());

Teste t5 = new Teste();
System.out.println("Contador =" + t5.getContador());

// Contador de t1 após a criação dos 5 objetos:
System.out.println("Contador =" + t1.getContador());
}
}
```

Output:


Contador =1

Contador =2

Contador =3

Contador =4

Contador =5

Contador =5

> [!Note]
> 1. Foram criados cinco objetos, mas em nenhum caso foi feito um acesso direto para o atributo contador; cada objeto, ao ser criado, incrementava o contador;
>
> 2. Conforme foram sendo criados os objetos, o valor compartilhado do atributo contador ia sendo atualizado para todos os objetos;
>
> 3. Após a criação do 5º objeto, o objeto t5, o atributo do objeto t1, assim como os demais, compartilhavam o mesmo local de memória para buscar o valor do atributo; por isso, todos os objetos encerraram a aplicação retornando o valor 5 para o atributo contador compartilhado (`static`).

# ☕ [Java] Tratamento de exceções

O **tratamento de exceções** é um importante recurso que permite criar programas tolerantes a falhas. Trata-se de um mecanismo que permite resolver ou ao menos lidar com exceções, muitas vezes evitando que a execução do software seja interrompida, e ela está presente na maioria das linguagens de programação. A linguagem Java se baseou mna linguagem C++ para o tratamento de exceções.

Uma **exceção** é uma condição causada por um erro em tempo de execução que interrompe o fluxo normal de software. Não se trata de um desvio normal para um fluxo alternativo, mas sim previsto ao tempo de execução, ou seja, a exceção leva ao estado desejado. E, esse erro pode ter muitas causas como receber valor `0` ou uso indevido de um array.

Quando uma exceção é criada no Java é criado um objeto chamado de `Exception object` que contém informações sobre o erro, seu tipo e o estado do programa quando o erro ocorreu. Após ser criado esse objeto é entregue para o sistema de execução da máquina virtual Java. E todo esse processo é chamado de lançamento de exceção. Uma vez que a exceção é lançada para o método, o sistema de execução na máquina virtual Java procura na pilha de chamadas "`call stack`" por um método que contém o código para tratar essa exceção.

O bloco de código que tem por finalidade tratar essa exceção é chamado de `Exception handler` (tratador de exceções) e quando ele está rodando é verificado se o tipo de objeto é o mesmo do tratador de excessão pode tratar, se for, ele é considerado adequado e a exceção é passada. Quando o tratador de exceção recebe uma exceção para tratar diz que ele captura uma excessão, por isso, o bloco que trata exceções é designado pela instrução `catch` (capturar).

Caso se o tratador não for adequado para tratar aquela exceção, a busca prossegue até que a máquina virtual Java encontre até que o `exception handler` capaz de tratar a exceção. Se nenhum for encontrado, então, a excessão é entregue ao tratador de exceções padrão da máquina virtual Java. Que imprime as informações de exceção e encerra o programa.

Embora, os recursos de tratamento de exceção não seja a única maneira de lidar com erros de software, ela permite algumas vantagens, tais como:

- A separação de erro do código destinada ao tratamento de erros da execução do software, isso melhora a organização e contribui para facilitar a resolução de problemas.

- propagar o erro para cima na pilha de chamadas, entregando o objeto da exceção diretamente ao método que tem interesse na sua ocorrência, tradicionalmente o código do erro teria que ser propagado método a método no código.

> Para melhor absorção do conhecimento, recomenda-se o uso de computador com o Java Development Kit (JDK) e um IDE (Integrated Development Environment) instalados.

A documentação oficial da linguagem Java explica que o termo exceção é uma abreviatura para a frase **evento excepcional** e o define como “um evento, o qual ocorre durante a execução de um programa, que interrompe o fluxo normal das instruções do programa” (ORACLE AMERICA INC., 2021).

A definição de exceção em software apresentada por Java não é específica da linguagem. Sempre que um evento anormal causa a interrupção no fluxo normal das instruções de um software, há uma exceção. Porém, nem todas as linguagens oferecem mecanismos para lidar com tais problemas. Outras oferecem mecanismos menos sofisticados, como a linguagem C++.

A linguagem Java foi concebida com o intuito de permitir o desenvolvimento de programas seguros. Assim, não é de se surpreender que disponibilize um recurso especificamente projetado para permitir o tratamento de exceções de software. Esse será o objeto de nosso estudo, que buscará lançar as bases para que o futuro profissional de programação seja capaz de explorar os recursos da linguagem Java e produzir softwares de qualidade.

Hierarquia de exceções: No Java, todas as exceções são representadas por classes que fazem parte da hierarquia de `Throwable`. A estrutura principal dessa hierarquia é:

```
java.lang.Throwable
├── java.lang.Error (Exceções da JVM, não tratáveis)
└── java.lang.Exception (Exceções tratáveis)
├── Checked Exceptions (Obrigam tratamento)
├── java.lang.RuntimeException (Unchecked Exceptions)
```

A classe `Throwable` (Raiz da Hierarquia) é a **superclasse de todas as exceções e erros**. Ela define métodos comuns, como `getMessage()` e `printStackTrace()`.

Subtipos diretos de `Throwable`:

✅ `Error` → Indica problemas sérios que o programa **não deve capturar**.
✅ `Exception` → Indica problemas que podem ser **capturados e tratados**.

Error (Erros de Sistema) Os **erros** representam falhas graves da JVM ou do sistema, como falta de memória ou problemas na inicialização da classe.

Exemplos de `Error`:
- `StackOverflowError` → Quando há recursão infinita.
- `OutOfMemoryError` → Quando a JVM fica sem memória.
- `NoClassDefFoundError` → Quando uma classe necessária não pode ser carregada.

> [!Caution]
> Evite capturar `Error`, pois são problemas do sistema, não da aplicação!

Exception (Exceções Verificadas e Não Verificadas) a classe `Exception` representa exceções que **podem ser tratadas** pelo programa.

Ela se divide em duas categorias:

1️. `Checked Exceptions` (Verificadas)
2️. `Unchecked Exceptions` (Não Verificadas)

`Checked Exceptions` (Exceções Verificadas) são **checadas pelo compilador** e obrigam o uso de `try-catch` ou `throws`.

Exemplos de `Checked Exceptions`:
- `IOException` → Problemas de entrada/saída.
- `SQLException` → Erros em bancos de dados.
- `FileNotFoundException` → Arquivo não encontrado.

Exemplo de tratamento obrigatório:

```java
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class Teste {
public static void main(String[] args) {
try {
FileReader file = new FileReader("arquivo.txt");
} catch (IOException e) {
System.out.println("Erro ao abrir arquivo: " + e.getMessage());
}
}
}
```

Se não tratar, o código nem compila!

`Unchecked Exceptions` (Exceções Não Verificadas) também chamadas de **Runtime Exceptions**, não são forçadas pelo compilador e podem ser evitadas com boas práticas.

Exemplos de `Unchecked Exceptions`:
- `NullPointerException` → Quando uma variável `null` é usada.
- `ArrayIndexOutOfBoundsException` → Acesso a índice inválido em arrays.
- `ArithmeticException` → Erro matemático, como divisão por zero.

Exemplo sem tratamento obrigatório:

```java
public class Teste {
public static void main(String[] args) {
String texto = null;
System.out.println(texto.length()); // NullPointerException
}
}
```

> [!Warning]
> O código compila, mas falha em tempo de execução!

Criando Exceções Personalizadas: Podemos criar nossas próprias exceções estendendo `Exception` (checked) ou `RuntimeException` (unchecked).

Exemplo de `Checked Exception` personalizada:

```java
class MinhaExcecao extends Exception {
public MinhaExcecao(String mensagem) {
super(mensagem);
}
}

public class Teste {
static void validar(int idade) throws MinhaExcecao {
if (idade < 18) {
throw new MinhaExcecao("Idade mínima é 18!");
}
}

public static void main(String[] args) {
try {
validar(16);
} catch (MinhaExcecao e) {
System.out.println("Erro: " + e.getMessage());
}
}
}
```

Obrigatório tratar (`throws` ou `try-catch`).

Exemplo de `Unchecked Exception` personalizada:

```java
class ErroDeNegocioException extends RuntimeException {
public ErroDeNegocioException(String mensagem) {
super(mensagem);
}
}

public class Teste {
static void processar(int valor) {
if (valor < 0) {
throw new ErroDeNegocioException("Valor negativo não permitido!");
}
}

public static void main(String[] args) {
processar(-5); // Lança a exceção sem obrigar try-catch
}
}
```

Não precisa de tratamento obrigatório.

| Tipo | Tratamento Obrigatório? | Exemplo |
|------|-----------------|---------|
| `Error` | 🚫 Não | `OutOfMemoryError` |
| `Checked Exception` | ✅ Sim | `IOException`, `SQLException` |
| `Unchecked Exception` | 🚫 Não | `NullPointerException`, `ArithmeticException` |

# ☕ [Java] Java MVC
Agora, você está cruzando os conceitos de MVC (Model View Controller) para criação de aplicações web entre Java, Node.js, PHP e .NET. O *Express.js* é minimalista, pensado para construir APIs e rotas HTTP com bastante flexibilidade, sem impor uma estrutura pesada. Ele não lida com “interface de usuário” diretamente (isso fica a cargo de engines de template ou de um front separado).

No mundo Java, o que se aproxima do *Express.js* não é o JSF/PrimeFaces, mas sim frameworks voltados para rotas e APIs. Os principais equivalentes seriam:

* **Spring Boot (com Spring MVC)** → o mais usado hoje. Você define endpoints com anotações (`@RestController`, `@GetMapping`, etc.), bem parecido com as rotas no Express (`app.get(...)`). Ele também é leve na configuração (principalmente com Boot), mas já vem com muito mais recursos prontos do que o Express puro.

* **Javalin** → este é o **mais próximo ao Express em simplicidade**. É um microframework Java inspirado no Express/Koa. Você cria rotas em poucas linhas (`app.get("/users", ctx -> {...})`).

* **Spark Java** → outro microframework muito parecido com o Express, criado antes do Javalin. Também é bem direto para definir rotas e retornar respostas HTTP.

* **Vert.x** → mais robusto, mas ainda assim leve, focado em aplicações reativas e event-driven. Ele lembra o Express em termos de trabalhar bem com concorrência e eventos.

Agora, se a sua dúvida era mais na linha **JSF/PrimeFaces ↔ Express**, aí temos uma diferença conceitual importante:

* **JSF/PrimeFaces** = frameworks de **camada de apresentação (UI)**, baseados em componentes de interface.
* **Express.js** = framework de **backend HTTP**, que só expõe rotas e serviços, sem UI nativa.

Ou seja, JSF/PrimeFaces estão mais próximos de algo como Blazor/Razor Pages no .NET, enquanto o Express estaria mais próximo de Spring Boot, Javalin ou Spark Java no ecossistema Java.

Pergunto: você pensou nessa comparação porque está avaliando migrar tanto a **camada de UI (JSF/PrimeFaces)** quanto a **camada de backend (Servlets/Java EE)**, ou queria entender só a equivalência da parte de **backend tipo Express**? Porque muita gente associa **MVC em Java** diretamente ao **Spring MVC**, mas a história começou bem antes. O ecossistema Java teve uma evolução longa de frameworks web, cada um tentando melhorar as dores da geração anterior.

Antes do Spring MVC (2003), já existiam várias opções para implementar MVC em Java:

Nos anos 90, a base de tudo eram os **Servlets** (lançados em 1997, com J2EE 1.2). Eles eram “o controlador cru”, processando requisições HTTP diretamente. Para a camada de apresentação, surgiu o **JSP (Java Server Pages)** em 1999, que junto com **JSTL/EL** ajudava a aplicar o padrão MVC separando a View do Controller. Só que JSP+Servlets ainda deixavam muito boilerplate e não forçavam uma arquitetura consistente.

Em 1999–2000, surgiram os primeiros frameworks MVC *de fato* no ecossistema Java:

* **Apache Struts (2000)** → pioneiro do MVC em Java. Foi o framework que realmente consolidou o modelo **Front Controller + Action + JSP**. Era o “pai” de todos os frameworks web Java.
* **JavaServer Faces (JSF, 2004)** → oficializado pela Oracle como padrão Java EE para UI component-based. Embora seja posterior ao Struts, sua especificação começou antes do Spring MVC ganhar popularidade.
* **Tapestry (2000)** → concorrente do Struts, trazia um modelo de componentes orientado a objetos, parecido com o que anos depois seria comum em frameworks SPA.
* **WebWork (2001)** → framework criado pela comunidade, com ideias mais flexíveis que o Struts. Mais tarde foi incorporado ao Struts 2.
* **Wicket (2005)** → modelo baseado em componentes, mas mais leve que o JSF.

Então a linha do tempo, em resumo:

* **1997** → Servlets (base de tudo).
* **1999** → JSP.
* **2000** → Apache Struts (primeiro grande MVC).
* **2000** → Tapestry.
* **2001** → WebWork (virou Struts 2).
* **2004** → JSF.
* **2003** → Spring MVC (ganhou espaço depois do Struts).
* **2005** → Wicket.

Ou seja, antes do Spring MVC já existiam **Struts, Tapestry, WebWork e JSF**, todos tentando aplicar MVC no mundo Java. O Spring só dominou porque trouxe menos configuração, mais integração com o container de IoC e mais produtividade em comparação com o Struts pesadão e o JSF burocrático.

## [Java] JSP (Java Server Pages)
O JSP e JSF fazem parte do ecossistema Java/Kotlin para desenvolvimento web e surgiram como formas diferentes de construir aplicações Java na camada de apresentação (a interface com o usuário).

O **JSP (Java Server Pages)** nasceu nos anos 1990 como uma tecnologia simples para gerar páginas dinâmicas em Java. O JSP funciona de forma parecida ao **PHP ou ASP clássico ou EJS**: você escreve código HTML e intercala trechos de Java entre `<% ... %>`. Esse código Java é processado no servidor, e o resultado (HTML puro) é enviado ao navegador. Com o tempo, o uso de **tags libraries (JSTL)** e **EL (Expression Language)** deixou o JSP mais limpo e menos “misturado” com código Java. O JSP é, portanto, uma solução de **template engine** para a web no mundo Java.

## [Java] JSF (JavaServer Faces)
Já o **JSF (JavaServer Faces)** veio depois, como uma evolução mais estruturada. É um **framework MVC para a camada de apresentação** padronizado pela especificação Java EE (hoje Jakarta EE). Diferente do JSP, o JSF é **component-based**: em vez de você misturar HTML e Java, você trabalha com **componentes reutilizáveis (botões, tabelas, inputs, etc.)** definidos em XML/Facelets, e os eventos (como cliques ou submits) são tratados por *managed beans* (objetos Java). Isso traz uma separação de responsabilidades melhor e torna o desenvolvimento mais organizado.

Para resumir:

* **JSP** → mais simples, estilo “template dinâmico”, rápido para páginas, mas tende a virar “bagunça” se o projeto crescer muito.
* **JSF** → mais robusto, baseado em componentes e ciclo de vida controlado, indicado para aplicações corporativas com muita lógica de interface.

Hoje em dia, tanto JSP quanto JSF perderam espaço para frameworks modernos (como Spring MVC/Thymeleaf, Spring Boot com APIs REST, ou até front-ends separados como React, Angular e Vue). Mas em sistemas legados e grandes soluções corporativas, principalmente dentro de empresas que adotaram **Jakarta EE**, você ainda encontra bastante JSF.

## [Java] Hibernate

No ecossistema Java, o uso de ORM (Object-Relational Mapping) é bastante consolidado e fundamental para aplicações que interagem com bancos de dados relacionais. O objetivo de um ORM é mapear as classes e objetos do código Java para tabelas e registros em bancos de dados, facilitando o desenvolvimento ao esconder as complexidades do SQL puro. O framework mais popular e amplamente adotado para isso em Java é o **Hibernate**, que inclusive serve de base para outros frameworks, como o JPA (Java Persistence API).

O **Hibernate** é uma implementação completa de ORM, oferecendo recursos como mapeamento de entidades, relacionamento entre tabelas (um-para-um, um-para-muitos, muitos-para-muitos), cache, lazy loading e suporte a bancos como MySQL, PostgreSQL, Oracle, SQL Server, entre outros. O Hibernate pode ser usado diretamente, mas geralmente ele é utilizado através do padrão JPA, que é uma especificação oficial da plataforma Java EE (e hoje também do Jakarta EE). O **JPA** define uma API de persistência e o Hibernate é a implementação mais comum dessa especificação, embora existam outras, como **EclipseLink** e **OpenJPA**.

Outro framework importante, especialmente no contexto do Spring, é o **Spring Data JPA**, que abstrai ainda mais a complexidade de persistência e permite definir interfaces para acessar dados com métodos simples como `findByEmail()` ou `findAllByStatus()`, sem a necessidade de escrever consultas SQL ou JPQL diretamente. Ele integra de forma fluida com Hibernate e JPA por trás dos panos.

Além desses, há também bibliotecas mais leves ou com propostas diferentes:

* **MyBatis** (antigo iBATIS) é uma alternativa ao ORM completo. Ele não faz mapeamento automático de objetos, mas permite escrever SQL diretamente e mapear os resultados para objetos Java, sendo mais controlado e flexível para casos complexos.
* **jOOQ** (Java Object Oriented Querying) foca em oferecer uma forma fluente e tipada de escrever SQL diretamente em Java, mantendo controle total da consulta, mas com segurança e estrutura orientada a objetos.
* **Ebean ORM** é outra alternativa mais moderna e leve que foca em simplicidade, oferecendo uma experiência semelhante ao Active Record do Ruby on Rails, mas ainda assim com suporte a JPA.

Para bancos de dados NoSQL, frameworks como **Spring Data MongoDB**, **Spring Data Redis**, **Morphia** (para MongoDB), entre outros, também seguem a ideia de facilitar o acesso a dados, mas fora do modelo relacional tradicional.

Portanto, os principais frameworks e bibliotecas de ORM e persistência em Java são: **Hibernate**, **JPA**, **Spring Data JPA**, **MyBatis**, **jOOQ**, **Ebean** e **EclipseLink**, sendo o trio Hibernate + JPA + Spring Data o mais comum nas aplicações modernas.

O **JDBC (Java Database Connectivity)** é a API padrão da linguagem Java para acesso a bancos de dados relacionais. Ele permite que aplicações Java se conectem, consultem, insiram, atualizem e removam dados em bancos como MySQL, PostgreSQL, Oracle, SQL Server, entre outros, usando SQL diretamente. Diferente dos frameworks ORM como Hibernate ou JPA, o JDBC trabalha em um nível mais baixo e exige que o desenvolvedor escreva manualmente as instruções SQL e gerencie aspectos como conexões, prepared statements, result sets e fechamento de recursos.

Resumidamente, o JDBC é a ponte direta entre o Java e o SQL, essencial para quem quer ou precisa de controle absoluto nas operações com banco de dados, mas que pode ser substituído por abstrações de alto nível em sistemas maiores para reduzir complexidade e repetição de código.

Captura de tela 2026-01-15 214044

Isso significa que você escreve comandos SQL manualmente, como `SELECT`, `INSERT`, `UPDATE` e `DELETE`, e os passa diretamente para o mecanismo de execução — sem mapeamento automático de objetos como nos ORMs (tipo Hibernate, Doctrine, Prisma, etc.).

JDBC (em Java), pg-promise (em Node.js) e PDO (em PHP) não são tecnicamente "SQL Embutidos" (Embedded SQL) no sentido acadêmico, mas todos trabalham com SQL cru (raw SQL) dentro do código, geralmente como strings ou templates, integradas diretamente nas funções ou métodos que interagem com o banco de dados. Então sim, o JDBC, pg-promise e PDO permitem trabalhar com SQL cru dentro da linguagem, o que te dá total controle, mas também exige cuidado com coisas como SQL Injection, repetição de código e legibilidade. Esses são exemplos de abordagens imperativas e explícitas de acesso a banco de dados usando SQL "na unha", e não abstrações como as que ORMs ou Query Builders oferecem.

A estrutura básica de uso do JDBC envolve alguns passos principais: carregar o driver JDBC apropriado para o banco (o JAR fornecido pelo fabricante), abrir uma conexão com o banco via `DriverManager`, preparar comandos SQL com `PreparedStatement` ou `Statement`, executar consultas com `executeQuery()` ou atualizações com `executeUpdate()`, e iterar sobre os resultados com `ResultSet`. Tudo isso geralmente ocorre dentro de blocos `try-with-resources` ou com tratamento explícito de exceções e fechamento manual de recursos, o que pode ser verboso, mas oferece total controle da execução.

Um exemplo simples de uso do JDBC seria: importar o driver JDBC do MySQL, abrir uma conexão com `DriverManager.getConnection(url, user, password)`, criar um `PreparedStatement` com um `SELECT` ou `INSERT`, executar a consulta e percorrer os dados com um `ResultSet`. Por ser tão fundamental, o JDBC serve como a base para bibliotecas mais sofisticadas como o Hibernate, JPA e Spring Data JPA, que por trás dos panos também usam JDBC para comunicação com o banco, mas automatizam e simplificam esse processo.

## [Java] JasperReports
**JasperReports** é uma biblioteca de código aberto em Java usada para gerar relatórios dinâmicos e bem formatados a partir de diversas fontes de dados, como bancos de dados relacionais, arquivos XML, coleções Java e até arquivos CSV. Ela permite criar relatórios que podem incluir tabelas, gráficos, imagens, sub-relatórios e textos estilizados, sendo amplamente utilizada em aplicações corporativas que precisam apresentar informações de forma organizada, profissional e exportável.

Esses relatórios podem ser gerados em diversos formatos como PDF, HTML, XLS, DOC, RTF, ODT e até imagens, o que dá bastante flexibilidade para integrá-los em sistemas de gestão, ERPs, sistemas bancários, entre outros. O JasperReports geralmente é usado em conjunto com o iReport Designer ou Jaspersoft Studio, que são ferramentas gráficas para desenhar visualmente os templates dos relatórios (arquivos `.jrxml`), permitindo que desenvolvedores e analistas criem relatórios complexos sem escrever tudo manualmente. Depois de desenhado, o template é compilado para um arquivo .jasper, que é interpretado em tempo de execução pelo JasperReports para gerar o relatório final com os dados preenchidos.

Ele também permite a criação de relatórios interativos com parâmetros, filtros e agrupamentos dinâmicos, e por isso é uma solução poderosa para quem precisa de funcionalidades avançadas de Business Intelligence (BI) embutidas diretamente nas aplicações Java.

O *JasperReports* é considerado uma biblioteca de Business Intelligence (BI) no ecossistema Java, mas com um foco diferente do **Streamlit**, que é mais voltado à construção de aplicações interativas para ciência de dados em Python. JasperReports é uma ferramenta de BI da linguagem Java, mas com foco em relatórios formais e impressão, diferente da proposta mais interativa e exploratória do Streamlit no Python.

Enquanto o *Streamlit* permite criar dashboards e apps com gráficos interativos usando poucas linhas de código Python, o *JasperReports* é especializado em geração de relatórios complexos e formatados, como PDFs com tabelas, faturas, recibos, gráficos, sub-relatórios e layouts sofisticados. Ele é muito utilizado em ambientes corporativos que precisam gerar documentos formais ou relatórios empresariais, com grande capacidade de personalização visual.

Ou seja, ambos podem ser usados em contextos de BI, mas com propósitos distintos. JasperReports é mais forte em documentos estáticos e formais, enquanto Streamlit é mais voltado a dashboards dinâmicos e prototipagem de interfaces analíticas. Além disso, o JasperReports se integra profundamente com o ecossistema Java, podendo ser embutido em aplicações Java Web, Spring Boot, JavaFX ou qualquer sistema backend que utilize Java, sendo usado frequentemente com o Jaspersoft Studio para desenhar os relatórios visualmente.

# 🪶 [Java] Apache Maven e Gradle

O **Apache Maven** é uma ferramenta de automação de construção e gerenciamento de projetos, especialmente para projetos Java, mas também pode ser usada para outras linguagens. Ele simplifica o processo de construção, gerenciamento de dependências e geração de relatórios. O Maven é baseado no conceito de Project Object Model (POM), onde as dependências do projeto são definidas em um arquivo XML.

Ela fornece às equipes de desenvolvimento uma forma padronizada de automação, construção e publicação de suas aplicações, agregando agilidade e qualidade ao produto final. Por ser extremamente flexível, permite que sejam adicionados plugins a si, para estender suas funcionalidades nativas. O Maven baixa bibliotecas Java e seus plug-ins dinamicamente de um ou mais repositórios, como o Maven 2 Central Repository, e armazena-os em uma área de cache local. Este cache local de artefatos baixados pode também ser atualizado com artefatos criados por projetos locais. Repositórios públicos podem também ser atualizados.

O processo de criação de um projeto Java EE em geral envolve a criação de um diretório principal com vários subdiretórios, a configuração de diversos arquivos XML, a obtenção (via cópia ou download) de bibliotecas para o projeto e, posteriormente, a execução dos testes unitários, a criação dos pacotes de publicação, a geração de documentação `javadoc`, entre outras etapas. Normalmente, até algum tempo atrás, cada projeto tinha sua própria estrutura, seu próprio jeito de gerar pacotes, de efetuar cada um destes passos. Projetos complexos, com vários módulos, ainda podem precisar que estes sejam compilados em determinada ordem, para que o pacote final seja criado.

Como se pode imaginar, facilmente isso poderia se tornar um pesadelo para os projetos em manutenção ou com um desenvolvimento mais extenso, em equipes que envolvam alguns desenvolvedores, porque as mudanças estruturais realizadas por um desenvolvedor têm que ser comunicadas a cada um dos demais colegas, para que o projeto continue a compilar no computador de cada um. Considere, por exemplo, que o desenvolvedor A precisou incluir uma biblioteca. Antes de colocar no controle de versão o código que a utiliza, ele precisaria avisar a cada um de seus colegas que adicionou algo novo no projeto, dizer de onde baixar o novo componente (ou passá-lo de computador em computador) e se assegurar que todos equalizaram seus ambientes de desenvolvimento, para que o código continue compilando nas máquinas de seus colegas. Tempos depois, o desenvolvedor B precisa efetuar uma manutenção no projeto e descobre que uma versão nova da biblioteca supracitada fornece uma funcionalidade nova, que facilitará o seu trabalho. Mais uma vez, o ciclo de aviso e cópia do novo componente começa, com perda de tempo da equipe e correndo o risco de algum computador ficar desatualizado e um desenvolvedor perder tempo tentando achar um erro acarretado pelo uso da biblioteca antiga.

Outra situação possível, em um projeto de vários módulos: um desenvolvedor modifica um dos módulos básicos do sistema e não avisa os demais colegas, ou estes não deram atenção ao e-mail de alerta. Como resultado o código ou para de compilar ou passa a ter comportamento instável, porque o módulo alterado não foi recompilado nos computadores do restante da equipe.

Agora, imagine os cenários acima em uma fábrica de software, em que o ritmo de trabalho é intenso e os prazos impõem pressão às equipes. Facilmente podemos prever que bibliotecas serão adicionadas sem que o restante do time seja avisado, acarretando impedimento de compilação do código mais recente do projeto. Pessoas modificariam módulos e se esqueceriam de avisar as demais para pegarem a versão mais recente de código e recompilar as alterações; mudanças no processo de geração de pacotes que não são comunicadas, e daí em diante.

Finalmente, se forem consideradas equipes distribuídas, com seus membros trabalhando em home office, a complexidade de propagar mudanças estruturais do projeto aumenta muito. Nesses casos, até mesmo iniciar o projeto podia trazer muitas dores de cabeça, até toda a equipe conseguir estabilizar o ambiente de desenvolvimento de cada participante.

O Maven é construído utilizando uma arquitetura baseada em plugin, que permite que ele faça uso de qualquer aplicação controlável através da entrada padrão. Teoricamente, isto permitiria qualquer um escrever plugins para fazer interface com ferramentas de construção (compiladores, ferramentas de teste de unidade, etc.) para qualquer outra linguagem. De fato, o suporte e uso para linguagens diferentes de Java tem sido mínimas. Atualmente existe um plugin para o framework .NET e é mantido, e um plugin nativo C/C++ é mantido para o Maven 2.

Projetos Maven são configurados usando um Project Object Model, que é armazenado em um arquivo `pom.xml`. A seguir está um exemplo mínimo:

```xml


4.0.0

com.mycompany.app
my-app
1.0


junit
junit
3.8.1

test


```

**Gradle** é uma ferramenta de automação de builds, ou seja, um sistema que organiza, compila, empacota, testa e implanta projetos de software de forma automatizada e padronizada. Ele é muito usado em projetos Java, Kotlin, Android e também em outras linguagens, e é uma alternativa moderna ao Maven e ao mais antigo Ant. Em resumo, o Gradle é a engrenagem que automatiza tudo o que seu projeto precisa para rodar, testar, empacotar e evoluir com segurança e eficiência. Ele é altamente configurável, moderno e muito usado em grandes ecossistemas como o Spring e o Android.

Em vez de você ter que compilar arquivos `.java` na mão, copiar dependências, gerar arquivos `.jar`, rodar testes um por um, o Gradle faz tudo isso por você com comandos simples como `gradle build`, `gradle test`, `gradle run`, entre outros.

A principal vantagem do Gradle está na sua flexibilidade e desempenho. Ele usa uma linguagem de script baseada em **Groovy** (ou **Kotlin DSL**) para definir os passos do build no arquivo `build.gradle` (ou `build.gradle.kts`, se for em Kotlin). Isso permite customizar praticamente tudo, com uma sintaxe mais enxuta que a do Maven (que usa XML).

Outra característica marcante do Gradle é o seu sistema de cache inteligente e builds incrementais, que evita refazer tarefas que não mudaram, acelerando muito o processo em projetos grandes.

Além disso, ele possui suporte nativo a resolução de dependências via repositórios como Maven Central ou JCenter. Isso significa que você pode declarar que precisa, por exemplo, da biblioteca do **Lombok** ou do Spring Boot com apenas uma linha, e o Gradle baixa tudo automaticamente.

![FB_IMG_1724386160785](https://github.com/user-attachments/assets/9806f255-6b70-457e-a039-c6a372755ecd)

Se você está em um projeto Android, inclusive, o Gradle já é o sistema de build padrão do Android Studio, então você provavelmente está usando ele sem perceber.

**SonarQube** é uma plataforma de análise contínua da qualidade do código-fonte. Ele examina seu código em busca de bugs, vulnerabilidades de segurança, duplicações, má formatação, complexidade excessiva, falhas em padrões de codificação e muito mais. Seu principal objetivo é ajudar equipes de desenvolvimento a manterem um código limpo, seguro e sustentável ao longo do tempo. Em resumo, SonarQube é uma ferramenta essencial para times que querem manter o código limpo, seguro e com alta manutenibilidade, funcionando como uma espécie de "fiscal automático" da qualidade do seu repositório.

Quando integrado ao processo de desenvolvimento (como em pipelines de CI/CD ou IDEs), o SonarQube age como um **raio-X do projeto**, apontando problemas antes que eles virem dívida técnica ou bugs em produção. Ele suporta diversas linguagens, incluindo Java, JavaScript, Python, C#, C/C++, Kotlin, TypeScript, entre muitas outras.

A análise é feita por meio de uma **engine estática**, ou seja, sem executar o código — apenas lendo e analisando sua estrutura, regras, estilo e complexidade. Cada regra detectada é classificada com severidades (por exemplo: **code smell**, **bug**, ou **security vulnerability**) e o sistema atribui uma **nota geral de qualidade** ao projeto (como uma média escolar), com níveis de cobertura de testes, duplicação de código, quantidade de problemas críticos, etc.

O SonarQube também se integra com ferramentas como **Maven**, **Gradle**, **Jenkins**, **GitLab CI**, **GitHub Actions**, e pode ser acessado via interface web para acompanhamento em tempo real, por desenvolvedores, QA e líderes técnicos.

A ideia central do SonarQube é criar uma cultura de qualidade de software, onde cada commit, pull request ou build é avaliado com critérios automáticos e objetivos — evitando que más práticas ou falhas passem despercebidas e se acumulem.

# 🤖 [Java] Primefaces

O **PrimeFaces** é uma biblioteca de componentes de interface de usuário (UI) de código aberto que é usada no desenvolvimento de aplicativos web Java Enterprise Edition (Java EE). Ela fornece uma ampla variedade de componentes de interface de usuário ricos e predefinidos que podem ser facilmente incorporados em aplicativos web Java, permitindo aos desenvolvedores criar interfaces de usuário atraentes e funcionais de maneira eficiente.

Alguns dos principais recursos e componentes fornecidos pelo PrimeFaces incluem:

- Componentes de interface de usuário: Botões, tabelas, árvores, menus, gráficos, calendários e muitos outros componentes prontos para uso, que podem ser personalizados e estilizados de acordo com as necessidades do aplicativo.

- Ajax e atualizações dinâmicas: O PrimeFaces facilita a criação de aplicativos web interativos, permitindo a atualização de partes específicas da página sem a necessidade de recarregar a página inteira, graças ao uso do Ajax.

- Temas e estilos: O PrimeFaces oferece suporte a temas personalizáveis que permitem aos desenvolvedores escolher entre várias opções de aparência para seus aplicativos.

- Integração com o Java EE: O PrimeFaces é especialmente projetado para ser usado em conjunto com tecnologias Java EE, como JavaServer Faces (JSF), e funciona bem com servidores de aplicativos Java EE, como o Apache Tomcat, o WildFly (anteriormente conhecido como JBoss) e o Oracle WebLogic.

- Comunidade ativa: O projeto PrimeFaces possui uma comunidade ativa de desenvolvedores e uma documentação abrangente, o que facilita o aprendizado e a resolução de problemas.

O PrimeFaces é uma opção popular para o desenvolvimento de aplicativos web Java que desejam fornecer interfaces de usuário ricas e interativas. Ele é amplamente usado em várias indústrias e fornece uma base sólida para a criação de aplicativos web empresariais.

Primefaces é parecido com Razor Pages, eles têm semelhanças, mas também diferenças importantes porque vêm de ecossistemas distintos (Java no caso do PrimeFaces/JSF e .NET no caso do Razor Pages).

Destrinchando, o **PrimeFaces** é um *UI framework* que roda em cima do **JSF (JavaServer Faces)**. Ele fornece **componentes ricos e prontos** (grids, calendários, gráficos, diálogos modais, etc.), que você pode usar em suas páginas Facelets (`.xhtml`). Ele segue o paradigma **component-based**, ou seja, cada elemento da tela é um componente com propriedades, eventos e ciclo de vida controlado pelo JSF. A ideia é que o desenvolvedor Java consiga construir interfaces complexas com pouco código HTML e sem se preocupar com o JavaScript por trás — o PrimeFaces já abstrai isso.

Já o **Razor Pages** é um modelo de desenvolvimento do **ASP.NET Core**, introduzido como uma alternativa simplificada ao MVC. Ele usa páginas `.cshtml` (HTML + sintaxe Razor para misturar com C#) e é **page-based**, não component-based. Ou seja, cada página é autocontida: você tem o HTML + o code-behind (um arquivo `.cs`) que trata os eventos daquela página. Ele é parecido com o antigo WebForms no conceito de "página com lógica associada", mas muito mais enxuto e moderno.

Onde eles se parecem:

* Ambos buscam **facilitar o desenvolvimento full-stack sem precisar de muito JavaScript ou frameworks externos**.
* Em ambos, você consegue declarar elementos visuais diretamente em HTML/XML com marcações adicionais, e eles se ligam à lógica no back-end.
* São usados em **aplicações server-side renderizadas** com forte integração entre front e back.

Onde eles diferem:

* **PrimeFaces/JSF**: orientado a **componentes reutilizáveis**. Você monta a tela com tags ``, `` etc., e o JSF gerencia o ciclo de vida e eventos.
* **Razor Pages**: orientado a **páginas**. O foco é simplicidade — cada página tem sua lógica de forma clara, mas não há um ecossistema tão rico de componentes visuais prontos quanto no PrimeFaces.

Dá pra pensar assim: **PrimeFaces está mais próximo de Blazor Components** do que de Razor Pages. Porque o Blazor, sim, é component-based como o JSF/PrimeFaces. O Razor Pages é mais um meio-termo entre o MVC tradicional e a simplicidade de “páginas com lógica”.

Então: **PrimeFaces ≈ Blazor Components (pela ideia de componentes)**.

Enquanto isso: **Razor Pages ≈ Facelets “simples” sem PrimeFaces (pela ideia de page-based)**.

# 🧩 [Java] Design Patterns

Na engenharia de software, um **padrão de projeto** ou **padrões de design** (design patterns) de software é uma solução geral e reutilizável para um problema comum dentro de um determinado contexto no projeto de software no nível do código-fonte.

Não é um projeto acabado que pode ser transformado diretamente em código-fonte ou código de máquina. Se você é um desenvolvedor, é importante que você saiba qual é o princípio do SOLID e como um design pattern (padrão de design) deve resolver problemas regulares. Portanto, Design Patterns (Padrões de Projeto) são sobre design de software, eles representam soluções reutilizáveis para problemas recorrentes no desenvolvimento de software, oferecendo uma forma estruturada e eficiente de abordar desafios comuns de design.

Os padrões de design mais comuns que podem ser implementados em Java, assim como na maioria das linguagens de programação, estão organizados em três categorias principais. Abaixo, veja o que você precisa saber sobre os padrões de cada uma delas:

- **Creational** (criacional): Responsáveis pela criação de objetos, oferecendo maior flexibilidade e abstração no processo de instanciação.

- **Structural** (estrutural): Dizem respeito à composição de classes e objetos para formar estruturas maiores e mais eficientes.

- **Behavioural** (comportamental): Lidam com a comunicação e interação entre objetos, definindo padrões de fluxo de responsabilidade.

![Types-of-Design-Pattern-in-Java](https://github.com/user-attachments/assets/66c3cac3-10a3-447e-9a99-2da9ab60d8ee)

Mas primeiro, o que é um padrão de design? Um padrão de design pode ser pensado como uma solução razoável que pode ser aplicada a cenários comuns de programação; como desenvolvedor, você encontrará padrões de design em toda parte. A API do Java sozinha resolve a maioria de seus desafios de programação com diferentes padrões de design; podemos pensar em um padrão de design como uma solução para um problema comum e também como uma estrutura que tornará o código mais fácil de manter. Por último, mas não menos importante, ele fornece mais credibilidade para o código, tornando-o mais fácil de ler.

# 🦖 [Java] VRaptor
**VRaptor** é um framework web baseado em Java que segue o padrão MVC (Model-View-Controller) e foi bastante utilizado no Brasil, principalmente em projetos desenvolvidos por empresas que buscavam uma abordagem mais simples, flexível e moderna do que o tradicional Java EE. Ele foi criado para facilitar o desenvolvimento de aplicações web de forma mais produtiva e menos burocrática, reduzindo a verbosidade típica do Java, especialmente nas versões mais antigas da linguagem.

Com o VRaptor, os controladores (controllers) são implementados como classes Java comuns, usando anotações para mapear rotas HTTP, como `@Get`, `@Post` ou `@Path`. O framework se integra naturalmente com outras bibliotecas do ecossistema Java, como Hibernate para persistência de dados e JSP para a camada de visualização, embora também permita o uso de outras tecnologias para as views. Ele também tem uma boa integração com CDI (Contexts and Dependency Injection), tornando a injeção de dependências algo simples e direto.

Um dos pontos fortes do VRaptor é sua curva de aprendizado mais amigável em comparação com frameworks mais complexos, como JSF ou Spring MVC, além de permitir um desenvolvimento mais “limpo”, com foco em boas práticas como a inversão de controle e responsabilidade única. Outro diferencial é que, por ser mantido principalmente por desenvolvedores brasileiros (por exemplo, da Caelum), ele conta com documentação em português e uma comunidade local ativa, o que ajudou muito a sua adoção em empresas e cursos de formação.

Apesar disso, o VRaptor perdeu espaço nos últimos anos com a ascensão de frameworks mais modernos e populares como Spring Boot, que oferece uma abordagem mais integrada e estável para aplicações completas. Ainda assim, o VRaptor foi um passo importante para tornar o desenvolvimento web em Java mais pragmático e voltado à produtividade, influenciando a forma como muitos desenvolvedores brasileiros aprenderam a construir aplicações web em Java.

# ☕ [Java] Akka

O **Akka** é um toolkit e runtime para construção de sistemas concorrentes, distribuídos e resilientes na JVM, muito usado com Scala e também com Java. A ideia central do Akka é baseada no modelo de atores, que é uma forma de lidar com concorrência sem cair nos problemas clássicos de threads compartilhando estado, como deadlocks e condições de corrida. Em vez de vários threads acessando o mesmo objeto ao mesmo tempo, você tem “atores”, que são entidades independentes que mantêm seu próprio estado interno e se comunicam exclusivamente por troca de mensagens assíncronas. Cada ator processa uma mensagem por vez, o que simplifica muito o raciocínio sobre concorrência e torna o sistema mais previsível e fácil de escalar.

Na prática, o Akka resolve um problema que você conhece bem no backend: como lidar com múltiplas requisições, filas, eventos e processamento paralelo de forma segura e eficiente. Em vez de você gerenciar manualmente pools de threads, locks e sincronização, o Akka abstrai isso com um runtime que distribui os atores em threads de forma eficiente. Além disso, ele traz conceitos importantes como supervisão, onde atores podem monitorar outros atores e reiniciá-los em caso de falha, o que é essencial para sistemas resilientes. Isso conecta diretamente com arquiteturas reativas e distribuídas, onde falhas são esperadas e o sistema precisa continuar funcionando mesmo assim.

Já o **Akka Streams** é uma camada construída sobre o Akka que foca especificamente em processamento de streams de dados. Ele implementa o padrão Reactive Streams, que define como lidar com fluxos assíncronos de dados com controle de backpressure. O conceito de backpressure é fundamental aqui: significa que o consumidor de dados pode sinalizar ao produtor para reduzir a velocidade, evitando sobrecarga e consumo excessivo de memória. Isso é algo crítico em sistemas de streaming, pipelines de dados e processamento em tempo real.

Enquanto o Akka “puro” te dá atores e mensagens para você montar sua lógica concorrente, o Akka Streams te dá uma forma declarativa de construir pipelines de dados, muito parecido com o que você vê em sistemas como pipelines de dados em nuvem. Você trabalha com fontes (sources), fluxos (flows) e destinos (sinks), encadeando operações como transformação, filtragem, agregação e roteamento de dados. Por baixo dos panos, o Akka Streams usa atores para executar essas operações, mas você não precisa lidar diretamente com eles, o que reduz bastante a complexidade.

Um ponto interessante é que o Akka Streams se encaixa muito bem em cenários como processamento de eventos, integração com filas (como Kafka ou RabbitMQ, que você já usa), ingestão de dados em tempo real e até pipelines de vídeo ou logs. Ele permite construir fluxos altamente escaláveis e resilientes, respeitando limites de throughput e evitando gargalos. Isso conversa diretamente com conceitos que você já domina em observabilidade, porque você pode monitorar latência, throughput e comportamento dos fluxos em produção.

Resumindo de forma mais direta: o Akka é a base para construir sistemas concorrentes e distribuídos usando atores, enquanto o Akka Streams é uma abstração mais alta para construir pipelines de processamento de dados em fluxo, com controle de backpressure e execução eficiente. Juntos, eles formam um ecossistema muito poderoso para sistemas reativos, especialmente quando você precisa lidar com alta concorrência, dados em tempo real e tolerância a falhas em escala.

# 🍃 [Java] Spring e Spring Boot

O **Spring** é um framework open source desenvolvido para a plataforma Java baseado nos padrões de projetos (design patterns) inversão de controle (IoC) e injeção de dependência (DI) criado em 2002 por Rod Johnson. Sua estrutura é composta por módulos afins de reduzir a complexidade no desenvolvimento aplicações simples ou corporativa. O Spring Framework fornece infraestrutura abrangente para desenvolver aplicações Java robustas e escaláveis. Ele facilita a inversão de controle (IoC) e a injeção de dependências (DI), além de oferecer suporte para desenvolvimento de aplicativos web, segurança, transações, persistência de dados, entre outros.

Spring é conhecido por sua modularidade e capacidade de integrar facilmente com outros frameworks e tecnologias. Podemos remover grande parte do código boilerplate ou do copiar e colar que resulta em erros e muito tempo é gasto em depuração, o que facilita o desenvolvimento de aplicativos, pois podemos nos concentrar no que sabemos: passar horas lendo documentações chatas. O Spring também incentiva você a usar bons princípios de programação, como DRY - Don't Repeat Yourself e princípios de orientação a objetos, usando injeção de dependência e interfaces bem definidas.

Olhando um pouco a história, há muito, mas muito tempo atrás, o Java EE (atualmente chamado de Jakarta EE) era realmente muito complicado e nem era necessário entrar numa discussão, era visto como um framework pesado e burocrático, com muita configuração manual e complexidade desnecessária, então usar o Spring era um caminho mais simples e mais fácil de evoluir. Aí chegou a versão 5 do Java EE e a discussão voltou a ficar um pouco mais quente.

Com a chegada do Java EE 5, muitas melhorias foram introduzidas, como anotações que reduziram a necessidade de arquivos XML extensivos, facilitando a configuração e tornando o desenvolvimento mais produtivo. No entanto, o Spring já havia conquistado uma base sólida de desenvolvedores devido à sua flexibilidade, ampla gama de funcionalidades e suporte ativo da comunidade. Enquanto o Java EE tentava simplificar sua abordagem, o Spring continuava evoluindo rapidamente, oferecendo soluções mais modernas, como o _Spring Boot_, que revolucionou a criação de aplicações ao reduzir ainda mais a necessidade de configuração e proporcionar um modelo de desenvolvimento ágil e produtivo.

A adoção do Spring Boot tornou-se um marco importante, pois ele trouxe a filosofia de "convenção sobre configuração", permitindo que os desenvolvedores iniciassem novos projetos rapidamente com dependências pré-configuradas e suporte a microsserviços de forma nativa. Mesmo com as evoluções do Jakarta EE, que trouxe melhorias significativas e se desvinculou da Oracle, o Spring ainda permanece dominante no ecossistema Java, pois além da sua maturidade e robustez, conta com uma vasta gama de integrações com frameworks modernos e tecnologias emergentes, tornando-se a escolha preferida para aplicações modulares, microsserviços e arquiteturas complexas como DDD e Hexagonal.

O Spring Framework é um framework para aplicações Java que fornece infraestrutura para desenvolvimento de aplicativos Java corporativos. Ele é modular e abrange diversas funcionalidades, como:

- **Spring Core** possui, de forma nativa e central, Inversão de Controle (IoC) e Injeção de Dependência.

- **Spring AOP** (Programação orientada a aspectos) é um paradigma que nasce para resolver um problema que aparece em praticamente todos os sistemas de médio ou grande porte: a existência dos chamados cross-cutting concerns, ou seja, comportamentos que atravessam diversas partes da aplicação, mas que não pertencem diretamente à lógica de negócio de nenhuma delas. Esse tipo de comportamento inclui auditoria, logging, autenticação, autorização, transações, métricas, rastreamento, tratamento padronizado de exceções e diversas outras funcionalidades que, se escritas diretamente dentro dos métodos, acabam poluindo o código (Code Smells), quebrando o encapsulamento e tornando difícil manter uma base organizada (Clean Code). O AOP tenta devolver a limpeza, modularidade e clareza ao sistema ao separar essas preocupações transversais em unidades isoladas chamadas “aspectos”.

- **Spring ORM**

- **Spring Security**

- **Spring Cloud**: https://cloud.spring.io/spring-cloud-gateway/reference/html/?utm_source=substack&utm_medium=email

- **Java Bean** é uma classe Java que segue um conjunto de convenções específicas, usadas para encapsular dados e facilitar a reutilização e o gerenciamento de componentes em aplicações Java. Ele é amplamente utilizado em frameworks e bibliotecas Java, especialmente em ambientes que lidam com interfaces gráficas (Swing, JavaFX) ou frameworks web (como Spring e JavaServer Faces). Java bean consiste somente em getters e setters.

- **Data Transfer Object (DTO)** é um padrão de software voltado para a transferência de dados entre as camadas de uma aplicação. Ele consiste basicamente no entendimento de como as informações trafegam dentro de um sistema. Ele simplesmente consiste em transportar dados de uma camada para outra, embora tanto os DTOs quanto os JavaBeans usem getters e setters, eles têm propósitos diferentes. O DTO é focado exclusivamente em transportar dados, enquanto o JavaBean segue uma especificação mais formal e é usado como um componente reutilizável em aplicações Java.

- **POJO (Plain Old Java Object)** é um termo utilizado para descrever um objeto Java simples, que não segue nenhuma restrição específica imposta por frameworks ou bibliotecas. Ele é usado para criar classes que contenham dados e lógica básica, sem herdar de classes específicas ou implementar interfaces obrigatórias, exceto aquelas necessárias para sua funcionalidade (como `Serializable`, por exemplo). POJO é simplesmente um objeto Java antigo, mas então você pode estar pensando: qual é a diferença entre Java Bean e o POJO? Bem, POJO é um objeto que possui atributos e comportamento, portanto, não são apenas getters e setters

- Acesso a banco de dados via JDBC, JPA/Hibernate.

- **JMS** (Java Message Service)

- Criação de APIs REST com Spring MVC e Spring Boot.

- Gerenciamento de transações ideal para padrões como SAGA.

- Integração com serviços na nuvem e muito mais.

![FB_IMG_1729854396734](https://github.com/user-attachments/assets/090f7c1d-91c7-4999-8e6c-1c89384146ef)

As **Anotações de código**, como as usadas no ecossistema **Spring** no Java, são metadados declarativos que você adiciona diretamente sobre classes, métodos ou atributos para indicar ao framework como aquele trecho de código deve se comportar em tempo de execução. Elas não são comentários nem instruções diretas do programador para a máquina, mas sim **sinalizadores** que o Spring interpreta para aplicar comportamentos automáticos, recursos do framework ou integrações com seus módulos internos. A anotação `@Transactional`, por exemplo, é uma instrução declarativa que informa ao Spring que o método ou classe deve ser executado dentro de um contexto transacional. Isso significa que o framework irá abrir uma transação antes da execução, monitorar o método e, dependendo do resultado, confirmar (commit) ou desfazer (rollback) todas as operações de banco de dados feitas ali dentro. Essa abordagem permite que você trate transações complexas de maneira simples, sem precisar manipular manualmente conexões, commits ou rollbacks, deixando o código mais limpo, menos verboso e muito mais seguro.

![FB_IMG_1720236099805](https://github.com/user-attachments/assets/0ee7c72d-df8d-454e-9545-127ede0e441f)

Esse mecanismo funciona graças ao uso de **AOP (Aspect-Oriented Programming)** no Spring, onde o framework intercepta a execução do método anotado e adiciona comportamentos antes e depois da execução sem alterar o código original. Isso vale não apenas para `@Transactional`, mas também para diversas outras anotações como `@Service`, `@Repository`, `@Controller`, `@Autowired`, `@RestController`, `@Configuration`, entre muitas outras, que também instruem o Spring a registrar componentes, montar injeções de dependência, configurar beans, habilitar APIs REST e controlar o ciclo de vida da aplicação inteira. Na prática, as anotações deixam o código declarativo e expressivo, permitindo que a infraestrutura técnica — transações, configurações, injeções, tratamento de erros, gerenciamento de componentes — seja tratada automaticamente, enquanto o desenvolvedor se concentra apenas na lógica central da aplicação.

Portanto, anotações de Spring são uma forma elegante de estabelecer contratos entre o código e o framework. Elas indicam intenções arquiteturais e comportamentais sem poluir a lógica, trazendo organização, clareza e padronização. `@Transactional` é apenas um exemplo clássico de como uma simples anotação é capaz de ativar mecanismos poderosos e sofisticados, tornando o desenvolvimento mais seguro, produtivo e alinhado com boas práticas modernas.

A programação orientada a aspectos, ou AOP (Aspect-Oriented Programming), é um paradigma que nasce para resolver um problema que aparece em praticamente todos os sistemas de médio ou grande porte: a existência dos chamados *cross-cutting concerns*, ou seja, comportamentos que atravessam diversas partes da aplicação, mas que não pertencem diretamente à lógica de negócio de nenhuma delas. Esse tipo de comportamento inclui auditoria, logging, autenticação, autorização, transações, métricas, rastreamento, tratamento padronizado de exceções e diversas outras funcionalidades que, se escritas diretamente dentro dos métodos, acabam poluindo o código, quebrando o encapsulamento e tornando difícil manter uma base organizada. O AOP tenta devolver a limpeza, modularidade e clareza ao sistema ao separar essas preocupações transversais em unidades isoladas chamadas “aspectos”.

A ideia central é que, com AOP, você pode declarar regras que interceptam certos pontos da execução do programa, os chamados *join points*, e inserir comportamentos adicionais antes, depois ou em torno da execução de métodos específicos, sem precisar modificar diretamente o código desses métodos. Esse mecanismo é chamado de *advice*, e permite que o comportamento cruzado exista de forma enxuta, desacoplada e gerenciada centralmente. Assim, se você decide adicionar um log antes de todas as chamadas de serviço, ou iniciar uma transação antes de executar um método e finalizá-la depois, ou medir o tempo de execução de uma função crítica, você faz isso declarando um aspecto, e não modificando centenas de classes espalhadas pelo projeto. Isso mantém a lógica principal intacta e evita duplicações, ruídos e inconsistências.

O AOP, portanto, funciona quase como uma camada invisível que envolve o fluxo da aplicação. Quando você olha para o código-fonte, enxerga apenas a lógica limpa daquilo que de fato importa. Quando a aplicação roda, os aspectos entram em ação e adicionam os comportamentos necessários sem que você tenha poluído nenhuma classe. Isso traz vantagens enormes no que diz respeito à manutenção, clareza, modularidade, capacidade de evolução e velocidade de implementação de funcionalidades transversais. Em projetos grandes, principalmente em sistemas distribuídos com muitos serviços, o AOP ajuda a manter padrões consistentes, reduzindo erros humanos e garantindo que funcionalidades essenciais (como transações, logs estruturados ou métricas) sejam aplicadas de forma uniforme.

Na prática, o AOP aparece com mais força em frameworks como Spring, no ecossistema Java/Kotlin, onde anotações como `@Transactional`, `@Cacheable`, `@Before`, `@After` e outras são manifestações diretas desse paradigma. Quando você escreve `@Transactional`, não é o método que sabe abrir e fechar transações, mas sim um aspecto dentro do framework que intercepta a chamada daquele método e injeta ali todo o comportamento necessário. Da mesma forma, logs automáticos, segurança declarativa com `@PreAuthorize` e até auditorias podem ser implementadas sem que o desenvolvedor tenha que escrever essas lógicas repetitivas. Em outras linguagens, como C#, Python ou JavaScript, também existem implementações alternativas desse conceito, geralmente ligadas a decorators, proxies dinâmicos, middlewares ou metaprogramação.

O AOP, por fim, é uma ferramenta conceitual que eleva o grau de organização de sistemas complexos ao retirar do código de negócio tudo aquilo que não é o negócio em si. Ele cria uma separação clara entre *o que seu código é* e *o que ele precisa que aconteça ao redor para funcionar*, permitindo que cada parte seja evoluída de forma independente. Em aplicações modernas, principalmente quando você trabalha com mensageria, gateways, APIs, microsserviços ou integrações complexas, o AOP se torna um aliado poderoso para manter consistência, padronização e um código limpo e sustentável.

Sobre o **Spring Actions** nós temos gerenciamento de dependências (Dependency management) com a ajuda do Maven. Também temos acesso a dados. Removemos o código boilerplate ao lidar com transações, gerenciamento de conexões, acesso remoto e segurança. E, finalmente, temos o controlador (controller) de visualização do modelo ou o MVC com integração do controlador.

Mas e quanto ao contexto das aplicações de vínculo? Bem, e quanto à Bean Factory, suas portas?

Todos os objetos que o Spring gerencia, mas também temos o Factory pattern, que serve ao Bean Factory por meio da operação de inversão de controle (IoC) para seus objetos. E ele também transmite todas as informações sobre a definição do objeto da aplicação e o que ele mede ao longo de seu ciclo de vida.

O Spring Framework Runtime Environment (ambiente de execução do Spring) não é um termo técnico oficial dentro do ecossistema Spring, ele varia conforme o tipo de aplicação. Se for uma aplicação Spring Boot, o runtime pode ser apenas um JAR executável com um servidor embutido. Se for uma aplicação corporativa, pode envolver servidores de aplicação externos, bancos de dados, serviços na nuvem, entre outros. Mas no fim das contas, tudo sempre roda dentro da JVM, gerenciado pelo **Spring Container**.

O ambiente de execução do Spring pode ser entendido como o conjunto de componentes e infraestrutura necessários para que uma aplicação Spring rode corretamente.

Principais componentes do ambiente de execução do Spring:

1. **JVM (Java Virtual Machine)** O Spring é um framework Java, então a aplicação Spring **roda dentro da JVM**. Você precisa de um JDK/JRE compatível para executar sua aplicação.

2. **Container de Inversão de Controle (IoC Container)** O **Spring Container** gerencia a criação e o ciclo de vida dos beans (objetos controlados pelo Spring). Ele pode ser o `ApplicationContext` ou `BeanFactory`, dependendo do tipo de aplicação.

3. **Servidor de Aplicação/Servlet Container** Se você estiver rodando uma aplicação web com **Spring Boot**, ela pode rodar em servidores como **Tomcat, Jetty ou Undertow** (embutidos ou externos). Em aplicações corporativas maiores, pode ser implantada em servidores como **WildFly, WebLogic, WebSphere**.

4. **Spring Boot Runtime (para aplicações Spring Boot)** No caso do **Spring Boot**, o ambiente de execução inclui bibliotecas de autoconfiguração e um servidor embutido, permitindo executar a aplicação como um **JAR executável** (`java -jar app.jar`).

5. **Banco de Dados e Integrações** Muitas aplicações Spring interagem com bancos de dados via **Spring Data JPA**, **JDBC**, **MongoDB**, **Redis**, etc. O ambiente de execução pode incluir conexões com serviços externos, como APIs REST, mensageria (RabbitMQ, Kafka), caches distribuídos, entre outros.

6. **Spring Runtime e Nuvem** Se sua aplicação roda em *ambientes de nuvem*, como AWS, Azure, GCP ou Kubernetes, o runtime pode incluir:

- **Spring Cloud** (para microservices e integração com serviços na nuvem).
- **Docker/Kubernetes** para implantação escalável.
- **Configuração distribuída** via Spring Config Server.
- **Service Discovery** com Eureka, Consul ou Zookeeper.

O Spring é ideal para aplicações modulares, microserviços e arquiteturas robustas, como Hexagonal Architecture (Ports & Adapters) e Domain-Driven Design (DDD). O motivo é que ele oferece um ecossistema completo, que facilita a implementação de princípios arquiteturais modernos, incluindo modularização, injeção de dependências e comunicação entre serviços.

A **Inversão de Controle** (Inversion of Control ou IoC) é um princípio de design de software e trata-se do redirecionamento do fluxo de execução de um código retirando parcialmente o controle sobre ele e delegando-o para um container. O principal propósito é minimizar o acoplamento do código.


Sem IoC
Com IoC




- **Sem IoC**: Em nosso desenvolvimento éramos responsáveis pela instanciação e gestão dos objetos da aplicação.

- **Com IoC**: Agora um container cuida de todo este trabalho de criação e controle dos objetos da aplicação denominados de `Component`.

> [!Note]
> **Não confunda!** Os Design patterns (Padrões de design) são soluções reutilizáveis para problemas comuns de design de software, enquanto design de software é o processo de definir a arquitetura, componentes e interfaces de um sistema para atender a requisitos específicos. No contexto de IoC, o design de software aplica o princípio de Inversão de Controle para criar arquiteturas desacopladas, e padrões como Dependency Injection e Service Locator implementam esse princípio para gerenciar dependências de forma eficiente.

A **Injeção de dependência** (Dependency Injection ou DI) é um padrão de desenvolvimento com a finalidade de manter baixo o nível de acoplamento entre módulos de um sistema.


DI
DI com Singleton, Prototype e Spring Bean Scopes




- **Beans** é o objeto que é instanciado (criado), montado e gerenciado por um container através do princípio da inversão de controle.

- **Scopes** é o controle da existência de nossos objetos Components da aplicação.

- **Singleton**: O container do Spring IoC define apenas uma instância do objeto.

- **Prototype**: Será criado um novo objeto a cada solicitação ao container.

- **Request**: Um bean será criado para cada requisição HTTP.

- **Session**: Um bean será criado para a sessão de usuário na Web.

- **Global**: Ou Application Scope cria um bean para o ciclo de vida do contexto da aplicação.

O **Autowired** é uma anotação (indicação) onde deverá ocorrer uma injeção automática de dependência.

- `byName`: É buscado um método `set` que corresponde ao nome do Bean.

- `byType`: É considerado o tipo da classe para inclusão do Bean.

- `byConstrutor`: Usamos o construtor para incluir a dependência.

Enquanto que o Spring Framework é baseado no padrão de injeção de dependências, o **Spring Boot** foca na configuração automática. O Spring Boot é um framework Java de código aberto usado para programar aplicativos autônomos baseados em Spring de nível de produção com o mínimo de esforço.

O Spring Boot é uma extensão de convenção sobre configuração para a plataforma Spring Java destinada a ajudar a minimizar as preocupações de configuração ao criar aplicações baseadas no Spring. A maior parte do aplicativo pode ser pré-configurada usando a "visão opinativa" da equipe Spring da melhor configuração e uso da plataforma Spring e bibliotecas de terceiros.

O Spring Boot, com sua poderosa configuração automática e ecossistema rico, tornou-se um framework líder em desenvolvimento Java. Além de sua funcionalidade principal, o Spring Framework oferece diversas classes utilitárias que podem simplificar drasticamente o desenvolvimento diário. Ao aproveitar essas ferramentas integradas, você pode escrever código mais limpo e robusto e reduzir sua dependência de bibliotecas externas como Apache Commons ou Guava.

Dado que a maior parte das configurações necessárias para o início de um projeto são sempre as mesmas, por que não iniciar um projeto com todas estas configurações já definidas?

Essa é exatamente a proposta do Spring Boot! Ele adota a filosofia de convenção sobre configuração para eliminar a necessidade de definir manualmente todas as configurações que, na maioria dos casos, seguem um padrão repetitivo. Em vez de perder tempo ajustando arquivos XML, definindo beans manualmente e configurando o servidor de aplicação, o Spring Boot simplifica tudo com **configuração automática** (**Spring Boot AutoConfiguration**), **starters** e **embutindo um servidor Tomcat ou Jetty** por padrão.

Com o **Spring Boot Starters**, basta adicionar a dependência certa no `pom.xml` (Maven) ou `build.gradle` (Gradle), e o framework já configura automaticamente tudo o que é necessário. Por exemplo, ao adicionar `spring-boot-starter-web`, o Spring Boot configura um ambiente pronto para APIs REST, incluindo um servidor embutido (Tomcat), suporte ao Spring MVC e conversão de JSON. Se adicionarmos `spring-boot-starter-data-jpa`, ele configura o Hibernate e a conexão com o banco de dados.

Outro grande benefício da inicialização automática do Spring Boot é o **Spring Boot Actuator**, que fornece endpoints para monitoramento e métricas da aplicação sem precisar de configurações complexas. Além disso, o **Spring Boot DevTools** ajuda na produtividade, permitindo recarregamento automático da aplicação durante o desenvolvimento.

Com isso, ao invés de gastar tempo com configurações repetitivas e infraestrutura, os desenvolvedores podem focar diretamente no desenvolvimento das **regras de negócio**. É isso que faz o Spring Boot ser tão eficiente para criar microserviços, APIs REST e aplicações escaláveis rapidamente.

**Lombok** é uma biblioteca Java que tem como objetivo principal reduzir o boilerplate, ou seja, aquele código repetitivo e verboso gerando automaticamente métodos como `getters`, `setters`, `toString`, `equals`, `hashCode`, construtores, builders, entre outros, por meio de anotações simples diretamente nas classes. Com ela, é possível deixar o código mais limpo, legível e focado na lógica de negócio, sem precisar escrever manualmente coisas que o compilador pode gerar por você.

Ela funciona utilizando anotações em tempo de compilação, através de uma técnica chamada "annotation processing", o que significa que os métodos gerados por Lombok não aparecem diretamente no código fonte, mas sim nos arquivos `.class` compilados. Isso exige que a IDE usada (como IntelliJ ou Eclipse) também tenha suporte para Lombok, caso contrário, pode parecer que os métodos não existem, mesmo estando lá.

Para instalar o Lombok em um projeto Maven, basta adicionar a dependência no arquivo `pom.xml`:

```xml

org.projectlombok
lombok
1.18.30
provided

```

Se o projeto usa Gradle, adicione no `build.gradle`:

```groovy
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'
}
```

E se for usar com o Spring Boot, o suporte é pleno e altamente compatível. Muitas vezes o `Spring Initializr` já oferece a opção de incluir Lombok automaticamente.

Além da dependência, para que Lombok funcione corretamente na sua IDE, é necessário instalar o **plugin do Lombok**. No IntelliJ IDEA, vá até `Settings → Plugins → Marketplace`, procure por "Lombok", instale e reinicie a IDE. Em seguida, vá em `Settings → Build, Execution, Deployment → Compiler → Annotation Processors` e habilite a opção “Enable annotation processing”. No Eclipse, você pode baixar o jar de instalação do Lombok no site oficial e executá-lo, que ele se integra ao Eclipse automaticamente.

Depois disso, basta usar anotações como `@Getter`, `@Setter`, `@AllArgsConstructor`, `@NoArgsConstructor`, `@Builder`, `@Data`, entre outras, para ver seu código se tornar muito mais enxuto e produtivo.

Ferramentas de Processamento de Strings:

1. **StringUtils**: Uma utilidade abrangente para manipulações comuns de strings, incluindo verificações para strings vazias ou apenas em espaços em branco.

![FB_IMG_1721873258608](https://github.com/user-attachments/assets/94acdc0c-2ed5-4259-8953-88baf195bd84)

## [Java] Spring Data JPA (Hibernate)

**Spring Data JPA** é uma abstração poderosa da Spring Framework que simplifica significativamente o uso do JPA (Java Persistence API) em aplicações Java, facilitando o acesso, manipulação e persistência de dados em bancos relacionais. Ele foi criado para reduzir o boilerplate de código necessário para operações com banco de dados, eliminando a necessidade de escrever implementações detalhadas de repositórios ou consultas SQL/JPA personalizadas para tarefas básicas do dia a dia.

Por trás do Spring Data JPA, geralmente está o **Hibernate**, que é a implementação mais amplamente utilizada da especificação JPA. Hibernate traduz os objetos Java para tabelas relacionais e vice-versa — um processo conhecido como ORM (Object-Relational Mapping). Isso permite que os desenvolvedores trabalhem com entidades Java (classes com anotações específicas) como se fossem objetos normais, enquanto o framework cuida da persistência dos dados no banco de dados real.

O grande diferencial do Spring Data JPA é que ele permite a criação de repositórios baseados apenas em interfaces, sem necessidade de implementação concreta. Isso é possível graças a mecanismos de proxy e reflexão do Spring, que interpretam nomes de métodos e os convertem em consultas automaticamente. Por exemplo, se você definir um método como `findByEmail(String email)`, o Spring Data JPA entende que precisa gerar uma consulta que busca um registro pelo campo `email`.

Além disso, ele oferece integração facilitada com queries personalizadas via JPQL (Java Persistence Query Language), SQL nativo, e também com a API Criteria, quando consultas mais dinâmicas são necessárias. A integração com o Spring Boot, por sua vez, torna a configuração e o uso ainda mais automáticos, permitindo que você conecte sua aplicação a um banco de dados e comece a salvar ou recuperar dados com mínima configuração.

No fim das contas, o Spring Data JPA (com Hibernate como base) é uma solução madura, robusta e produtiva para persistência de dados em aplicações Java, que reduz drasticamente o tempo de desenvolvimento ao abstrair tarefas repetitivas e promover boas práticas de acesso a dados com foco em legibilidade, desacoplamento e manutenção do código.

## [Java] How Netflix Runs on Java?
Não existe uma única Stack de Netflix. A Stack Java da Netflix tem evoluído nos últimos anos, começando desde frameworks internos até microserviços da era Groovy e, mais recentemente, migrando para a GraphQL Federation.

Todas as mudanças foram feitas para resolver problemas da abordagem anterior. Por exemplo, a mudança para o RxJava foi para lidar melhor com os fanouts e a mudança para a Federação GraphQL foi para resolver os problemas de complexidade causados pelo RxJava.

Junto com essas mudanças, houve também uma evolução paralela em termos de versões da linguagem Java do Java 8 para o 17 e agora para o 21+. Muito disso também foi motivado pelo Spring Boot versão 3 finalmente ultrapassando o Java 8 e forçando todo o ecossistema a atualizar.

Essas mudanças permitiram que eles construíssem aplicações mais performantes que podem economizar custos de CPU.

No geral, o tema tem sido a padronização da abordagem na construção de microserviços em toda a organização. No entanto, considerando os desafios constantes enfrentados para operar em sua escala mantendo-se à frente da concorrência, a evolução continuará.

A arquitetura Java da Netflix em 2025 não é um resquício do passado, mas um sistema de engenharia moderno e deliberado. O que torna interessante não é a escolha da linguagem, mas a forma como essa escolha é continuamente reavaliada, otimizada e alinhada com as restrições do mundo real.

O sistema não é estático. A Netflix ultrapassou os limites do Java 8 ao atualizar agressivamente sua pilha, não reescrevendo-a. Abraçou o Spring Boot como base, mas o expandiu para atender às demandas únicas de uma plataforma global de streaming. Adotou o GraphQL para flexibilidade, threads virtuais para concorrência e ZGC para desempenho.

Alguns pontos-chave são os seguintes:




Aqui estão os detalhes dessas iterações:

API Gateway
A Netflix segue uma arquitetura de microserviços. Cada funcionalidade e cada dado pertence a um microserviço construído usando Java (inicialmente versão 8).

Isso significa que renderizar uma tela (como a Lista de Listas de Filmes ou LOLOMO) envolvia buscar dados de dezenas de microserviços. Mas fazer todas essas ligações do cliente criou um problema de desempenho.

A Netflix inicialmente usou o padrão API Gateway usando Zuul para lidar com a orquestração.

BFFs com Groovy & RxJava
Usar um único gateway para múltiplos clientes era um problema para a Netflix porque cada cliente (como TV, aplicativos móveis ou navegador) tinha diferenças sutis.

Para lidar com isso, a Netflix usou o padrão Backend-for-Frontend (BFF). O Zuul foi movido para o papel de proxy

. Nesse padrão, cada frontend ou interface de usuário recebe seu próprio mini-backend que executa o fanout e orquestração de requisições para múltiplos serviços.

Os BFFs foram construídos usando scripts Groovy e o fato de serviços foi feito usando RxJava para gerenciamento de threads.

Federação
GraphQLA abordagem Groovy e RxJava exigiu mais trabalho dos desenvolvedores de interface na criação dos scripts Groovy. Além disso, programação reativa geralmente é difícil.

Recentemente, a Netflix migrou para a GraphQL Federation. Com o GraphQL, um cliente pode especificar exatamente qual conjunto de campos precisa, resolvendo assim o problema de overfetching e underfetching com APIs REST.

A Federação GraphQL cuida de chamar os microserviços necessários para buscar os dados.

Esses microsserviços são chamados de Domain Graph Service (DGS) e são construídos usando os pacotes Java 17, Spring Boot 3 e Spring Boot Netflix OSS. A transição do Java 8 para o Java 17 resultou em ganhos de 20% no CPU.

Mais recentemente, a Netflix começou a migrar para Java 21 para aproveitar recursos como threads virtuais.

- Java ainda é competitivo quando tratado como um ecossistema. A Netflix extrai ganhos significativos de desempenho dos recursos modernos da JVM. Não se contenta com configurações padrão ou frameworks antigos. A evolução é deliberada.

- Possuir a plataforma possibilita velocidade. Construir ferramentas internas para patches, transformações e implantações transforma as atualizações de risco em rotina. A propriedade da plataforma é uma vantagem para eles.

- Threads virtuais reduzem a complexidade. Eles mantêm um estilo de programação familiar enquanto escalam melhor sob carga. O retorno é um código mais limpo, menos bugs e modelos mentais mais simples.

- A infraestrutura de ajuste, e não apenas o código, melhora a confiabilidade. Atualizar o coletor de lixo e otimizar o comportamento da thread resultou em menos timeouts, taxas de erro menores e um throughput mais consistente em geral.

A Netflix é predominantemente uma loja de Java. Todo aplicativo backend na Netflix é um aplicativo Java. Isso inclui:

- Aplicações internas
- O software que alimenta um dos maiores estúdios de cinema do mundo e é usado para produzir filmes
- O aplicativo de streaming da Netflix

No entanto, isso não significa que a pilha Java da Netflix seja estática. Ao longo dos anos, evoluiu significativamente.

Vamos analisar a evolução do uso do Java na Netflix à luz das mudanças arquitetônicas gerais que ocorreram para apoiar as necessidades em transformação.

A Netflix é predominantemente uma loja de Java.

Todo aplicativo backend (incluindo apps internos, streaming e de produção de filmes) na Netflix é um aplicativo Java.

No entanto, a pilha Java não é estática e passou por várias iterações ao longo dos anos.

Aqui estão os detalhes dessas iterações:

1. API Gateway: A Netflix segue uma arquitetura de microserviços. Cada funcionalidade e cada dado pertence a um microserviço construído usando Java (inicialmente versão 8)

2. BFFs com Groovy & RxJava: Usar um único gateway para múltiplos clientes era um problema para a Netflix porque cada cliente (como TV, aplicativos móveis ou navegador) tinha diferenças sutis. Para lidar com isso, a Netflix usou o padrão Backend-for-Frontend (BFF). Zuul foi transferido para o papel de procurador

2. GraphQL Federation: GraphQLA abordagem Groovy e RxJava exigiu mais trabalho dos desenvolvedores de interface na criação dos scripts Groovy. Além disso, programação reativa geralmente é difícil.

> [!Warning]
> Os detalhes deste post foram derivados dos artigos/vídeos compartilhados online pela equipe de engenharia da Netflix. Todo o crédito pelos detalhes técnicos vai para a equipe de engenharia da Netflix. Os links para os artigos e vídeos originais estão presentes na seção de referências ao final do post. Tentamos analisar os detalhes e dar nossa opinião sobre eles. Se você encontrar alguma imprecisão ou omissão, por favor, deixe um comentário e faremos o possível para corrigi-las.

A Era Groovy com melhores amigos: É de conhecimento geral que a Netflix tem uma arquitetura de microsserviços.

Cada funcionalidade e cada dado pertence a um microserviço e existem milhares de microsserviços. Além disso, múltiplos microserviços se comunicam entre si para realizar algumas das funcionalidades mais complexas.

Por exemplo, quando você abre o aplicativo da Netflix, vê a tela do LOLOMO. Aqui, LOLOMO significa lista de listas de filmes e é essencialmente construído buscando dados de muitos microserviços, tais como:

- Serviço que retorna uma lista dos 10 melhores filmes
- Serviço de arte que fornece imagens personalizadas para cada filme
- Serviço de metadados de filmes que retorna os títulos dos filmes, detalhes dos atores e descrições
- LOLOMO que fornece quais listas realmente renderizar para a página inicial do usuário.

O diagrama abaixo mostra essa situação:

unnamed

É bem possível que renderizar apenas uma tela no aplicativo da Netflix envolva ligar para 10 serviços.

No entanto, ligar para tantos serviços pelo seu dispositivo (como a televisão) ou pelo aplicativo móvel geralmente é ineficiente. Fazer 10 chamadas de rede não escala e resulta em uma experiência ruim para o cliente. Muitos aplicativos de streaming sofrem com esses problemas de desempenho.

Para evitar esses problemas, a Netflix usou uma única porta principal para as várias APIs. O dispositivo faz uma ligação para essa porta da frente que realiza o fanout para todos os diferentes microserviços. A porta da frente funciona como um portal e a Netflix usou Zuul para esse fim.

Essa abordagem funciona porque a chamada para múltiplos microserviços ocorre na rede interna, que é muito rápida, eliminando assim as implicações de desempenho.

No entanto, havia outro problema a ser resolvido.

Todos os diferentes dispositivos que os usuários podem usar para acessar a Netflix têm requisitos distintos de maneiras sutis. Embora a Netflix tenha tentado manter uma aparência e sensação consistentes para a interface e seu comportamento em todos os dispositivos, cada dispositivo ainda tem limitações diferentes em termos de memória ou largura de banda de rede e, portanto, carrega os dados de maneiras um pouco diferentes.

É difícil criar uma única API REST que funcione em todos esses dispositivos diferentes. Alguns dos problemas são os seguintes:

APIs REST Ou buscam dados demais ou de muito poucos

Mesmo que criassem uma API REST para cuidar de todas as necessidades de dados, seria uma experiência ruim porque estariam desperdiçando muitos dados

No caso de múltiplas APIs, isso significaria múltiplas chamadas de rede

Para lidar com isso, a Netflix usou o padrão backend para frontend (BFF).

Nesse padrão, cada frontend ou interface de usuário recebe seu próprio mini backend. O mini backend é responsável por realizar o fanout e buscar os dados que a interface precisa naquele ponto específico.

O diagrama abaixo retrata o conceito do padrão BFF:

unnamed

No caso da Netflix, os melhores amigos eram basicamente um roteiro Groovy para uma tela específica em um dispositivo específico.

Os scripts eram escritos por desenvolvedores de interface, pois eles sabiam exatamente quais dados precisavam para renderizar uma tela específica. Uma vez escritos, os scripts eram implantados em um servidor API e realizavam o fanout para todos os diferentes microsserviços, chamando as bibliotecas clientes Java apropriadas. Essas bibliotecas clientes eram wrappers para um serviço gRPC ou um cliente REST.

O diagrama abaixo mostra essa configuração.

unnamed

The Use of RxJava and Reactive Programming: Os roteiros do Groovy ajudaram a realizar o lançamento.

Mas fazer esse tipo de expansão em Java não é trivial. A abordagem tradicional era criar vários threads e tentar gerenciar o fanout usando o gerenciamento mínimo de threads.

No entanto, as coisas ficaram complicadas rapidamente por causa da tolerância a falhas. Ao lidar com vários serviços, pode ser que um deles não responda rápido o suficiente ou falha, resultando em uma situação em que você precisa limpar threads e garantir que tudo funcione corretamente.

Foi aí que o RxJava e a programação reativa ajudaram a Netflix a lidar melhor com os fanouts, cuidando de toda a complexidade do gerenciamento de threads.

Além do RxJava, a Netflix criou uma biblioteca tolerante a falhas chamada Hystrix, que cuidava de failover e bulkheading. Embora a programação reativa fosse complicada, fazia muito sentido para a época e a arquitetura permitia atender à maior parte das necessidades de tráfego da Netflix.

No entanto, havia algumas limitações importantes nessa abordagem:

Havia um script para cada endpoint, resultando em muitos scripts para manter e gerenciar

Desenvolvedores de UI tiveram que criar todos os mini backends e não gostaram de trabalhar no espaço Java Groovy com RxJava. Não é a linguagem principal que eles usam diariamente que dificulta as coisas

Programação reativa geralmente é difícil e tem uma curva de aprendizado íngreme.

A Mudança para a Federação GraphQL
Nos últimos anos, a Netflix tem migrado para uma arquitetura completamente nova quando se trata de seus serviços Java. O centro dessa nova arquitetura é a GraphQL Federation.

Quando você compara GraphQL com REST, a principal diferença é que GraphQL sempre tem um esquema. Esse esquema ajuda a definir alguns aspectos-chave como:

Todas as operações, junto com as várias consultas e mutações

Campos disponíveis dos tipos que estão sendo retornados pelas consultas

Por exemplo, no caso da Netflix, você pode ter uma consulta para todos os programas que retornam um tipo de programa. Tem um programa como título e também contém críticas, que podem ser de outro tipo.

Com o GraphQL, o cliente precisa ser explícito sobre a seleção do campo. Você não pode simplesmente pedir por shows e obter todos os dados deles. Em vez disso, você precisa mencionar especificamente que quer obter o título do programa e a nota de várias críticas. Se você não pedir um campo, não vai conseguir o campo.

Com o REST, isso era o oposto, porque você recebia o que o serviço REST decidia enviar.

Embora seja mais trabalho para o cliente especificar a consulta no GraphQL, isso resolve todo o problema de over-fetching, onde você obtém muito mais dados do que realmente precisa. Isso abre caminho para criar uma API única que possa atender todas as diferentes interfaces.

Para complementar o GraphQL, a Netflix foi além e usou a GraphQL Federation para reintegrá-lo em sua arquitetura de microsserviços.

O diagrama abaixo mostra a configuração com o GraphQL Federation.

unnamed

Como você pode ver, os microserviços agora são chamados de DGS ou Domain Graph Service.

DGS é um framework interno desenvolvido pela Netflix para construir serviços GraphQL. Quando começaram a migrar para GraphQL e GraphQL Federation, não havia nenhum framework Java maduro o suficiente para usar na escala da Netflix. Por isso, eles construíram sobre o framework Java de baixo nível GraphQL e o complementaram com recursos como geração de código para tipos de esquema e suporte para federação.

No seu cerne, um DGS é apenas um microserviço Java com um endpoint GraphQL e um esquema.

Embora existam múltiplos DGSs, existe apenas um grande esquema GraphQL do ponto de vista de um dispositivo como a TV. Esse esquema contém todos os dados possíveis que podem ser renderizados. O dispositivo não precisa se preocupar com todos os diferentes microserviços que fazem parte do esquema no backend.

Por exemplo, a DGS da LOLOMO pode definir um tipo de show apenas com o título. Depois, as imagens que o DGS pode estender esse tipo aparecem e adicionam uma URL de arte a ele. Os dois DGS diferentes não sabem nada um sobre o outro. Tudo o que eles precisam fazer é publicar o esquema deles no gateway federado. O gateway federado sabe como se comunicar com um DGS porque todos eles têm um endpoint GraphQL.

Existem várias vantagens nesse setup:

Não há mais duplicação de APIs.

Não há necessidade de backend para frontend (BFF) porque o GraphQL como API é flexível o suficiente para suportar diferentes dispositivos devido ao recurso de seleção de campos.

Não há necessidade de desenvolvimento do lado do servidor por engenheiros de interface. Os desenvolvedores do backend e os desenvolvedores da interface apenas colaboram no esquema.

Não há mais necessidade de bibliotecas clientes em Java. Isso porque o gateway federado sabe como se comunicar com um serviço genérico GraphQL sem a necessidade de escrever código específico.

Versões em Java na Netflix
Recentemente, a Netflix migrou do Java 8 para o Java 17. Após a migração, eles viram cerca de 20% melhor no uso de CPU no Java 17 do que no Java 8, sem nenhuma alteração no código. Isso se deveu às melhorias no coletor de lixo G1. Na escala da Netflix, uma utilização de CPU 20% melhor é um grande valor em termos de custos.

Ao contrário do que muitos pensam, a Netflix não tem sua própria JVM. Eles estão apenas usando a Azul Zulu JVM, que é uma build do OpenJDK.

No total, a Netflix possui cerca de 2800 aplicações Java que são em sua maioria microserviços de tamanhos variados. Além disso, eles têm cerca de 1500 bibliotecas internas. Algumas delas são bibliotecas reais, enquanto muitas são apenas bibliotecas clientes na frente de um serviço gRPC ou REST.

Para o sistema de build, a Netflix depende do Gradle. Além do Gradle, eles usam o Nebula, que é um conjunto de plugins open-source do Gradle. O aspecto mais importante do Nebula está na resolução das bibliotecas. Nebula ajuda com o bloqueio de versões, o que ajuda com builds reprodutíveis.

Mais recentemente, a Netflix tem testado ativamente e implementado mudanças com Java 21. Comparando a transição do Java 8 para o Java 17, é significativamente fácil passar do Java 17 para o 21. O Java 21 também oferece alguns recursos importantes, tais como:

Threads virtuais permitem que aplicações do lado do servidor, escritas no estilo thread-per-request, escalem com a utilização ideal de hardware. No estilo thread-per-request, uma solicitação chega e o servidor fornece uma thread para ela. Todo o trabalho do pedido acontece neste tópico

Um coletor de lixo ZGC atualizado que foca em tempos de pausa baixos e funciona bem em uma variedade maior de casos de uso.

Programação orientada a dados com uma combinação de registros e correspondência de padrões

Uso da Spring Boot na Netflix: A Netflix é famosa pelo uso do Spring Boot. No último ano, eles deixaram completamente a pilha Java criada no Guice e padronizaram completamente o Spring Boot.

Por que a Spring Boot? É o framework Java mais popular e tem sido muito bem mantido ao longo dos anos.

A Netflix encontrou muitos benefícios ao aproveitar a enorme comunidade open-source do framework Spring, a documentação existente e as oportunidades de treinamento facilmente disponíveis. A evolução de Spring e seus longas se alinham muito bem com o princípio central da Netflix de "altamente alinhado, frouxamente acoplado".

A Netflix usa a versão mais recente do OSS Spring Boot e seu objetivo é se manter o mais próximo possível da comunidade open source. No entanto, para se integrar de perto com o ecossistema e a infraestrutura da Netflix, eles também criaram o Spring Boot Netflix, que é um conjunto de módulos construídos sobre o Spring Boot.

Spring Boot A Netflix oferece suporte para várias coisas, como:

- Cliente gRPC
- Suporte a servidores integrado à pilha SSO da Netflix para AuthZ e AuthN
- Observabilidade na forma de rastreamento, métricas e registro distribuído
- Clientes HTTP que suportam mTLS
- Service Discovery com Eureka
- Integração AWS/Titus
- Integração com Kafka, Cassandra e Zookeeper

A Netflix é uma aula magistral em engenharia backend em grande escala. Por trás da reprodução contínua, recomendações personalizadas e consistência entre dispositivos, existe uma arquitetura complexa alimentada por Java.

A maioria dos serviços de backend da Netflix roda em Java. Isso pode surpreender engenheiros que já assistiram à ascensão de Kotlin, Go, Rust e frameworks reativos. Mas a Netflix não vai continuar com Java por inércia. Java amadureceu, assim como o ecossistema ao seu redor. JVMs modernas oferecem coletores de lixo poderosos. O Spring Boot se tornou tanto extensível quanto confiável. E com a chegada de threads virtuais e concorrência estruturada, o Java está recuperando seu lugar no design de sistemas de alta taxa e baixa latência, sem a sobrecarga da complexidade reativa.

Neste artigo, vamos explicar como a Netflix usa Java hoje. Também abordaremos os seguintes temas:

- A espinha dorsal arquitetônica da Netflix: uma plataforma federada GraphQL que conecta aplicativos clientes a dezenas de serviços backend em Java.

- O modelo de concorrência: como threads virtuais Java e coletores de lixo modernos mudam o desempenho e a confiabilidade.

- A evolução: uma migração em toda a empresa da dívida técnica para Spring Boot, JDK 21+ e além.

Arquitetura Backend com a GraphQL Foundation: No coração do backend da Netflix está uma arquitetura GraphQL federada. Esta é a abstração principal pela qual todas as aplicações clientes interagem com os dados do backend. O modelo oferece tanto flexibilidade quanto isolamento: os clientes podem expressar exatamente o que precisam, e as equipes de backend podem evoluir seus serviços de forma independente.

unnamed

Toda consulta GraphQL de um cliente Netflix, seja de uma smart TV, telefone ou navegador, chega a um API Gateway centralizado. Esse gateway analisa a consulta, decompõe-a em subconsultas e as direciona para os serviços backend apropriados.

Cada equipe backend possui um Domain Graph Service (DGS), que implementa uma fatia do esquema geral do GraphQL. Todo Domain Graph Service (DGS) da Netflix é um aplicativo Spring Boot.

O próprio framework DGS é construído como uma extensão do Spring Boot. Isso significa que suporta os seguintes recursos:

- A injeção de dependências, configuração e gerenciamento do ciclo de vida são gerenciados pelo Spring Boot.

- Resolvers GraphQL são apenas componentes Spring anotados.

- Observabilidade, segurança, lógica de retentativa e integração com malha de serviço são implementadas usando os mecanismos do Spring.

![ed4f8f83-0122-4892-9709-9b000a9281fb_1600x1147](https://github.com/user-attachments/assets/112b6179-86e8-4625-882e-76b86538283a)

A Netflix escolheu Spring Boot porque já está comprovada em escala e é uma tecnologia de longa duração na Netflix. Também é extensível à medida que a Netflix adiciona seus módulos para segurança, métricas, descoberta de serviços e mais.

As DGSs registram seus fragmentos de esquema em um registro compartilhado. O gateway então sabe qual serviço é responsável por qual campo. Isso transforma um esquema "monolítico" em um grafo totalmente federado e implantável de forma independente.

Essa separação da propriedade do esquema possibilita:

- Implantabilidade independente dos serviços
- Colaboração baseada em esquema entre frontend e backend
- Limites mais limpos entre domínios (por exemplo, recomendações vs. perfis de usuário)

A ideia principal é que os serviços backend possuem sua parte do grafo, não apenas seus dados internos.

Microservice Fan-out: What a Query Hits: Nos bastidores, até mesmo uma consulta simples, como buscar títulos e imagens de cinco programas, faz fãs espalhados por vários serviços:

- O API Gateway recebe a solicitação.
- Ele contatou 2–3 DGSs para resolver áreas como metadados, arte e disponibilidade.
- Cada DGS pode então se espalhar novamente para buscar em depósitos de dados ou ligar para outros serviços.

Esse padrão de expansão é essencial para flexibilidade, mas introduz uma complexidade real. O sistema precisa de timeouts agressivos, lógica de retentativa e estratégias de reposição para evitar que um serviço lento se transforme em latência visível para o usuário.

![4e5ae488-425c-443b-a702-bfc2f6857df9_1600x1062](https://github.com/user-attachments/assets/6e7b184d-6794-454c-8ac7-51fbc1e55218)

Protocol Choices: Entre o cliente e o gateway, a Netflix mantém HTTP e GraphQL em protocolos web padrão. Isso garante compatibilidade entre navegadores, aplicativos móveis e smart TVs.

Dentro do backend, os serviços se comunicam via gRPC: um protocolo binário de alto desempenho que suporta chamadas eficientes entre serviços. O gRPC habilita:

- Comunicação de baixa latência
- Tipagem forte via Protocol Buffers
- Evolução fácil da interface

Essa separação faz sentido: o GraphQL é ótimo para busca de dados flexível e orientada pelo cliente, enquanto o gRPC se destaca em interações internas no estilo RPC.

![dd72b09d-b352-420c-9b1e-d08428842768_1600x1062](https://github.com/user-attachments/assets/d19b4d52-7e46-4955-97e1-625345673e34)

Evolução da JVM: Até recentemente, grande parte do código Java da Netflix ficava preso no JDK 8. O problema não era a inércia, mas o bloqueio. Um framework de aplicação personalizado e interno, construído anos antes, acumulou camadas de bibliotecas não mantidas e APIs desatualizadas. Essas dependências tinham acoplamentos apertados e problemas de compatibilidade que tornavam arriscado atualizar qualquer coisa além do JDK 8.

Nesse contexto, os proprietários de serviços não podiam avançar de forma independente. Mesmo quando versões mais novas em Java estavam tecnicamente disponíveis, a plataforma não estava pronta. As equipes tinham pouco incentivo para evoluir porque isso exigia esforço sem benefício imediato. O resultado foi um progresso estagnado em todos os aspectos.

Quebrar esse ciclo exigiu uma abordagem direta. A Netflix corrigiu as bibliotecas incompatíveis em si, não reescrevendo tudo, mas fazendo forks e atualizações mínimas do que era necessário para torná-lo compatível com o JDK-17. Na prática, isso não foi tão assustador quanto parece. No fim das contas, apenas um pequeno número de bibliotecas críticas exigiu intervenção.

Paralelamente, a empresa começou a migrar todos os serviços Java (cerca de 3000) para o Spring Boot. Não foi um simples levantar e mudar. Eles construíram ferramentas automatizadas para transformar código, configurar serviços e padronizar a implantação. Embora o esforço tenha sido significativo, o resultado é uma plataforma unificada que pode evoluir em sintonia com o ecossistema Java mais amplo.

Agora, a linha de base na maioria dos times é Spring Boot no JDK 17 ou mais recente. Alguns serviços legados permanecem para compatibilidade retroativa, mas foram exceção.

Uma vez que os serviços foram transferidos para o JDK 17, os benefícios ficaram evidentes:

- O coletor de lixo G1, já em uso, apresentou uma melhora significativa: cerca de 20% menos tempo de CPU gasto no GC sem alterar o código da aplicação.
- Pausas menos e mais curtas para parar o mundo levaram a menos tempos de espera em cascata em sistemas distribuídos.
- Maior throughput geral e melhor utilização da CPU tornaram-se possíveis, especialmente para serviços de alto RPS.

ZGC Geracional: O coletor de lixo G1 serviu bem à Netflix por anos. Ele encontrava um equilíbrio entre throughput e tempo de pausa, e a maioria dos serviços baseados em JVM o utilizava por padrão. Mas à medida que o tráfego aumentava e os tempos se fechavam, as rachaduras apareciam.

Sob alta concorrência, alguns serviços tiveram pausas de parar o mundo que duravam mais de um segundo, tempo suficiente para causar timeouts de IPC e acionar lógica de retentativa entre serviços dependentes. Essas tentativas inflaram o tráfego, introduziram jitter e obscureceram a causa raiz das falhas. Em clusters rodando com altas cargas de CPU, os picos ocasionais de latência do G1 se tornavam um fardo operacional.

No entanto, a introdução do ZGC geracional mudou o jogo.

O ZGC já estava disponível em versões anteriores em Java, mas não possuía um modelo de memória geracional. Isso limitou sua eficácia para cargas de trabalho onde a maioria das alocações era de curta duração, como nos serviços de streaming da Netflix.

No JDK 21, finalmente chegou a geração ZGC. Ele trouxe um coletor de lixo moderno, com poucas pausas, que também entendia a vida útil do objeto. O efeito foi imediato:

- O tempo de pausa caía quase zero, mesmo sob carga pesada.

- Os serviços não tinham mais tempo de encerramento durante as pausas do GC, o que levou a uma redução visível nas taxas de erro.

- Com menos estojos de coleta de lixo, menos requisições upstream falharam, reduzindo a pressão em todo o cluster.

- Os clusters rodavam mais próximos da saturação da CPU sem cair. A margem de segurança antes reservada para a segurança do GC agora estava disponível para cargas reais de trabalho.

Do ponto de vista do operador, essas melhorias foram significativas. Uma mudança de configuração de uma linha (trocando de G1 para ZGC) se traduziu em comportamento mais suave, menos alertas e escalonamento mais previsível.

Uso de Threads Virtuais Java: No modelo tradicional de concorrência, cada manipulador de requisições roda em um thread separado.

Como funcionam os Java Virtual Threads? Threads Virtuais são threads leves introduzidos no Java 19 (Prévia) e Java 21 (Estável). Eles permitem que o Java crie milhões de threads de forma eficiente, ajudando a lidar com tarefas simultâneas sem desperdiçar memória ou CPU.

![unnamed](https://github.com/user-attachments/assets/bbaf6194-df40-424b-8abf-40b62d102f97)

Threads virtuais não mapeiam 1:1 para as threads do sistema operacional e não substituem as threads originais da plataforma. As Threads de Plataforma são suportadas pelas Threads do SO e às vezes também são conhecidas como Threads de Operadora nesse contexto.

Pense nos Threads de Plataforma como um pequeno grupo de trabalhadores, e nos Threads virtuais como tarefas. Com threads virtuais, as tarefas são atribuídas aos trabalhadores apenas quando necessário, permitindo que um trabalhador gerencie milhares de tarefas de forma eficiente.

Veja como funcionam os Virtual Threads:

1. Threads virtuais rodam sobre Threads de Plataforma. A JVM os agenda em um pequeno número de Threads de Plataforma.

2. Quando uma Thread Virtual inicia, a JVM a atribui a uma Thread de Plataforma normal apoiada pelo sistema operacional.

3. Threads Virtuais também podem lidar com trabalhos intensivos em CPU, mas sua verdadeira vantagem está em cenários com um alto número de tarefas de I/O ou concorrentes.

4. Se a Thread Virtual realiza uma operação de bloqueio (como I/O, chamada de banco de dados, suspensão, etc.), a JVM a desmonta da Thread da Plataforma. No entanto, isso não bloqueia a thread do sistema operacional subjacente propriamente dita.

5. A thread da Plataforma é liberada para gerenciar outra Thread Virtual.

6. Quando a operação de bloqueio termina, a Thread Virtual é reagendada em qualquer thread disponível da Plataforma.

Para sistemas de alta produtividade, isso leva a um alto número de threads, uso inflacionado de memória e sobrecarga de escalonamento. A Netflix enfrentou exatamente essa situação, especialmente em sua pilha GraphQL, onde resolvers de campo individuais podem realizar I/O bloqueadores.

Paralelizar essas chamadas de resolver manualmente era possível, mas doloroso. Os desenvolvedores precisavam raciocinar sobre pools de threads, gerenciar "CompletableFutures" e lidar com a complexidade de misturar modelos bloqueantes e não bloqueantes. A maioria não se preocupava a menos que o desempenho tornasse inevitável.

Com o Java 21+, a Netflix começou a lançar threads virtuais. Essas threads leves, agendadas pela JVM em vez do sistema operacional, permitiam que o código bloqueador escalasse sem monopolizar recursos. Para serviços construídos sobre a estrutura DGS e Spring Boot, a integração era automática. Resolvers agora podiam rodar em paralelo por padrão.

Veja o diagrama abaixo que mostra o conceito de threads virtuais em Java.

Uso de Threads Virtuais Java: No modelo tradicional de concorrência, cada manipulador de requisições roda em um thread separado.

Para sistemas de alta produtividade, isso leva a um alto número de threads, uso inflacionado de memória e sobrecarga de escalonamento. A Netflix enfrentou exatamente essa situação, especialmente em sua pilha GraphQL, onde resolvers de campo individuais podem realizar I/O bloqueadores.

Paralelizar essas chamadas de resolver manualmente era possível, mas doloroso. Os desenvolvedores precisavam raciocinar sobre pools de threads, gerenciar "CompletableFutures" e lidar com a complexidade de misturar modelos bloqueantes e não bloqueantes. A maioria não se preocupava a menos que o desempenho tornasse inevitável.

Com o Java 21+, a Netflix começou a lançar threads virtuais. Essas threads leves, agendadas pela JVM em vez do sistema operacional, permitiam que o código bloqueador escalasse sem monopolizar recursos. Para serviços construídos sobre a estrutura DGS e Spring Boot, a integração era automática. Resolvers agora podiam rodar em paralelo por padrão.

Veja o diagrama abaixo que mostra o conceito de threads virtuais em Java:

![17ec33d3-6394-485b-acca-f9381bd29533_1600x1061](https://github.com/user-attachments/assets/b1d1b1fb-6d26-47d9-bb22-4403b75e9573)

Pegue o exemplo comum de uma consulta GraphQL que retorna cinco programas, cada um exigindo dados de arte. Anteriormente, o resolver que buscava URLs de arte rodava em série, adicionando latência entre múltiplas chamadas. Com threads virtuais, essas chamadas agora são executadas em paralelo, reduzindo significativamente o tempo total de resposta, sem alterar o código da aplicação.

A Netflix conectou suporte a threads virtuais diretamente em seus frameworks:

- Serviços baseados em Spring Boot se beneficiam automaticamente da execução paralela em resolvers de campo.

- Desenvolvedores não precisam usar novas APIs ou alterar anotações.

- O modelo de escalonamento de threads permanece abstrato, preservando fluxos de trabalho de desenvolvimento familiares.

Esse modelo de opt-in por padrão funciona porque threads virtuais impõem quase nenhuma sobrecarga. A JVM os gerencia de forma eficiente, tornando-os adequados mesmo em caminhos de alto volume onde modelos tradicionais de thread-per-request falham.

Trade-Offs: Threads virtuais não são mágica. Experimentos iniciais revelaram um modo de falha específico: bloqueios causados pelo fixação de thread.

Veja o que aconteceu:

- Algumas bibliotecas usavam blocos ou métodos sincronizados.
- Quando uma thread virtual entra em um bloco sincronizado, ela passa a ser fixada a uma thread física da plataforma.
- Se muitas threads virtuais fixadas bloquearem enquanto seguram locks, e o pool de threads da plataforma for esgotado, o sistema pode travar. Nenhuma thread pode avançar, porque a thread que segura o lock não pode ser agendada.

A Netflix enfrentou exatamente esse cenário na produção.

O problema era sério o suficiente para que a Netflix recuasse temporariamente na adoção agressiva de threads virtuais. Mas com o JDK 24, esse problema foi resolvido diretamente: a JVM reescreveu o interno do sincronizado para evitar fixação desnecessária de threads.

Com essa mudança, a equipe de engenharia pôde avançar novamente. Os ganhos de desempenho e simplicidade são bons demais para serem ignorados, e agora o risco foi significativamente reduzido.

Why Netflix Moved Away from RxJava?

A Netflix ajudou a pioneirar a programação reativa no ecossistema Java. A RX Java, uma das primeiras e mais influentes bibliotecas reativas, nasceu internamente. Por anos, abstrações reativas moldaram a forma como os serviços lidavam com cargas de trabalho de alta concorrência.

A programação reativa se destaca quando aplicada de ponta a ponta: E/S de rede, computação e armazenamento de dados todos envolvidos em fluxos não bloqueantes e orientados por eventos. Mas esse modelo exige total adesão. Na prática, a maioria dos sistemas fica em algum lugar intermediário: algumas bibliotecas assíncronas, outras bloqueando IOs e muito código legado. O resultado é uma mistura frágil de modelos de concorrência que é difícil de raciocinar, difícil de depurar e fácil de errar.

Um ponto problemático frequente era combinar um modelo thread-per-request com um cliente HTTP reativo como o WebClient. Mesmo quando funcionava, introduzia duas camadas de concorrência (uma bloqueadora e outra não bloqueadora), criando modos de falha complexos e contenção de recursos. Era eficaz para certos casos de expansão por dispersão, mas operacionalmente cara.

A introdução de threads virtuais mudou a equação. Como mencionado, eles permitiram milhares de operações de bloqueio simultâneas sem a sobrecarga dos threads tradicionais. Combinado com concorrência estruturada, os desenvolvedores podem expressar fluxos de trabalho assíncronos complexos usando código simples, sem o inferno de retorno de chamadas da programação reativa.

Dito isso, a programação reativa ainda tem seu lugar. Em serviços com cadeias longas de IO, preocupações com backpressure ou cargas de trabalho em streaming, APIs reativas continuam sendo úteis. No entanto, para a maior parte do backend da Netflix, que envolve chamadas RPC, joins em memória e tempos de resposta apertados, threads virtuais e concorrência estruturada oferecem os mesmos benefícios, mas com menor complexidade.

The Spring Boot Netflix Stack

A Netflix padroniza seus serviços de backend no Spring Boot, não como um framework pronto, mas como base para uma plataforma profundamente integrada e extensível. Todo serviço roda no que a equipe chama de "Spring Boot Netflix": uma pilha curada de módulos que sobrepõem infraestrutura específica da empresa ao ecossistema familiar da Spring.

Esse design mantém o modelo de programação limpo. Desenvolvedores usam anotações e expressões expressivas padrão do Spring. Por trás do capô, a Netflix insere lógica personalizada para tudo, desde autenticação até descoberta de serviços.

A Spring Boot Stack da Netflix inclui:

- Integração de segurança com os sistemas de autenticação e autorização da Netflix, exposta por meio de anotações padrão da Spring Security como @Secured e @PreAuthorize.

- Suporte à observabilidade usando as APIs Micrometer da Spring, conectadas a pipelines internos de rastreamento, métricas e logs construídos para lidar com telemetria em escala da Netflix.

- Integração com malha de serviço para todo o tráfego via um sistema baseado em proxy (construído sobre ProxyD), lidando com TLS, descoberta de serviços e políticas de retentativa de forma transparente.

- framework gRPC baseado em modelos de programação orientados por anotação, permitindo que engenheiros escrevam serviços gRPC com a mesma abordagem dos controladores REST.

- Configuração dinâmica com "propriedades rápidas": configurações alteráveis em tempo de execução que evitam reinicializações de serviço e permitem ajustes ao vivo durante incidentes.

- Clientes retryables envolveram gRPC e WebClient para impor timeouts, tentativas e estratégias de reposição logo de cara.

A Netflix permanece alinhada com o Spring Boot no montante. Versões menores chegam à frota em poucos dias. Para lançamentos principais, a equipe constrói ferramentas e camadas de compatibilidade para suavizar o caminho de atualização.

A mudança para o Spring Boot 3 exigiu migrar de javax.* para jakarta.* namespaces. Foi uma mudança que afetou muitas bibliotecas. Em vez de esperar por atualizações externas, a Netflix desenvolveu um plugin Gradle que realiza transformações de bytecode em tempo de resolução de artefatos. Esse plugin reescreve classes compiladas para usar as novas APIs de Jacarta, permitindo que bibliotecas da era Spring Boot 2 funcionem no Spring Boot 3 sem alterações na fonte.

- https://www.infoq.com/presentations/netflix-java/?utm_source=substack&utm_medium=email
- https://youtu.be/5dpLVvRpPPs

# 🥛 [Java] Kotlin

**Kotlin** é uma linguagem de programação moderna, concisa e segura que foi projetada para interoperar plenamente com o Java. Desenvolvida pela JetBrains, Kotlin se tornou uma escolha popular para o desenvolvimento de aplicativos Android, servidores, web e desktop. Kotlin é uma linguagem versátil e poderosa que tem ganhado popularidade rapidamente devido à sua concisão, segurança e interoperabilidade com Java.

Por que Kotlin? Kotlin é uma linguagem de programação multiparadigma. Ele suporta muitos recursos de linguagens diferentes de vários outros linguagens. Se um recurso estiver ausente, não será difícil criar um código em Kotlin que o incorpore.

É amplamente adotada para desenvolvimento Android, mas também é adequada para back-end, web e desenvolvimento multiplataforma. As corrotinas do Kotlin são um daqueles açúcares sintáticos de async/await que realmente simplificam o código assíncrono sem sacrificar desempenho, tornam o desenvolvimento assíncrono mais intuitivo e menos verboso.

Todo desenvolvedor de back-end que você encontra hoje diria que codifica em JavaScript, Python, PHP ou Ruby. Há uma pequena porcentagem de pessoas que você conheceria que mudaram para o Kotlin como sua escolha de linguagem para criar servidores da Web nos últimos anos.

Por exemplo, Haskell — uma linguagem puramente funcional — usa o para compor duas funções juntas. Em Kotlin, você pode escrever uma função que executa o comportamento exato.

Principais Características da Linguagem Kotlin:

1. **Interoperabilidade com Java**: Kotlin é totalmente compatível com Java, o que significa que você pode chamar código Java a partir de Kotlin e vice-versa. Isso facilita a integração com projetos existentes em Java.

2. **Sintaxe Concisa**: Kotlin foi projetada para ser mais expressiva e concisa do que Java. Muitas construções comuns podem ser escritas de forma mais curta e clara em Kotlin.

3. **Segurança Nula**: Um dos principais problemas em Java é o null pointer exception (NPE). Kotlin aborda isso de forma nativa com seu sistema de tipos que diferencia entre tipos que podem ser nulos e tipos que não podem ser nulos.

4. **Funções de Extensão**: Permitem que você adicione novas funcionalidades a classes existentes sem ter que herdá-las ou usar padrões de design como Decorator.

5. **Suporte para Programação Funcional**: Kotlin suporta muitos paradigmas de programação funcional, incluindo lambdas, funções de alta ordem, e imutabilidade.

6. **Coroutines para Programação Assíncrona**: Coroutines facilitam a escrita de código assíncrono, permitindo a execução de operações de longo prazo, como chamadas de rede ou operações de E/S, de maneira não bloqueante.

7. **Null Safety**: O sistema de tipos do Kotlin tem null safety integrada, reduzindo a probabilidade de erros de referência nula.

8. **Estrutura Moderna de Linguagem**: Kotlin possui várias características modernas de linguagem, como propriedades, delegados, data classes, sealed classes, entre outros, que ajudam a escrever código mais limpo e expressivo.

Exemplo de Código em Kotlin:

```kotlin
// Declaração de uma função de extensão para a classe String
fun String.lastChar(): Char = this[this.length - 1]

fun main() {
// Declaração de uma variável imutável (val)
val name: String = "Kotlin"
// Uso da função de extensão
println(name.lastChar()) // Saída: n

// Exemplo de null safety
var nullableName: String? = null
println(nullableName?.length) // Saída: null

// Exemplo de uma data class
data class User(val name: String, val age: Int)

val user = User("John Doe", 30)
println(user) // Saída: User(name=John Doe, age=30)

// Uso de coroutines
runBlocking {
launch {
delay(1000L)
println("World!")
}
println("Hello,")
}
}
```

Ecossistema Kotlin:

1. **Desenvolvimento Android**: Kotlin é a linguagem preferida para o desenvolvimento de aplicativos Android. A Google anunciou suporte total para Kotlin como uma linguagem de primeira classe para o desenvolvimento Android em 2017.

2. **Kotlin/JS**: Permite escrever código Kotlin que pode ser compilado para JavaScript, possibilitando o desenvolvimento front-end.

3. **Kotlin/Native**: Permite a compilação de código Kotlin para binários que podem rodar sem uma Máquina Virtual Java (JVM), permitindo o desenvolvimento para plataformas onde a JVM não está disponível, como iOS.

4. **Kotlin Multiplatform**: Permite compartilhar código comum entre diferentes plataformas (JVM, JavaScript, iOS, etc.), facilitando o desenvolvimento de aplicativos multiplataforma.

Já existem inúmeras bibliotecas disponíveis para os amantes do Kotlin. Os mais populares são:

- O **Ktor** é uma estrutura para criar facilmente aplicativos conectados - aplicativos da web, serviços HTTP, aplicativos móveis e de navegador. Os aplicativos conectados modernos precisam ser assíncronos para fornecer a melhor experiência aos usuários, e as corrotinas Kotlin fornecem recursos incríveis para fazer isso de maneira fácil e direta.

- Um servidor **http4k** é apenas uma função regular que é invocada com uma solicitação e retorna uma resposta.

- **Javalin** é um framework web simples para Java e Kotlin.

- Spring com o Spring Boot em seu aplicativo, apenas algumas linhas de código são tudo o que você precisa para começar a criar serviços como um chefe.

## [Kotlin] Hello, World
O estilo da sintaxe é parecido com as linguagens de programação modernas como Rust, Dart:

```kotlin
fun main() {
println("Hello, World!")
}
```

## [Kotlin] Corrotinas
As **corrotinas** são operações leves do tipo thread que se saem bem em simultaneidade e na gravação de código assíncrono. Ao contrário do Java, o Kotlin abandonou a ideia de multi-threading e adotou um método de suspensão de funções e corrotinas.

As corrotinas são executadas com muito mais rapidez e eficiência. Também é possível que várias corrotinas sejam executadas na mesma linha de execução.

```kotlin
fun main() = runBlocking { // this: CoroutineScope
launch { // launch a new coroutine and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
print("Hello, ") // main coroutine continues while a previous one is delayed
}
```

## [Kotlin] Parâmetros nomeados
Em Kotlin, você pode invocar funções usando o nome do parâmetro junto com seu valor, independentemente da ordem dos parâmetros. Isso torna o código altamente legível e fácil de depurar. Ter parâmetros nomeados com parâmetros padrão na definição de função exige alta flexibilidade.

```kotlin
fun Application.configureRoutes() {
routing {
route("/greet") {
get {
call.respondText(
text = "Hello, World!", // <- named parameters
status = HttpStatusCode.OK
)
}
}
}
}
```

O acima obviamente não é o melhor código, mas mostra o uso de parâmetros nomeados.

## [Kotlin] Funções de extensão
O que você viu acima também é um exemplo de uma **função de extensão**. Em outras linguagens de programação, você não pode adicionar funções a uma classe existente (que é um arquivo somente leitura). No entanto, no Kotlin, você pode definir Extension Functions que se comportam como se fossem membros dessa classe específica.

```kotlin
fun String.countDigits(): Int {
return this.count { it in '0'..'9'}
}

fun main() {
println("hello1234".countDigits())
}
```

Não posso alterar a definição de , mas posso estendê-la com uma função definida pelo `user.String`

## [Kotlin] Classes de dados
As **classes de dados** são super importantes quando se trata de lidar com o desenvolvimento de back-end. Eles (com listas) são facilmente serializáveis em dados JSON. As classes de dados são usadas para criar objetos de modelo com tipos de dados primitivos. Tipos de dados complexos podem ser usados se e somente se forem classes de dados (serializáveis) também.

Os benefícios do uso de classes de dados são

- `toString` gerado automaticamente.
- `equals` é gerada uma função que compara o conteúdo de suas propriedades.
- Função embutida que copia as propriedades em um novo `objeto.copy`

```kotlin
class Order(
val number: Int,
val items: List
)

data class OrderData(
val number: Int,
val items: List
)

fun main() {
val ord1 = Order(1, listOf("Banana"))
val ord2 = Order(1, listOf("Banana"))

val ord3 = OrderData(2, listOf("Apple"))
val ord4 = OrderData(2, listOf("Apple"))

println(ord1) // Order@65b54208
println(ord3) // OrderData(number=2, items=[Apple])

println(ord1 == ord2) // false
println(ord3 == ord4) // true

val ord5 = ord1.copy() // Compilation error
val ord6 = ord3.copy(
items = ord3.items + "Orange" // Modifying the items property
)
println(ord6) // OrderData(number=2, items=[Apple, Orange])
}
```

## [Kotlin] DSL Kotlin
**Kotlin DSL** é um recurso brilhante implementado pela JetBrains. Isso permite que você use o Kotlin para criar código para qualquer outra linguagem com facilidade. Por exemplo, se você quiser enviar de volta um arquivo HTML com uma solicitação recebida, poderá escrever um código DSL HTML em Kotlin e retornar sua representação de string.

Depois de importar as dependências corretas, você pode escrever DSL como o seguinte:

```kotlin
fun main() {
val htmlContent = html {
head {
title { +"This is Kotlin DSL" }
style {
css(".color-red") {
color = "#ff0000"
}
css("h1") {
cursor = "pointer"
fontSize = "2.5rem"
}
}
}
body {
h1 {
+"Heading 1 is 2.5rem big."
}
p(className = "color-red") {
+"Red"
b { +" and bold" }
}
}
}

println(htmlContent)
}
```

## [Kotlin] Spring Boot com Kotlin


O **Spring Boot com Kotlin** é uma combinação moderna e poderosa para o desenvolvimento de aplicações backend. O Spring Boot é um framework da plataforma Spring (Java) que simplifica a criação de aplicações web e APIs, enquanto o Kotlin é uma linguagem moderna, concisa e segura, desenvolvida pela JetBrains, que roda na JVM (Java Virtual Machine).

Usar Spring Boot com Kotlin significa unir a robustez do ecossistema Spring com a expressividade, segurança e produtividade do Kotlin. Resumindo, usar Spring Boot com Kotlin é apostar em um desenvolvimento mais conciso, moderno e seguro, sem abrir mão do poder do ecossistema Spring. É uma das combinações preferidas hoje por quem deseja construir aplicações sólidas com menos fricção e mais produtividade.

Essa junção oferece várias vantagens. O Kotlin reduz a verbosidade típica do Java, permitindo que você escreva menos código para fazer mais. Isso é especialmente útil em aplicações Spring, onde a configuração e o boilerplate podem ser extensos. Além disso, o Kotlin traz recursos como null safety (segurança contra nulos em tempo de compilação), data classes (para representar dados de forma enxuta), corrotinas (para programação assíncrona e concorrente com menor complexidade), e funções de extensão (que deixam o código mais fluido).

O Spring Boot, por sua vez, continua fornecendo suas principais qualidades: autoconfiguração, injeção de dependência, suporte a APIs REST, persistência com Spring Data JPA, segurança com Spring Security, mensageria, testes, entre outros. Ao usar Kotlin, esses recursos permanecem disponíveis — e muitos são até otimizados para trabalhar melhor com Kotlin (como o Spring Fu e Kofu, que são experimentos focados em configurações Kotlin DSL).

Na prática, você pode escrever controladores REST com classes Kotlin, criar repositórios com interfaces do Spring Data, e usar corrotinas em chamadas assíncronas com WebFlux, por exemplo. O Spring Boot reconhece arquivos `.kt`, e o build geralmente é feito com Gradle (especialmente usando Kotlin DSL no `build.gradle.kts`).

# 🧊 [Java] Quarkus
**Quarkus** é um framework Java projetado para implantação no Kubernetes. Os principais componentes de tecnologia relacionados a ele são o OpenJDK HotSpot e o GraalVM. O Quarkus tem como objetivo tornar o Java uma plataforma de destaque em ambientes Kubernetes e serverless, além de oferecer aos desenvolvedores um modelo de programação reativo e imperativo unificado para atender de forma otimizada a uma ampla gama de arquiteturas de aplicações distribuídas.

![545396924_1535613304553565_5956882820855096625_n](https://github.com/user-attachments/assets/85285f40-81a3-48df-a02b-6b3e1f84ca29)

Quarkus Funqy

# 🧪 [Java] TDD, BDD e DDD
![Cucumber](https://img.shields.io/badge/-Cucumber-23D96C?style=badge&logo=cucumber&logoColor=white)

É totalmente possível aplicar **TDD (Test-Driven Development)**, **BDD (Behavior-Driven Development)** e **DDD (Domain-Driven Design)** em projetos Java, e o ecossistema da linguagem tem um suporte robusto para essas práticas. Kotlin também é uma linguagem plenamente capaz de adotar TDD, BDD e DDD, e inclusive se beneficia da sintaxe mais expressiva e concisa para aplicar esses paradigmas de forma elegante, sendo uma grande vantagem. Para desenvolvimento mobile em Android, o TDD (Test-Driven Development) em Java, o mesmo ocorre com Kotlin, segue os mesmos princípios fundamentais que no back-end Java tradicional, mas com algumas nuances específicas do ambiente Android.

Já o **DDD** é mais arquitetural, sendo uma abordagem para modelar sistemas complexos com base no domínio da aplicação. Em Java, DDD é frequentemente aplicado em conjunto com frameworks como **Spring Boot**, que oferece suporte a uma arquitetura em camadas (Domain, Application, Infrastructure, etc). Classes como Entity, ValueObject, Repository, Service, AggregateRoot são modeladas diretamente no código para representar conceitos do negócio. O Spring Data, por exemplo, facilita a implementação do padrão Repository. DDD em Java também é impulsionado pela maturidade da linguagem e seu uso em sistemas corporativos, que geralmente têm um domínio de negócio complexo.

É comum ver DDD sendo aplicado com TDD, e o BDD como uma ponte de comunicação entre o domínio e os testes automatizados. Em conjunto, essas práticas criam um ecossistema onde o código é bem testado, orientado ao negócio, e de fácil manutenção. Java, com sua vasta gama de bibliotecas e frameworks, oferece um ambiente maduro e bem documentado para a aplicação dessas metodologias.

O **TDD** em Java é amplamente utilizado e bem estabelecido. A prática envolve escrever testes antes da implementação, geralmente com frameworks como **JUnit** e **Mockito**. Você começa escrevendo um teste que falha, implementa o código mínimo necessário para passar no teste e depois refatora. Isso é comum em aplicações Java, principalmente usando IDEs como IntelliJ ou Eclipse, que integram bem essas ferramentas. Geralmente, o TDD em Java acompanha o quarteto: JUnit5, Mockito, Gradle e Maven.

No entanto, isso não impede, e muitas vezes exige que a aplicação seja complementada com ferramentas de logging e monitoring (observabilidade), mesmo durante o desenvolvimento guiado por testes. O Log4j (ou outras alternativas como SLF4J com Logback) é comumente usado para logar comportamentos importantes da aplicação, e isso pode inclusive ajudar nos testes, especialmente nos de integração e testes funcionais. Com o log ativado, é possível rastrear eventos, verificar se exceções foram corretamente capturadas e até usar os logs como apoio na escrita de asserts em testes mais elaborados.

Para desenvolvimento Android, em Java ou Kotlin, você ainda pode usar JUnit 5 e Mockito para testes unitários puros, especialmente para testar lógica de negócio, `ViewModels`, `UseCases`, `Repositories`, etc. O Gradle continua sendo a ferramenta principal para build e integração de testes no Android Studio, que é a IDE padrão no Android e já vem altamente integrada com essas ferramentas.

mockito-tutorial

O **BDD** se constrói sobre o TDD, com foco na descrição do comportamento do sistema em linguagem natural. Em Java, o framework mais popular para isso é o **Cucumber**, que permite escrever testes em **Gherkin**, um formato descritivo que usa estruturas como `Given`, `When`, `Then`. O Cucumber interpreta essas especificações e as vincula a implementações Java, promovendo colaboração entre desenvolvedores, QA e product owners.