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:
@Override
public 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:
@Override
public 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:
@Override
public 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ção
System.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.