Casamento de padrões e escopo de fluxo do Java

Marcio Endo
May 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:

@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:

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:

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.