Java – instanceof Pattern Matching

O Pattern Matching para o operador instanceof foi introduzido como um recurso de visualização com JDK 14 e foi finalizada com JDK 16, desde então, não é surpreendente ver mudanças sendo feitas no JDK para tirar vantagem dessa nova funcionalidade.

Nesta postagem, vamos focar no uso de Pattern Matching do instanceof na implementação anterior (antes da JDK 14) e atual.

Anterior:

if (animal instanceof Duck) {
    Duck duck = (Duck) animal;
    duck.quack();
} else if (animal instanceof Cat) {
    Cat cat = (Cat) animal;
    cat.meow();
}

Atual:

if (animal instanceof Duck duck) {
    duck.quack();
} else if (animal instanceof Cat cat) {
    cat.meow();
}

O que está a acontecer? Simplesmente o resultado do instanceof está sendo atribuído via pattern matching às variáveis cat e duck.

Mais um exemplo bem didático:

if (object instanceof String str && str.length() > 1) {
    //do something...
} else if (object instanceof List list) {
    list.forEach(o -> {
        if (o instanceof String str && str.length() > 1) {
            //do something...
        }
    });
}

Muito legal, não é? Com essa abordagem a nova feature faz com que tenhamos algumas boas vantagens, pois já não será mais preciso escrever tanto código, que dependendo do negócio se tornava muito estressante e tedioso.

Algumas vantagens:

  • Não é mais preciso escrever este tipo de código em que precisamos testar o tipo e fazer uma conversão para cada bloco condicional;
  • Não é mais preciso repetir o nome do tipo três vezes para cada bloco condicional;
  • A legibilidade é muito limpa, pois já não sujamos a conversão e extração do valor para a variável;
  • Não existe mais problemas com legibilidade, e previne possíveis erros de excecução ao adicionar várias variáveis e casts, ou adicionando um novo tipo à da super classe. (ex: animal)

Essa feature veio para tornar nosso código conciso, mais simples de escrever e fácil de ler, todos os dias.

Essas mudanças no JDK para alavancar o pattern matching de instância podem fornecem muitas idéias e exemplos para os desenvolvedores saberem onde começar a aplicar isso no próprio código.

Brian Goetz usa um bom exemplo de como muitas vezes implementamos equals (Object), e vamos exemplificá-lo aqui com o objeto chamado “Marreta“:

É uma implementação bastante comum e conhecida, não é?

@Override
public boolean equals(Object obj){
    if (obj instanceof Marreta) {
       Marreta other = (Marreta) obj;
        if (model.equals(other.model) && strength == other.strength) {
            return true;
        }
    }
    return false;
}

Agora utilizando o Pattern Matching do instanceof:

@Override
public boolean equals(Object obj){
    return (obj instanceof Marreta other && 
            model.equals(other.model) && 
            strength == other.strength);
}

Bom, como desenvolvedores, costumamos usar o operador instanceof em nosso código, e minha intenção foi apresentar e exemplificar como utiliza-lo atualmente(desde a JDK 14), de forma a melhorar sua produtividade e legibilidade.

Obrigado.

🙂

Como utilizar Optional.stream()

Olá! Tudo bem com vocês? Depois de algum tempo ausente, finalmente estou disponível para continuar escrevendo em meu blog.

Hoje vou tentar demonstrar como deixar seu código mais limpo, ajudando assim na leitura do mesmo e também a vida das pessoas que fazem review do seu código java.

Optional.stream() é uma feature do Java 9, porém mesmo inserido na JDK há tanto tempo, muitos ainda não utilizam-o, ou não em sua complexidade.

Vamos iniciar com a sequência de como seria para computar o valor total de um pedido:

public BigDecimal getOrderPrice(Long orderId) {
    List<OrderItem> items = orderRepository.findByOrderId(orderId);
    BigDecimal price = BigDecimal.ZERO;       
    for (OrderItem orderItem : items) {
        price = price.add(orderItem.getPrice());   
    }
    return price;
}
  1. Primeiro vamos ter uma variável para acumular o preço.
  2. A cada iteração adicionamos valor à nossa variável.

Hoje em dia, é provavelmente mais adequado usar streams em vez de iterações. Esse snippet é equivalente ao anterior:

public BigDecimal getOrderPrice(Long orderId) {
    List<OrderItem> items = orderRepository.findByOrderId(orderId);
    return items.stream()
                .map(OrderItem::getPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
}

Temos um problema, pois a variável orderId pode ser nula.

Bem, a maneira imperativa de lidar com valores nulos é verificá-los no início do método e, se for o caso, lançar uma exceção.

Vamos atualizar nosso código:

public BigDecimal getOrderPrice(Long orderId) {
    if (orderId == null) {
        throw new IllegalArgumentException("Order ID cannot be null.");
    }
    List<OrderItem> items = orderRepository.findByOrderId(orderId);
    return items.stream()
                .map(OrderItem::getPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
}

Agora vamos tentar repeti-lo em uma maneira funcional. Vamos envolver o orderId em um optional.

Esta é a aparência do código utilizando Optional:

public BigDecimal getOrderPrice(Long orderId) {
    return Optional.ofNullable(orderId)                            
            .map(orderRepository::findByOrderId)                   
            .flatMap(items -> {                                    
                BigDecimal sum = items.stream()
                        .map(OrderItem::getPrice)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                return Optional.of(sum);                           
            }).orElse(BigDecimal.ZERO);                            
}
  • Envolva o orderId em um Optional;
  • Encontre os items do pedido;
  • Use flatMap() para obter um Optional<BigDecimal>, nesse caso pode surgir o questionamento, de porque não utilizar um map(), porém o map() obteria um Optional<Optional<BigDecimal>>, e não é isso que queremos.
  • Precisamos colocar o resultado em um Optional para estar em conformidade com a assinatura do método;
  • Se o Optional não contém um valor, a soma é 0.

Dessa forma acredito que o Opcional torna o código até menos legível, que antes…

Mas sempre acreditei no princípio de que a legibilidade deve superar o estilo do código.

E aí que felizmente, Optional oferece um método stream() (desde Java 9) que permite simplificar o pipeline funcional dessa forma:

public BigDecimal getOrderPrice(Long orderId) {
    return Optional.ofNullable(orderId)
            .stream()
            .map(orderRepository::findByOrderId)
            .flatMap(Collection::stream)
            .map(OrderItem::getPrice)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
}

Agora vou explicar linha a linha do pipeline:

Optional.ofNullable(orderId)Optional<Long>
stream()Stream<Long>
map(orderRepository::findByOrderId)Stream<List<OrderLine>>
flatMap(Collection::stream)Stream<OrderLine>
map(OrderItem::getPrice)Stream<BigDecimal>
reduce(BigDecimal.ZERO, BigDecimal::add)BigDecimal

Código funcional não significa necessariamente código legível. Porém com essas mudanças, acredito que sejam as duas coisas.

Espero que passem a utilizar Optional.stream(), ou que melhorem a dinâmica do seu uso.

Obrigado.

🙂

Minha participação no @2devs podcast | Bem vindo 2021

Antes de tudo gostaria de dar as boas vindas ao ano de 2021, desejar a todos que tenham seus sonhos realizados e saúde.

Bom o ano começou muito legal, já teve o primeiro meetup este ano da comunidade SIMBORADEVS, fica aqui o convite para participar do nosso slack e ficar por dentro das ações.

E esse post é para comunicar que já está no ar a primeira parte da minha participação num dos melhores podcasts de desenvolvimento de software do Brasil, o @2devs, foi um bate papo muito interessante e divertido, tentei passar um pouco do meu conhecimento e atualizações do java.

Fica aqui meu agradecimento aos amigos Thiago Ramos e Rashid Calazans, que são os hosts desse podcast, muito grato em participar pela segunda vez. Recomendo que escutem todos os episódios pois vale muito a pena.

Fica aqui o link direto para o episódio e deixem seus comentários.

Forte abraço e espero que gostem!

E em breve mais novidades!

Java 15 – Sealed Classes and Interfaces

Photo by John-Mark Smith on Pexels.com

A novidade que tenho pra vocês me deixou realmente animado, adorei essa nova feature do Java 15, a linguagem está a ficar cada vez mais moderna e divertida!

“Sealed Classes” ou “classes seladas” é o nome da nova feature que veio através da JEP 360, e tem como fundamento definir um design limpo onde você pode restringir a estensão ou implementação de uma classe base.

Como utilizar?

Usando duas novas keywords: sealed e permits.

Exemplos? Claro!

Vamos dizer que esteja criando uma classe base e deseja que ela seja estendida por outros desenvolvedores para reutilizar o código.

Digamos que você desenvolveu uma classe base chamada Animal e deseja permitir que outro desenvolvedor crie uma classe Cat que pode estender essa classe base.

Então, você tornou a classe base Animal pública e qualquer outra classe poderia estendê-la.

E aí veio um desenvolvedor que criou uma classe Eagle e estendeu a sua classe base Animal só porque ele queria reutilizar um dos métodos da classe Animal, digamos um método chamado walk().

Mas você não quer permitir isto! O que você quer é que apenas certas subclasses estendam sua classe base Animal.

Você não pode marcar sua classe como final porque isso restringirá qualquer classe a estendê-la.

Você não pode colocar em um package privado, porque todas as classes dentro do pacote serão capazes de criar uma subclasse de Animal.

Java não permitiu tal recurso por muito tempo e esse recurso surgiu agora no Java 15!

Vamos codar!

package dev.ivanmarreta;
 
public sealed class Animal permits Cat, Dog {

   void walk();
}
package dev.ivanmarreta;
 
public final class Cat extends Animal {
 
}
package dev.ivanmarreta;
 
public final class Dog extends Animal {
 
}

Se a classe Animal permitir que apenas Cat e Dog a estendam, um erro do compilador será lançado quando uma classe Eagle tentar estendê-la.

package dev.ivanmarreta;
 
public class Eagle extends Animal {
 
}
Eagle.java:3:8 error: class is not allowed to extend sealed class: Animal

Modificadores de classe – final vs sealed

final: não pode ser estendido mais.

sealed: só pode ser estendido por suas subclasses permitidas. Dessa forma, podemos restringir ainda mais a subclasse.

non-sealed: pode ser estendido por subclasses desconhecidas.

Quando utilizar?

Não permitirá que nenhuma outra classe o estenda?

Então deve ser final.

Permitirá que apenas certas subclasses o estendam?

Então deve ser sealed.

Permitirá que qualquer número de classes o estenda?

Então deve ser non-sealed.

Mudanças também em java.lang.Class

A Reflections API também foi alterada para oferecer suporte às sealed classes.

Foram adicionados os métodos isSealed() que retorna um valor boolean e permittedSubclasses() que retorna um array do tipo ClassDesc das subclasses que são permitidas a extensão ou implementação.

Exemplo:

package dev.ivanmarreta;

public class AnimalSealedClassExample {
    public static void main(String[] args) {
        System.out.println("Animal is sealed: " + Animal.class.isSealed());
        System.out.println("Animal permittedSubclasses:");
        for (ClassDesc clazzDesc : Animal.class.permittedSubclasses()) {
            System.out.println(clazzDesc.toString());
        }
    }
}

Output:

Animal is sealed: true
Animal permittedSubclasses:
ClassDesc[Cat]
ClassDesc[Dog]

Conclusão

Java está a ficar cada vez mais dinâmico e moderno, e isso também se deve a vários recursos de linguagens JVM mais modernas, como Scala ou Kotlin.

Utilizo diariamente a JDK 11, mas estou animado pra poder usar todas as novas features das JDKs mais novas o mais breve possível. Enquanto isso a JDK 16 já anda em desenvolvimento. õ/

Espero ter animado vocês também!

Obrigado e até mais! 🙂

SynchronizedMap x ConcurrentHashMap – micro benchmark

Photo by Lukas on Pexels.com

Conforme dito previamente, agora vamos criar uma classe para fazer um micro benchmark para exemplificar o que foi explicado sobre SynchronizedMap e ConcurrentHashMap nos posts anteriores.

Quais foram os passos que segui?

1. Criei a classe TestBenchmark;

2. Passei uma implementação diferente como argumento para o método de teste Collections.synchronizedMap(new HashMap()) e ConcurrentHashMap();

3. Criei uma lógica para adicionar(PUT) e recuperar(GET) 600 mil entradas do Map;

4. Calculei a média de tempo em milissegundos para os processamentos;

5. Utilizei um ExecutorService simples para executar 5 threads em paralelo e para cada uma delas repetiremos por 5 vezes as iterações para capturar uma média de tempo.

6. Aproveitei e adicionei o Hashtable, que é a estrutura de dados base, ao teste.

Teste

package dev.ivanmarreta;

import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class TestBenchmark {

    private static final int SIX_HUNDRED_THOUSAND = 600_000;
    private static final int THREAD_POOL_SIZE = 5;
    private static final int TIMES_TO_TEST = 5;
    
    public static void main(String[] args) throws InterruptedException {
 
        // Hashtable
        microBenchmarkTest(new Hashtable<String, Integer>());
 
        // Collections.synchronizedMap
        microBenchmarkTest(Collections.synchronizedMap(new HashMap<String, Integer>()));
 
        // ConcurrentHashMap
        microBenchmarkTest(new ConcurrentHashMap<String, Integer>());
 
    }
 
    public static void microBenchmarkTest(final Map<String, Integer> map) throws InterruptedException {
        
        System.out.println("Iniciando o teste de performance para: " + map.getClass());
        
        long averageTime = 0;
        for (int i = 0; i < TIMES_TO_TEST; i++) {
 
            long startTime = System.nanoTime();
            ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
 
            for (int j = 0; j < THREAD_POOL_SIZE; j++) {
                executor.execute(() -> {
                    for (int key = 0; key < SIX_HUNDRED_THOUSAND; key++) {
                        Integer randomValue = (int) Math.ceil(Math.random() * SIX_HUNDRED_THOUSAND);

                        // GET
                        Integer value = map.get(String.valueOf(randomValue));

                        // PUT 
                        map.put(String.valueOf(randomValue), randomValue);
                    }
                });
            }

            executor.shutdown();
            executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.MINUTES);
 
            long totalTime = (System.nanoTime() - startTime) / 1000000L;
            averageTime += totalTime;
            System.out.println("600 mil registros foram adicionados(PUT)/recuperados(GET) em " + totalTime + " ms");
        }
        System.out.println("Média de tempo de execução para a implementação " + map.getClass() + ": " + averageTime / TIMES_TO_TEST + " ms \n");
    }
    
}

Nas linhas 56 e 57, chamo shutdown() para o executor service não esperar mais tasks e awaitTermination() para bloquear as threads até que as tasks finalizem.

Resultado do teste:

Máquina de teste: Macbook Pro 2.6GHz quad-core Intel Core i7 16GB

Conclusão:

Conforme conversamos sobre SynchronizedMap e ConcurrentHashMap, os resultados obtidos refletem a forma de acesso aos objetos simultaneamente.

Portanto a estrátegia de lock do objeto ou de segmentos do objeto é bastante relevante, e parece-me ficar bem exemplificada.

Obrigado e até breve. 🙂

HashMap pode ser sincronizado em Java?

HashMap é uma estrutura de dados muito poderosa em Java e nós a usamos todos os dias e em quase todos os aplicativos.

Como devem saber HashMap é uma classe de coleção não sincronizada, caso não saibam, a novidade é saber que SIM, HashMap pode ser sincronizado.

Neste tutorial, vamos tentar entender “Por Quê” e “Como” podemos sincronizar o Hashmap!

Por quê?

O Map é uma estrutura de dados que armazena elementos, formado por uma combinação de uma chave de identificação exclusiva e um valor atribuído mapeado. Se você tiver uma aplicação altamente concorrente no qual deseja modificar ou ler o valor da chave em diferentes threads, digo-te logo que o ideal usar a implementação ConcurrentHashMap. O melhor exemplo é o Producer/Consumer, que faz com leitura/gravação concorrente.

Então, o que significa o Map ser thread-safe?

Significa que se vários encadeamentos acessam um HashMap em concorrência e pelo menos um dos encadeamentos modifica o Map estruturalmente, ele deve ser sincronizado externamente para evitar uma visualização inconsistente do conteúdo. Tão isso quanto isso.

Como?

Vamos lá! Existem duas maneiras de sincronizar o HashMap:
  1. Usar ConcurrentHashMap (Que já aprendemos a utilizar no post anterior)
  2. Java Collections – método synchronizedMap()
//synchronizedMap
Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());

SynchronizedHashMap

  • Sincronização a nível do objeto.
  • Cada operação de leitura/gravação precisa adquirir lock.
  • Fazer lock a coleção inteira é uma sobrecarga de desempenho.
  • Essencialmente dá acesso a apenas uma thread para todo o Map e bloqueia todos as outras threads.
  • Pode causar contenção.
  • SynchronizedHashMap retorna um Iterator, que falha rapidamente se utilizado em concorrêcia.

No próximo post pretendo então exemplificar, explicar e fazer um benchmark entre SynchronizedHashMap e ConcurrentHashMap.

Obrigado e até lá. 🙂

ConcurrentHashMap

ConcurrentHashMap é uma melhoria do HashMap, pois sabemos que, ao lidar com Threads, o HashMap não é uma boa escolha porque o em termos de desempenho deixa muito a desejar.

A classe ConcurrentHashMap é thread-safe, ou seja, várias threads podem operar em um único objeto sem complicações. Ao mesmo tempo, qualquer número de encadeamentos é aplicável para uma operação de leitura sem bloquear o objeto ConcurrentHashMap que não existe no HashMap.

A estrutura de dados que o ConcurrentHashMap utiliza é a HashTable.

No ConcurrentHashMap, o objeto é dividido em vários segmentos de acordo com o concurrency-level, e como default o concurrency-level do ConcurrentHashMap é 16.


No ConcurrentHashMap, a qualquer momento mais que uma thread pode executar operação de recuperação de valor, só que para fazer update no objeto, a thread deve bloquear esse segmento específico que a thread deseja operar. Este tipo de mecanismo de bloqueio é conhecido como Segment locking or bucket locking. Consequentemente, 16 operações de update podem ser realizadas por threads.

A inserção de objetos nulos não é possível em ConcurrentHashMap como chave ou valor. Diferentemente da implementação mais utilizada, a HashMap.

public class ConcurrentHashMap<K,​V> extends AbstractMap<K,​V> implements ConcurrentMap<K,​V>, Serializable

Onde K é a chave do objeto e V é o valor do objeto.

Inicializando um ConcurrentHashMap

Podemos utilizar um dos 5 contrutores para isso:

//Cria um novo map vazio com valores default: initial capacity (16), load factor (0.75) e concurrencyLevel (16).
new ConcurrentHashMap<>();

//Cria um novo map vazio com: initial capacity que foi definida, e com default load factor (0.75) e concurrencyLevel (16).
new ConcurrentHashMap<>(int initialCapacity);

//Cria um novo map vazio com: initial capacity e load factor que foram definidos, e com concurrencyLevel default (16).
new ConcurrentHashMap<>(int initialCapacity, float loadFactor);

//Cria um novo map vazio com: initial capacity, load factor e concurrencyLevel que foram definidos.
new ConcurrentHashMap<>(int initialCapacity, float loadFactor, int concurrencyLevel);

//Cria um novo map com os mesmos atributos do map passado como parâmetro.
new ConcurrentHashMap<>(Map m);

Por quê?

Quando precisar de alta simultaneidade em seu projeto.

É thread-safe sem sincronizar todo o mapa. (apenas os segmentos, lembram?)

As leituras podem acontecer muito rapidamente enquanto a gravação é feita com um bloqueio.

Não há bloqueio no nível do objeto.

O bloqueio tem uma granularidade muito mais fina no nível do depósito de hashmap.

Não lança uma ConcurrentModificationException se uma thread tenta modificá-lo enquanto outra está iterando sobre ele.

ConcurrentHashMap usa uma infinidade de bloqueios.


🙂

Diferença entre os patterns: Observer x Publish Subscribe

Adoro conversar, se tem uma coisa que gosto de fazer é falar e ouvir… e em uma dessas conversas recentes surgiu a discussão sobre como explicar mais rapidamente as diferenças entre os padrões de Observables e PubSub.

Resolvi então traduzir o fio da conversa em um post aqui, não vou entrar em todos os detalhes, mas apenas tentar ser mais lúcido e prático nas definições.

Então vamos lá!

Observer

Image for post

O padrão Observer é um padrão de design comportamental em que um objeto, chamado de Subject, permite definir uma ou mais inscrições dos chamados Observers, e os notifica automaticamente sobre quaisquer mudanças de estado, geralmente chamando um de seus métodos.

Vamos a um exemplo do mundo real:

Você trabalha como vendedor em uma loja de calçados, porém a loja está sempre cheia e todos os dias anota diversos contatos por itens que estão em falta em um bloquinho. No dia seguinte ao verificar que um dos calçados já encontra-se em estoque e claro, para não perder sua comissão de venda, resolve ligar para todos todos os contatos do dia específico de procura daquele item que mudou de status.

Image for post
Ilustração do exemplo do vendedor.

Agora o exemplo como deve ser:

Image for post
Observer Design Pattern

Simples não é? 🙂

Publish Subscriber

Image for post

É um padrão para troca de menssagens onde quem as envia são chamados de Publishers, porém eles não enviam as mensagens diretamente para quem os recebe, os Subscribers.

Os publishers não tem conhecimento dos subscribers, e vice versa.. mas então como eles se comunicam? Eles expressam interesses(intents) de envios, e interesses em recepções.

Mas se eles não se conhecem, como é feita a comunicação? Há outro componente, o Message Broker, e ele sim é conhecido tanto pelo publisher quanto pelo subscriber. O publish enviará a mensagem ao message broker e ele por sua vez filtrará e transmitirá a mensagem ao(s) subscriber(s) correto(s).

Image for post
PubSub Pattern

Existem vários message brokers disponíveis, no exemplo ilustrativo a área em cinza seria a sua representação, lembrando que são bem mais complexos que isso, é claro.

Obrigado

O post foi curto e rápido como previsto, vejo vocês em breve.

QuarkusIO — Supersonic Subatomic Java

Hoje vamos iniciar um tutorial para aprender um pouco como utilizar esse framework java nativo da RedHat, que espera se tornar a plataforma líder em ambientes Serverless e Kubernetes, além de oferecer aos desenvolvedores um modelo unificado de programação reativa e imperativa.

O QuarkusIO aproveita uma série de libs comuns para os desenvolvedores Java, e algumas delas estão muito no hype como a Eclipse MicroProfile, Kafka e Vert.x. A injeção de dependência na framework é baseada no CDI, permitindo que os desenvolvedores usem JPA/Hibernate, JAX-RS/RESTEasy e etc.

Baseado nos melhores padrões, baixo consumo de processamento e tempo de inicialização incrivelmente rápido, o Quarkus permite o uso do Java em ambientes serverless, suportando um ambiente responsivo e escalável.

1 — Nossa primeira aplicação

A forma mais fácil e rápida de criar nossa primeira aplicação Quarkus seria via linha de comando utilizando o Maven.

mvn io.quarkus:quarkus-maven-plugin:0.13.1:create \
 -DprojectGroupId=com.ivanmarreta.quarkus \
 -DprojectArtifactId=quarkus-project \
 -DclassName=”com.ivanmarreta.quarkus.SampleResource” \
 -Dpath=”/sample”

Este comando irá gerar um esqueleto do projeto, com um resource chamado SampleResource e com um endpoint /sample exposto, configurações e Dockerfiles.
Sendo um projeto maven, agora basta fazer o importe para sua IDE favorita, a estrutura será similar a esta:

Image for post
Eu utilizo o Intellij IDEA

Na nossa classe SampleResource temos o seguinte código:

@Path(“/sample”)
public class SampleResource {
  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String hello() {
    return “hello”;
  }
}

E para executar nossa aplicação basta executar no terminal o comando:

./mvnw compile quarkus:dev

Na primeira vez o maven irá efetuar o download dos artefatos necessários e logo a aplicação estará up:

Image for post
Mac OSX terminal screenshot

Pronto! Sua primeira aplicação está UP!

Para acessar o endpoint exposto podes testar via browser ou, como gosto de testar, via terminal:

curl -w “\n” http://localhost:8080/sample

Obtemos o resultado esperado: a string “hello”. (clap clap clap)

2 — Hot Reload

Image for post

No modo de desenvolvimento (./mvn compile quarkus:dev), o Quarkus fornece um recurso de Hot Reload. Com isso, as alterações feitas nos arquivos Java ou nos arquivos de configuração serão compiladas automaticamente assim que o navegador for atualizado ou, uma request for efetuada. O recurso mais impressionante aqui é que não precisamos salvar nossos arquivos. Portanto, dependendo da sua preferência, podes achar isso bom ou ruim.

Agora vamos observar como o Hot Reload age!


Na nossa ide criaremos uma classe chamada SampleService:

@ApplicationScoped
public class SampleService {
  public String hello(String name) {
    return String.format(“Hello %s. This is your first QuarkusIO
                          sample!”, name);
  }
}

E agora vamos injetar essa classe utilizando CDI no nosso resource, criando também um novo endpoint exposto:

@Path(“/sample”)
public class SampleResource {
  @Inject
  SampleService service;
  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String hello() {
    return “hello”;
  }
  @GET
  @Path(“/{name}”)
  public String hello(@PathParam(“name”) String name) {
    return service.hello(name);
  }
}

E então executamos nossa requisição ao nosso novo endpoint:

curl -w “\n” http://localhost:8080/sample/Ivan

Hello Ivan. This is your first QuarkusIO sample!

Conforme mencionei, podemos também alterar arquivos de configuração.

Vamos adicionar ao arquivo application.properties a linha:

hello=Hello %s. This is your first QuarkusIO sample!

E em nossa classe de serviço SampleService, vamos injetar a propriedade utilizando a anotação @ConfigProperty do Eclipse Microprofile.

Nossa classe ficará simples como isso:

@ApplicationScoped
public class SampleService {
  @ConfigProperty(name = “hello”)
  private String hello;
  public String hello(String name) {
    return String.format(hello, name);
  }
}

Basta repetir a request e verás que o resultado será o mesmo e, podes até brincar alterando a string adicionada ao application.properties.


3 — Json no response dos Rest Services

Agora vamos tornar as coisas um pouco mais divertidas e aumentar um pouco a “dificuldade”.

Vamos adicionar código para retornar uma lista de objetos do tipo Sample.

public class Sample {
  private int order;
  private String name;
  //…
}

Na nossa SampleService, adicionamos o método:

public List<Sample> list(){
   Sample sample1 = new Sample(1, “Nossa primeira aplicação”);
   Sample sample2 = new Sample(1, “Hot Reload”);
   Sample sample3 = new Sample(1, “Json no response dos Rest ...”);
   return Arrays.asList(sample1, sample2, sample3);
}

E nossa classe SampleResource, agora completa, ficará assim:

package com.ivanmarreta;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path(“/sample”)
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class SampleResource {
  @Inject
  SampleService service;
  @GET
  public String hello() {
     return “hello”;
  }
  @GET
  @Path(“/{name}”)
  public String hello(@PathParam(“name”) String name) {
     return service.hello(name);
  }
  @GET
  @Path(“/list”)
  public Response list() {
     return Response.ok(service.list()).build();
  }
}

Ao executar a request:

curl -w “\n” http://localhost:8080/sample/list

Iremos receber a seguinte resposta:

Could not find MessageBodyWriter for response object of type: java.util.Arrays$ArrayList of media type: application/json

Mas não se preocupe, está correto. O que tem de errado então? Precisamos adicionar no pom.xml a seguinte dependencia:

<dependency>
 <groupId>io.quarkus</groupId>
 <artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>

JSON-B não é a única forma de desserialização, podes também utilizar o Jackson, depende da sua preferência.

Bom, após adicionar a dependência, enfim, devemos reiniciar a app no nosso terminal, pois o maven precisará adicionar bibliotecas ao projeto. Desta vez verá que em questão de milisegundos a aplicação é started. õ/

Agora podemos testar nossa request e verificar o resultado:

curl -w “\n” http://localhost:8080/sample/list

[{“name”:”Nossa primeira aplicação”,”order”:1},{“name”:”Hot Reload”,”order”:1},{“name”:”Json Rest Services”,”order”:1}]

Nossa, muito complicado não é? 🙂


4 — Packaging da nossa aplicação

Via terminal, executamos:

./mvnw package

Serão gerados 2 artefactos jar dentro da pasta target:

Jar executável com todas as dependencias:
ivanmarreta-quarkus-project-1.0-SNAPSHOT-runner.jar

Jar contendo classes e resources:
ivanmarreta-quarkus-project-1.0-SNAPSHOT.jar

Agora para executar sua aplicação basta rodar o jar:

java -jar target/ivanmarreta-quarkus-project-1.0-SNAPSHOT-runner.jar

😉

5 — Conclusão

Demonstramos aqui um pouco do Quarkus, e verificamos que é um ótimo framework para levar o Java de maneira mais eficaz à nuvem. Por exemplo, agora é possível imaginar Java no AWS Lambda ou outra plataforma Serverless. Além disso, o QuarkusIO é baseado em padrões como JPA e JAX/RS. Portanto, para desenvolvedores Java experientes, não existe uma barreira de curva de aprendizado.

No próximo post sobre o QuarkusIO iremos explorar recursos mais avançados! õ/

Espero ler o que acham sobre o framework nos comentários e debater sobre o assunto.

Obrigado 🙂

Java 8 — Just a little “explanation” 2

In the first part of this sequence you saw a new method on our List, the forEach. But, until Java 7 this method didn’t existed… how did it happen?

We know that if this method would be declared in an interface, we could have a big problem! What?

Everyone who implemented the interface would need to implement it.
Chaos would be installed, work would be immense, the Java community would go insane.
An interface like List would destroy libraries used by most of us, such as Hibernate.

Would be welcome to “NoSuchMethodErrors” Hell

BUT … we get a new feature in Java 8!

Default Methods

With this feature we can add a method to an interface and ensure that all implementations have it implemented. 🙂

For example, the forEach method we use is declared inside java.lang.Iterable, which is the parent of Collection, in turn List’s parent.

If we look at the method in the interface we’ll find:

default void forEach(Consumer <? Super T> action) {
   Objects.requireNonNull (action);
   for (T t: this) {
      action.accept (t);
   }
}

Yes, I think you never expected that, method with code inside interfaces..

As ArrayList implements List, which is (indirect) daughter of Iterable,
The ArrayList has this method, whether it wants to or not. It is for this reason that we can do it:

books.forEach(b -> System.out.println(b.getName()));

This reading serves as an explanation for the first part, which used this new feature.

Bye bye, see you!

References:
https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html