Casamento de padrões e escopo de fluxo do Java

Marcio EndoMarcio EndoMay 28, 2023

A JEP 394, entregue na versão 16 do Java, trouxe de maneira definitiva o casamento de padrões (ou pattern matching em inglês) para a linguagem Java.

Para ser mais exato, essa JEP trouxe um tipo específico de casamento de padrões, o padrão de tipo, para uma instrução específica, o instanceof.

Junto com o casamento de padrões, a JEP trouxe outro conceito novo para a linguagem: o escopo de fluxo (ou flow scoping em inglês).

Neste artigo discutiremos o último um pouco mais.

Bora lá.

Casamento de padrões para instanceof (breve introdução)

Se você já implementou um método equals, então já escreveu construções do tipo "instanceof + cast".

Por exemplo, suponha uma classe Artigo representando um artigo de um blog.

Uma possível implementação para o método equals pode ser:

@Overridepublic boolean equals(Object obj) {  if (obj instanceof Artigo) {    Artigo that = (Artigo) obj;    return Objects.equals(titulo, that.titulo)        && Objects.equals(data, that.data);  } else {    return false;  }}

Sim, há outras maneiras para implementar o método, tenham um pouco de paciência comigo. O importante é notar:

  • o uso do instanceof para verificar se o objeto é de fato um Artigo; e

  • o cast para Artigo logo em seguida a uma resposta positiva.

Desde o Java 16 (sem necessitar de --enable-preview) é possível fazer:

@Overridepublic boolean equals(Object obj) {  if (obj instanceof Artigo that) {    return Objects.equals(titulo, that.titulo)        && Objects.equals(data, that.data);  } else {    return false;  }}

Note que a expressão instanceof introduz a variável local 'that'. O cast ocorre implicitamente.

O if, no entanto, é desnecessário. Podemos fazer simplesmente:

@Overridepublic boolean equals(Object obj) {  return obj instanceof Artigo that      && Objects.equals(titulo, that.titulo)      && Objects.equals(data, that.data);}

Note que a variável that é acessível no lado direito do operador &&.

Isto é, a variável 'that' está no escopo daquele operador.

Esse é um exemplo do escopo de fluxo introduzido pelo casamento de padrões para instanceof.

O escopo "tradicional"

Antes de entrar em mais detalhes no escopo de fluxo, vejamos o escopo "tradicional".

Por exemplo, vejamos a instrução for seguinte:

var result = new ArrayList<Future<Foo>>();for (var element : elements) {  var future = submit(element);  result.add(future);}

A instrução introduz duas variáveis locais:

  • element; e

  • future.

As variáveis só podem ser acessadas dentro do laço for. Em outras palavras, o escopo das variáveis é restrito à instrução for.

Portanto, o seguinte código não compila:

var result = new ArrayList<Future<Foo>>();for (var element : elements) {  var future = submit(element);  result.add(future);}// erro de compilaçãoSystem.out.println(element);

Este escopo é o que estamos acostumados (até agora) como programadores Java. É de certa forma natural perceber que o escopo fica restrito dentro dos marcadores { e }.

O escopo de fluxo

O escopo de fluxo difere do anterior de algumas maneiras importantes. Não são os marcadores que delimitam o escopo. Ao invés disso, é o fluxo de execução do programa que delimita o escopo.

Vejamos o exemplo seguinte:

static void printIfString(Object o) {  if (!(o instanceof String s)) {    return;  }  System.out.println(s);}public static void main(String[] args) {  printIfString("Olá");  printIfString(LocalDate.now());  printIfString("Mundo!");}

Quando executado o programa imprime:

Olá
Mundo!

Vamos focar no método printIfString.

Como o nome do método diz, ele imprime o objeto somente se este for uma String.

O interessante é notar que a variável local s pode ser acessada lá no final do método:

if (!(o instanceof String s)) {  return;}// 's' está no escopo!System.out.println(s);

Note que, se o objeto 'o' não for uma String, então saímos do método via um return.

Em outras palavras, se o fluxo de execução passa da instrução if, então com certeza o objeto é uma instância de String.

Confuso? Vejamos, então, uma variação do exemplo anterior:

static void printIfString(Object o) {  if (!(o instanceof String s)) {    return;  } else {    System.out.println(s);  }}

O laço else é executado se e somente se o objeto é uma instância de String.

Nesse caso, a variável s pode ser acessada no laço else.

Conclusão

O casamento de padrões trouxe um novo tipo de escopo para a linguagem Java: o escopo de fluxo.

No artigo vimos que esse é definido pelo fluxo de execução do programa.

É, portanto, diferente do escopo "tradicional" em que o escopo fica restrito a instrução em si.

Até o próximo artigo

Por hoje é isto. Espero que tenha gostado.

O código para todos os exemplos pode ser encontrado no GitHub.

Se tiver comentários, dúvidas ou correções sobre este post, por favor, envie um email.

Siga-me no Twitter.