0

Desenvolvimento Avançado em Java(Revisão)

Vinicius Almeida
Vinicius Almeida

Paradigma Funcional no Java

De acordo com o professor a base da linguagem Java foi herdada da linguagem C, qual foi constituída em cima de um paradigma Imperativo.

  • Programação Funcional
  • É o processo de construir software através de composição de funções puras evitando compartilhamento de estados, dados mutáveis e efeitos colaterais. É declarativa ao invés de imperativa, essa é uma definição de Eric Elliot.
  • Paradigma Imperativo
  • É aquele que expressa o código através de comandos ao computador, nele é possível ter controle de estado dos objetos.
  • Exemplo:
class Imperativo{
 public static void main(String[] args){
 int valor =10;
 int resultado = valor*3;
 System.out.println("O resultado é :: " + resultado);
 }
}
  • Aqui cada linha é executada como um comando.
  • Paradigma Funcional
  • É aquele onde passamos uma regra ou declaração de como queremos que o programa se comporte.
  • Exemplo:
class Funcional{
 public static void main(String[] args){
 UnaryOperator<Integer> calcularValorVezes3 = valor -> valor*3;
 int valor =10;
 System.out.println("O resultado é :: " + calcularValorVezes3.apply(10));
 }
}
  • Aqui declaramos como queremos que a conta seja efetuada mas ela não é realizada até o momento em que é impressa.
  • Composição de Funções
  • É criar uma nova função através da composição de outras.
  • No exemplo abaixo, é criada uma função que filtra um array, pegando somente os números pares e multiplicando por dois.
  • Exemplo:
class ComposicaoDeFuncoes{
public static void main(String[] args){
int[] valores ={1,2,3,4};
Arrays.stream(valores)
// filtra todos os numeros da lista com resto zero
  .filter(numero -> numero % 2 == 0) 
//multiplica os numeros filtrados por 2
  .map(numero -> numero * 2)
//Imprime cada resultado
  .forEach(numero -> System.out.println(numero));
}
}
  • Funções Puras
  • É chamada de pura toda função que quando invocada mais de uma vez, produz exatamente o mesmo resultado.
  • Exemplo:
class FuncoesPuras{
public static void main(String[] args){
 BiPredicate<Integer, Integer> verificaSeEMaior =
 (parametro, valorComparacao) -> parametro > valorComparacao;
 
 System.out.println(verificaSeEMaior.test(12,13)); //retorna TRUE
 System.out.println(verificaSeEMaior.test(12,13)); //também retorna TRUE
}
}
  • Imutabilidade
  • É o conceito que diz que uma vez que uma variável ou objeto recebeu um valor, vai possuir esse valor para sempre, ou seja, uma vez criado, não pode ter seu valor modificado.
  • Exemplo:
class Imutabilidade{
public static void main(String[] args){
 int valor = 20;
 UnaryOperator<Integer> retornarDobro = v -> v * 2;
 
 System.out.println(retornarDobro.apply(valor)); //retorna 40
 System.out.println(valor); //retorna 20 pois o valor não foi alterado
 }
}

Lambda no Java

  • Os lambdas obedecem o conceito de paradigma funcional. Com eles podemos facilitar a legibilidade de nosso código e com a nova API do Java podemos ter uma alta produtividade para lidar com objetos. Primeiramente, devemos entender o que são interfaces funcionais.
  • Interfaces Funcionais
  • São interfaces que possuem apenas um método abstrato, como no exemplo abaixo:
public interface Funcao{
 String gerar(String valor);
}
  • Geralmente as interfaces funcionais possuem uma anotação em nível de classe(@FunctionalInterface), para forçar o compilador a apontar um erro se a interface não estiver de acordo com as regras de uma interface funcional. Esta anotação não é obrigatória, pois o compilador consegue reconhecer este tipo de interface em tempo de compilação.
  • Exemplo:
public class Aula {
 public static void main(String[] args) {
   Funcao1 funcao1 = valor -> 
     return valor;    
   System.out.println(funcao1.gerar("Joao"));
 }
}
​
interface Funcao1 {
 String gerar(String valor);
}
  • Antes do Java 8 era necessário o uso de uma classe anônima para criar um comportamento específico para uma classe.
  • Exemplo:
public class FuncaoComClasseAnonima {
  public static void main(String[] args) {
    Funcao colocarPrefixoSenhorNaString = new Funcao() {
      @Override
      public String gerar(String valor) {
        return "Sr. "+valor;
     }
   };
    System.out.println(colocarPrefixoSenhorNaString.gerar("Joao"));
 }
}
​
@FunctionalInterface
interface Funcao1 {
  String gerar(String valor);
}
  • Definindo uma Expressão Lambda
  • A estrutura de uma expressão Lambda pode ser definida como:
  • InterfaceFuncional nomeVariavel = parametro -> logica;
  • Obs: Se a expressão lambda possuir apenas uma instrução, não se faz necessário o uso de chaves.
  • Exemplo com uma instrução:
public class FuncaoComLambda {
 public static void main(String[] args) {
   Funcao colocarPrefixoSenhorNaString = valor -> "Sr. "+valor;
   System.out.println(colocarPrefixoSenhorNaString.gerar("Joao"));
 }
}
  • Exemplo com mais de uma instrução:
public class FuncaoComLambda {
 public static void main(String[] args) {
   Funcao colocarPrefixoSenhorNaString = valor -> {
   String valorComPrefixo = "Sr. "+valor;
   String valorComPrefixoEPontoFinal = valorComPrefixo + ".";
   return valorComPrefixoEPontoFinal;
   };
 }
}

Recursividade no Java

  • Na recursividade, uma função chama a si mesma repetidamente, até atingir uma condição de parada. No caso de Java, um método chama a si mesmo, passando para si objetos primitivos. Cada chamada gera uma nova entrada na pilha de execução, e alguns dados podem ser disponibilizados em um escopo global ou local, através de parâmetros em um escopo global ou local. A recursividade tem um papel importante em programação funcional, facilitando que evitemos estados mutáveis e possamos manter nosso programa mais declarativo, e menos imperativo.

Exemplo:

public class FatorialRecursivo {
 public static void main(String[] args) {
   System.out.println(fatorial(5));
 }
​
 public static int fatorial( int value ) {
   if ( value == 1 ) {
     return value;
   } else {
     return value * fatorial((value -1));
   }
 }
​
 // 5
 // 5 * 4 * 3 * 2 * 1
 // 120
}

  • O programa empilhara a chamada de fatorial para cada numero(de 5 até 0). Após empilhar, ele começara a fazer o cálculo do menor numero, ou seja, da esquerda para a direita. O problema deste recurso é que ao colocar um numero muito grande, podemos ter o estouro da pilha(StackOverflow).

Tail Call

  • Tail Call(Recursividade em cauda) é uma recursão onde não há nenhuma linha de código após a chamada do próprio método e, sendo assim, não há nenhum tipo de processamento a ser feito após a chamada recursiva.
  • Obs: A JVM não suporta a recursão em cauda, ele lança um estouro de pilha(StackOverFlow).
  • Memoization
  • É uma técnica de otimização que consiste no cache do resultado de uma função, baseado nos parâmetros de entrada. Com isso, nas seguintes execuções conseguimos ter uma resposta mais rápida.
  • Exemplo:
import java.util.HashMap;
import java.util.Map;
​
class FatorialMemoization {
 static Map<Integer,Integer> MAPA_FATORIAL = new HashMap<>();
 public static void main(String[] args) {
   long I = System.nanoTime();
   System.out.println(fatorialComMemoization(15));
   long F = System.nanoTime();
   System.out.println("FATORIAL 1 "+(F-I));
​
   I = System.nanoTime();
   System.out.println(fatorialComMemoization(15));
   F = System.nanoTime();
   System.out.println("FATORIAL 2 "+(F-I));
 }
​
 public static Integer fatorialComMemoization( Integer value ) {
   if ( value == 1 ) {
     return value;
   } else {
     if (MAPA_FATORIAL.containsKey(value)) {
       return MAPA_FATORIAL.get(value);
     } else {
      Integer resultado = value * fatorialComMemoization(value-1);
       MAPA_FATORIAL.put(value,resultado);
       return resultado;
     }
   }
 }
}
  • Obs: Este método consome muita memória por guardar o cache das operações então as vezes pode não ser a melhor solução para seu problema.

Interfaces Funcionais

  • Funções de Alta Ordem
  • São funções recebem ou retornam uma função por parâmentro.
  • Exemplo:
public class FuncaoAltaOrdem {
 public static void main(String[] args) {
  Calculo soma = ( a, b) -> a+b;
   System.out.println(executarOperacao(soma,1,3));
 }
//Aqui passamos a função lambda acima para o método
 public static int executarOperacao(Calculo calculo,int a, int b){
   return calculo.calcular(a,b);
 }
}
​
@FunctionalInterface
interface Calculo {
 public int calcular(int a, int b);
}
  • Utilizando a biblioteca java.util
  • Consumer
  • Uma outra forma de criar funções de alta ordem é utilizando a biblioteca java.util.function.Consumer. Lembrando que a interface Consumer não aceita retorno. Existem dois meios de utilizar a interface Consumer.
  • Exemplo 1: Método de referência. Aqui o compilador simplesmente entende que deve imprimir o parâmetro de entrada, muito parecido com o que acontece no JavaScript.
import java.util.function.Consumer;
​
public class Consumidores {
 public static void main(String[] args) {  
​
  Consumer<String> imprimirUmaFrase = System.out::println;       imprimirUmaFrase.accept("Hello World");
 }
}
  • Exemplo 2: Expressão Lambda. Passamos uma expressão lambda para o Consumer.
import java.util.function.Consumer;
​
public class Consumidores {
 public static void main(String[] args) { 
  
  Consumer<String> imprimirUmaFrase2 = frase ->System.out.println(frase);
   imprimirUmaFrase.accept("Hello World");
 }
}
  • Function(Função)
  • Além de utilizar a notação @FunctionalInterface, as funções também são TypeSafe.
  • Para poder declarar uma função, precisamos da biblioteca java.util.function.Function.
  • Seu escopo possui a seguinte forma:
  • Function<ParametroEntrada , ParametroSaida> nomeFuncao = empressão Lambda;
  • Exemplo:
import java.util.function.Function;
​
public class Funcoes {
 public static void main(String[] args) {
  //Recebe uma string e retorna uma string
   Function<String,String> retornarNomeAoContrario = texto -> new StringBuilder(texto).reverse().toString();
    
   //Recebe uma string e retorna um int
   Function<String,Integer> converterStringParaInteiroECalcularODobro = string ->    Integer.valueOf(string) * 2;
   System.out.println(retornarNomeAoContrario.apply("joao"));
   System.out.println(converterStringParaInteiroECalcularODobro.apply("20"));
 }
}
  • Predicate(Predicato)
  • Recebem um parâmetro e retornam um valor boleano. Utilizado para fazer validações. Também é TypeSafe. Utiliza a biblioteca java.util.function.Predicate.
  • Pode-se utilizar expressões lambda.
  • Exemplo:
import java.util.function.Predicate;
​
public class Predicados {
 public static void main(String[] args) {
   Predicate<String> estaVazio = String::isEmpty;
   System.out.println(estaVazio.test(""));
   System.out.println(estaVazio.test("Joao"));
 }
}
  • Suppliers(Supridores)
  • Serve para retornar o parâmetro. Utiliza a biblioteca java.util.function.Supplier.
  • Exemplo:
import java.util.function.Supplier;
​
public class Suplidores {
 public static void main(String[] args) {
   Supplier<Pessoa> instanciaPessoa = () -> new Pessoa();
   Supplier<Pessoa> instanciaPessoa2 = Pessoa::new;
   System.out.println(instanciaPessoa.get());
   System.out.println(instanciaPessoa2.get());
 }
}
​
class Pessoa {
 private String nome;
 private Integer idade;
​
 public Pessoa() {
   nome = "joao";
   idade = 23;
 }
​
 @Override
 public String toString() {
   return String.format("nome : %s, idade: %d",nome,idade) ;
 }
}
  • Iterações
  • Utiliza a biblioteca java.util.stream.Stream, onde Stream é uma classe estática. Pode ser chamada através do método Stream.of(coleção) ou pela própria coleção.
  • filter() : Utiliza um predicado, ou seja, recebe um parâmetro e retorna um booleano.
  • collect() : Coleta os resultados de acordo com os parâmetros passados.
  • Collectors.joining() : Utiliza a classe estática Collectors que faz a junção das Strings coletadas em uma única String.
  • forEach() : Recebe um Consumer. Para cada item da lista ou array, ele faz uma ação.
  • map() : Recebe uma Function, faz uma ação e retorna seu valor.
  • Exemplo:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
​
public class Iteracoes {
​
 public static void main(String[] args) {
   String[] nomes = {"Joao","Joao", "Paulo","Oliveira","Santos","Instrutor","Java"};
   Integer[] numeros = {1,2,3,4,5};
//   imprirmirNomesFiltrados(nomes);
//   imprirmirTodosNomes(nomes);
//   imprirmirODobroDeCadaItemDaLista(numeros);
​
   List<String> profissoes = new ArrayList<>();
   profissoes.add("Desenvolvedor");
   profissoes.add("Testador");
   profissoes.add("Gerente de projeto");
   profissoes.add("Gerente de qualidade");
  
  //Chamada pela coleção
   profissoes.stream()
       .filter(profissao -> profissao.startsWith("Gerente"))
       .forEach(System.out::println);
 }
​
 public static void imprirmirNomesFiltrados(String... nomes){
  //Chamada pelo Stream.of()
   String nomesparaImprimirDaStream = Stream.of(nomes)
       .filter(nome -> nome.equals("Joao"))
       .collect(Collectors.joining());// String
   System.out.println("Nomes do stream: "+nomesparaImprimirDaStream);
 }
​
​
 public static void imprirmirTodosNomes(String... nomes){
   Stream.of(nomes)
     .forEach(nome -> System.out.println("Imprimido pelo forEach: "+nome));
 }
​
 public static void imprirmirODobroDeCadaItemDaLista(Integer... numeros){
   Stream.of(numeros).map( numero -> numero*2)
             .forEach(System.out::println);
 }
}

Processamento Assíncrono e Paralelo

  • Threads
  • É um pequeno programa que trabalha como um subsistema, sendo uma forma de um processo se autodividir em duas ou mais tarefas. Essas múltiplas tarefas podem ser executadas simultaneamente para rodar mais rápido do que um programa em um único bloco ou praticamente juntas.
  • Processamento síncrono
  • São todos os processamentos que ocorrem em sequência (sincronia). Os processos são executados em fila. É preciso que um processo termine para que outro processo seja executado.
  • Exemplo: Imagine você lavando louça e de repente você se lembra que tem que fazer uma ligação. A ligação só poderá ser realizada quando o processo lavar louça for finalizado.
  • Processamento assíncrono
  • Quando dois ou mais processos são realizados ao mesmo tempo, é dado o nome de processamento assíncrono. Os processos são realizados simultaneamente sem que um processo necessite que outro termine para ser executado.
  • Exemplo: Lavar louça e falar ao telefone ao mesmo tempo. Se você não sabe como fazer isso, prenda o telefone entre a cabeça e o ombro e tenha as mãos livres para lavar louça.
  • Utilizando Threads
  • Podemos utilizar Threads de duas formas:
  • Herdando da classe Thread e sobrescrevendo o método Run()
class BarraDeCarregamento extends Thread {
 private Thread iniciarGeradorPdf;
​
 public BarraDeCarregamento(Thread iniciarGeradorPdf) {
   this.iniciarGeradorPdf = iniciarGeradorPdf;
 }
​
 @Override
 public void run() {
   while (true){
     try {
       Thread.sleep(500);
​
       if (!iniciarGeradorPdf.isAlive()){
         break;
       }
       System.out.println("Loading ... ");
​
     } catch (InterruptedException e) {
       e.printStackTrace();
     }
​
   }
  • O método getName() nos retorna o nome da Thread sendo executada.
  • O método start() executa a classe que herda de Thread.
  • O método sleep(t) trava a Thread por t milissegundos.
  • O método start() inicia o processamento paralelo, e libera o programa para executar qualquer outra Thread.
  • isAlive() retorna TRUE se a Thread estiver viva ou FALSE se não estiver.
  • Implementando a Interface Runnable
  • Assim como estendemos nossa classe para Threads, aqui implementamos a interface Runnable e sobrescrevemos o método Run.
class GerarPDF implements Runnable {
 @Override
 public void run(){
 System.out.println("Gerar PDF");
 }
​
}
  • ExecutorService
  • É parecido com o método Run() mas aqui adicionamos as execuções dentro de uma pilha de execuções dentro do ExecutorService.
  • Instanciando um ExecutorService
  • Pode ser instanciado com um novo ExecutorService que recebe o retorno do método estático Executors.newFixedThreadPool.
  • Exemplo:
*Onde maxThreads é o número máximo de Threads que podem rodar em paralelo.
private static final ExecutorService nomeExecutor = Executors.newFixedThreadPool(maxThreads);
​
Exemplo:
private static final ExecutorService pessoasParaExecutarAtividade = Executors.newFixedThreadPool(3);
  • Dada a criação da interface e das classes abaixo:
class Casa {
 private List<Comodo> comodos;
​
 Casa(Comodo... comodos) {
   this.comodos = Arrays.asList(comodos);
 }
​
 List<Atividade> obterAfazeresDaCasa() {
   return this.comodos.stream().map(Comodo::obterAfazeresDoComodo)
       .reduce(new ArrayList<Atividade>(), (pivo, atividades) -> {
         pivo.addAll(atividades);
         return pivo;
       });
 }
}
​
interface Atividade {
 String realizar() throws InterruptedException;
}
​
abstract class Comodo {
 abstract List<Atividade> obterAfazeresDoComodo();
}
​
class Quarto extends Comodo {
 @Override
 List<Atividade> obterAfazeresDoComodo() {
​
   return Arrays.asList(
       this::arrumarACama,
       this::varretOQuarto,
       this::arrumarGuardaRoupa
   );
 }
​
 private String arrumarGuardaRoupa() throws InterruptedException {
   Thread.sleep(5000);
   String arrumar_o_guarda_roupa = "arrumar o guarda roupa";
   System.out.println(arrumar_o_guarda_roupa);
   return arrumar_o_guarda_roupa;
 }
​
 private String varretOQuarto() throws InterruptedException {
   Thread.sleep(7000);
   String varrer_o_quarto = "varrer o quarto";
   System.out.println(varrer_o_quarto);
   return varrer_o_quarto;
 }
​
 private String arrumarACama() throws InterruptedException {
   Thread.sleep(10000);
   String arrumar_a_cama = "Arrumar a cama";
   System.out.println(arrumar_a_cama);
   return arrumar_a_cama;
 }
}
  • Podemos tornar a execução dos métodos assíncrona utlizando utilizando método execute da interface Runnable, porém este método não tem retorno:
public static void main(String[] args) throws InterruptedException {
Casa casa = new Casa(new Quarto());
casa.obterAfazeresDaCasa().forEach(atividade -> pessoasParaExecutarAtividade.execute(()-> atividade.realizar()));
}
​
*Para cada atividade dentro dos afazeres da casa, o ExecutorService irá executar o método em uma Thread, sendo que o máximo definido foram 3.
  • Caso seja necessário um retorno, podemos utilizar o método submit, o qual retorna uma instância da classe FutureTask dentro da interface Future.
  • Exemplo utilizado em aula: Como as listas não são thread safe, instanciamos a classe CopyOnWriteArrayList, para não tomarmos uma CurrentModificationException enquanto tentamos alterar os valores da lista.
List<Future<String>> futuros =
new CopyOnWriteArrayList<>(casa.obterAfazeresDaCasa().stream()
	.map(atividade -> pessoasParaExecutarAtividade.submit(() -> {
                        try {
                            return atividade.realizar();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        return null;
                    })
                )
	.collect(Collectors.toList()));
  • Iterando a Lista:
while (true){
            int numeroDeAtividadesNaoFinalizadas = 0;
            for (Future<?> futuro : futuros) {
                if (futuro.isDone()){
                    try {
                        System.out.println("Parabens voce terminou de "+futuro.get());
                        futuros.remove(futuro);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    }
                }
                else {
                    numeroDeAtividadesNaoFinalizadas++;
                }
            }
            if (futuros.stream().allMatch(Future::isDone)){
                break;
            }
            System.out.println("Numero de atividades nao finalizadas :: "+numeroDeAtividadesNaoFinalizadas);
            Thread.sleep(500);
}
pessoasParaExecutarAtividade.shutdown();
    
  • *Lembrando que a Thread principal só será finalizada se o método shutdown() for chamado.
  • Parallel Streams
  • Utilizado quando necessitamos fazer tratamento de uma quantidade massiva de objetos quais não devem depender um do outro. Por padrão, o método parallel aloca todos os cores do processador -1.
  • Declarando um parallel(): Para declarar o método parallel, precisamos chamado em uma stream como no exemplo abaixo:
IntStream.range(1,100000).parallel().forEach(num -> fatorial(num));

public static long fatorial(long num){
        long fat = 1;

        for (long i = 2; i <= num ; i++) {
            fat*=i;
        }
        return fat;
    }
  • Modularização do Java
  • Jigsaw
  • Há muito tempo se diz sobre modularizar a plataforma Java. É um plano que começou desde antes do Java 7, foi possibilidade no Java 8 e por fim, para permitir mais tempo de desenvolvimento, revisão e testes, foi movido para o Java 9.
  • O projeto Jigsaw, como foi chamado, é composto por uma série de JEPs(Java Enhacement Proposal). Algumas delas inclusive já disponíveis no Java 8, como os conhecidos Compact Profiles. A ideia por trás do projeto não é só criar um sistema de módulos, que poderemos usar em nossos projetos, mas também aplicá-lo em toda a plataforma e JDK em busca de melhor organização e desempenho.
  • Por padrão, todo sistema modular já vem com o módulo java.base , java.util e demais pacotes muitas vezes essenciais para a esmagadora maioria dos projetos.
  • Criando um Modulo
  • Clicamos no pacote de nosso projeto com o click direito:
  • Clicamos em New e depois em Module:
  • Selecionamos a versão da SDK igual a Java 9 ou superior e clicamos em next:
  • Após isso é só dar um nome para o módulo e clicar em Finish.
  • Modularizando os módulos:
  • Por padrão, os módulos não estão modularizados e para isso precisamos criar um arquivo do tipo module-info.java :
  • Para criar este arquivo, clicamos com o botão direito do mouse sobre a pasta src de seu módulo, vamos até New, e clicamos em module-info.java e nosso arquivo está criado.
  • Liberando Acesso para outros módulos:
  • Para liberar acesso para outros módulos necessitamos editar as informações da classe module-info.java que acabamos de criar. Podemos fazer isso através da palavra reservada exports. No exemplo abaixo estou exportando o meu package digital.innovation.one.utils.operacao, assim qualquer módulo poderá utilizar as classes dentro deste pacote.
module digital.innovation.one.utils {
 exports digital.innovation.one.utils.operacao;
}
  • Para restringir quem pode utilizar estes pacotes, podemos utilizar a palavra reservada to : Aqui estou dizendo que estou exportando meu package digital.innovation.one.utils.operacao para(to) o módulo digital.innovation.one.core. Sendo assim, apenas este pacote poderá utilizar as classes dentro do pacote que está sendo exportado.
module digital.innovation.one.utils {
​
exports digital.innovation.one.utils.operacao to digital.innovation.one.core;
}
  • Recebendo o acesso dos módulos:
  • Para poder utilizar as classes de outros módulos que já foram exportadas, precisamos fazer um import dentro do arquivo module-info.java que está dentro do módulo que deseja utilizar as classes importadas. Para isso, utilizamos a palavra reservada requires.
  • Exemplo:
module digital.innovation.one.core {
 requires digital.innovation.one.utils;
}
  • *Note que o requires só irá funcionar se o módulo exportado tiver o limitador to para a classe que está tentando importar as classes ou se o módulo exportado não conter limitador.
  • Novidades do Java 10
  • Inferência de Tipo
  • Uma coisa que foi muito cobrada pela comunidade java foi o recurso de inferência de tipo, o qual já existia em algumas linguagens como por exemplo o C#. A inferência torna o código menos verboso pois não é necessário declarar o tipo da variável do lado esquerdo, já que ela terá seu tipo inferido pelo lado direito com o uso da palavra reservada var.
  • *Lembrando que o var só pode ser utilizado como variável local(dentro de métodos), dentro do for iterativo, e do try with resource.
  • Exemplo:
var url = new URL("https://docs.oracle.com/javase/10/language/");
  • Aqui estamos inferindo na variável url o tipo URL.
  • Docker
  • Instalando o Docker para Windows:
  • Primeiro precisaremos atualizar o pacote do Kernel do Linux. Podemos fazer isso baixando o arquivo do site de documentação da microsoft no link:
  • https://docs.microsoft.com/pt-br/windows/wsl/install-win10#step-4---download-the-linux-kernel-update-package
  • Após instalado o pacote, fazemos download do Docker no link:
  • https://www.docker.com/products/docker-desktop
  • Instalamos o Docker e pedimos para baixar qualquer dependência que seja necessária. Após isso é só reiniciar e provavelmente seu Docker estará funcionando. Para ter certeza vá até o prompt de comando e digite docker, deverá receber uma tela semelhante a essa:
  • Para não deixarmos nossa JVM sem memória, devemos definir o uso máximo de memória do Docker para 512 Mb. Fazemos isso pelo terminal através do comando :
docker container run -it -m512M --entrypoint bash openjdk:7-jdk
  • Com é a primeira vez que rodamos esta imagem, será necessário aguardar o termino de seu download. Se tudo correr corretamente, você receberá uma tela semelhante a abaixo:
  • Quando utilizamos este comando, é como se estivéssemos acessando a maquina openjdk:7-jdk.
  • Após acessarmos a openjdk 7, utilizamos o comando:
java -XX:+PrintFlagsFinal -version
  • Este comando nos mostra os parâmetros que podem ser utilizados na Máquina.
  • Ao utilizarmos o comando:
java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
  • Após este comando, podemos ver que a memória alocada está diferente da estipulada por nós:
  • Isso acontece porque antes da versão do java 10, não era possível definir a memória utilizada pelo Docker. Agora repetiremos os passos acima para a jdk 10 e para isso devemos sair da execução atual do Docker através do comando exit.
  • 134217728
  • 1635778560
  • Vamos rodar o comando para baixar a imagem agora para a jdk 10:
docker container run -it -m512M --entrypoint bash openjdk:10-jdk
  • Novamente, deverá receber uma tela parecida como a imagem abaixo:
  • Após rodarmos novamente o comando:
java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
  • Podemos ver que agora estamos utilzando aproximadamente 1/4 do que foi utilizado com a jdk 7.
  • Definindo o número máximo de CPUS para o Docker:
  • Podemos definir o número máximos de CPUS para o Docker através do comando:
docker container run -it --cpus 2 openjdk:10-jdk
  • No caso deste comando, estamos restringindo o seu uso para apenas duas cpus.
  • Para termos certeza de que foi feita a mudança, utilizamos o seguinte comando dentro do jshell:
Runtime.getRuntime().availableProcessors()
  • Novidades Java 11
  • HTTP Client API
  • Um dos recursos que foram incluídos na versão do JDK 11 é a API do cliente HTTP padronizada que visa substituir a classe HttpUrlConnection legada, que está presente no JDK desde os primeiros anos do Java. O problema com essa API antiga é descrito na proposta de aprimoramento, principalmente porque agora ela é considerada antiga e difícil de usar.
  • A nova API suporta HTTP / 1.1 e HTTP / 2. A versão mais recente do protocolo HTTP foi projetada para melhorar o desempenho geral do envio de solicitações por um cliente e do recebimento de respostas do servidor. Isso é conseguido através da introdução de várias alterações, como multiplexação de fluxo, compactação de cabeçalho e Push Promise. Além disso, o novo cliente HTTP também suporta nativamente WebSockets.
  • No exemplo abaixo que foi passado em aula, aprendemos a fazer requisições de uma URL através da interface http.
private static void connectAndPrintURLJavaOracle() throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()                .GET().uri(URI.create("https://docs.oracle.com/javase/10/language/"))
.build();

HttpClient httpClient = HttpClient.newHttpClient();

HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

System.out.println("Status code :: "+response.statusCode());
System.out.println("Headers response :: "+response.headers());
System.out.println(response.body());
}
  • Por padrão a interface Http utiliza o http 2. Para fins demonstrativos, foi feita um exemplo com o http 1:
private static void connectAkamaiHttp11Client() throws Exception {
System.out.println("Running HTTP/1.1 example ...");
try {
HttpClient httpClient = HttpClient.newBuilder()
          .version(HttpClient.Version.HTTP_1_1)
          .proxy(ProxySelector.getDefault())
          .build();
​
​
long start = System.currentTimeMillis();
​
HttpRequest mainRequest = HttpRequest.newBuilder()          .uri(URI.create("https://http2.akamai.com/demo/h2_demo_frame.html"))
.build();
​
HttpResponse<String> response = httpClient.send(mainRequest, HttpResponse.BodyHandlers.ofString());
​
System.out.println("Status Code ::: " + response.statusCode());
System.out.println("Response Headers ::: "+ response.headers());
String responseBody = response.body();
ystem.out.println(responseBody);
​
​
List<Future<?>> future = new ArrayList<>();
​
responseBody
     .lines()
     .filter(line -> line.trim().startsWith("<img height"))
     .map(line -> line.substring(line.indexOf("src='") + 5, line.indexOf("'/>")))
      .forEach(image -> {
Future<?> imgFuture = executor.submit(() -> {
HttpRequest imgRequest = HttpRequest.newBuilder()                  .uri(URI.create("https://http2.akamai.com"+ image))
       .build();
​
 try {
  HttpResponse<String> imageResponse = httpClient.send(imgRequest,    HttpResponse.BodyHandlers.ofString());
   System.out.println("Imagem Carregada :: "+image+", status code :: "+imageResponse.statusCode());
    } 
 catch (IOException | InterruptedException e) {
    System.out.println("Mensagem de error durante reequesição para recuperar a imagem" + image);
    }
    });
future.add(imgFuture);
System.out.println("Submetido um futuro para imagem :: "+image);
    });
​
​
future.forEach(f -> {
        try {
          f.get();
        } catch (InterruptedException | ExecutionException e) {
          System.out.println("Error ao esperar carregar imagem do futuro");
        }
      });
​
long end = System.currentTimeMillis();
System.out.println("Tempo de carregamento total :: "+ (end - start) + " ms");
    }finally {
      executor.shutdown();
    }
}
  • Para o http2, só mudamos a versão para http2 no método version:
private static void connectAkamaiHttp2Client() throws Exception {
   System.out.println("Running HTTP/2 example ...");
   try {
     HttpClient httpClient = HttpClient.newBuilder()
         .version(HttpClient.Version.HTTP_2)
         .proxy(ProxySelector.getDefault())
         .build();
​
​
     long start = System.currentTimeMillis();
​
     HttpRequest mainRequest = HttpRequest.newBuilder()
         .uri(URI.create("https://http2.akamai.com/demo/h2_demo_frame.html"))
         .build();
​
     HttpResponse<String> response = httpClient.send(mainRequest, HttpResponse.BodyHandlers.ofString());
​
     System.out.println("Status Code ::: " + response.statusCode());
     System.out.println("Response Headers ::: "+ response.headers());
     String responseBody = response.body();
     System.out.println(responseBody);
​
​
     List<Future<?>> future = new ArrayList<>();
​
     responseBody
         .lines()
         .filter(line -> line.trim().startsWith("<img height"))
         .map(line -> line.substring(line.indexOf("src='") + 5, line.indexOf("'/>")))
         .forEach(image -> {
           Future<?> imgFuture = executor.submit(() -> {
             HttpRequest imgRequest = HttpRequest.newBuilder()
                 .uri(URI.create("https://http2.akamai.com"+ image))
                 .build();
​
             try {
               HttpResponse<String> imageResponse = httpClient.send(imgRequest, HttpResponse.BodyHandlers.ofString());
               System.out.println("Imagem Carregada :: "+image+", status code :: "+imageResponse.statusCode());
             } catch (IOException | InterruptedException e) {
               System.out.println("Mensagem de error durante reequesição para recuperar a imagem" + image);
             }
           });
           future.add(imgFuture);
           System.out.println("Submetido um futuro para imagem :: "+image);
         });
​
​
     future.forEach(f -> {
       try {
         f.get();
       } catch (InterruptedException | ExecutionException e) {
         System.out.println("Error ao esperar carregar imagem do futuro");
       }
     });
​
     long end = System.currentTimeMillis();
     System.out.println("Tempo de carregamento total :: "+ (end - start) + " ms");
   }finally {
     executor.shutdown();
   }
 }
  • Inferência com Lambda
  • Na versão 11 do Java é possível inferir tipos dentro do lambda como no exemplo abaixo:
public static void main(String[] args) {
Function<Integer,Double> divisaoPor2 = (var numero) -> numero / 2.0;
​
System.out.println(divisaoPor2.apply(9849387));
}
  • *Note que estamos inferindo o resultado da operação da direita(que será um double) na variável número.
  • StringIsBlank
  • Nas versões anteriores ao Java 11, necessitávamos criar um método para saber se uma string estava em branco(vazia ou com espaços). Agora temos um método próprio do String que faz essa verificação, este método é o isBlank().
  • Exemplo:
public static void main(String[] args) {
String espaco = "         ";
​
System.out.println(espaco.isBlank());
}
  • Retorna um booleano true caso a String tenha apenas espaços, tenha tamanho zero ou seja nula.
  • StringLines
  • Para não termos que ficar procurando onde começam e terminam cada linha para podermos fazer manipulações, o Java 11 veio com o método lines(), qual retorna um stream de linhas que é separado pelas quebras de linha(\n, \r, \r\n).
  • Exemplo:
public static void main(String[] args) {
String html = "<html>\n<head>\n</head>\n <body> \n <body> \n<html>";
​
System.out.println(html.lines().map(s -> "[TAG] :: "+s).
collect(Collectors.toList()));
}
  • Collection
  • Uma Collection é a interface implementada pelas interfaces: Set, List, Map, SortedSet, SortedMap, HashSet, TreeSet, ArrayList, LinkedList, Vector, Collections, Arrays, AbstractCollection. Por ela ser a Classe mãe, ela pode receber as interfaces citadas acima.
  • Exemplo: Aqui estamos passando um Set para a Collection de Strings chamada nomes.
public static void main(String[] args) {
Collection<String> nomes = Set.of("Joao", "Paulo", "Oliveira", "Santos");
​
System.out.println(nomes);
}
  • StringRepeat
  • Para fazer a repetição de uma String várias vezes, podemos utilizar o método de String repeat(n), onde n é numero de vezes que esta String deve ser repetida. Este método simplesmente devolverá uma String concatenada por ela mesma n vezes.
  • Exemplo:
public static void main(String[] args) {
 String nome = "Joao";
 System.out.println(nome.repeat(10));
}
//Resultado: JoaoJoaoJoaoJoaoJoaoJoaoJoaoJoaoJoaoJoao


0
0

Comentários (0)

https://www.linkedin.com/in/vinicius-ornelas-587075128/

Brasil