Casamento de padrões e escopo de fluxo do Java

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 umArtigo
; 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.