3 coisas que você não sabia sobre os métodos Java

Marcio Endo
May 14, 2023

Para desenvolver aplicações em Java você cria e usa métodos o tempo inteiro.

Dependendo do framework que você usa, os endpoints de uma API são codados em um método, por exemplo.

Ainda que você use métodos diariamente há algumas coisas sobre os métodos Java que talvez você não sabia.

Neste artigo eu mostrarei 3 curiosidades sobre os métodos Java.

Antes de mais nada, siga-me no Twitter!

Bora lá.

1. Anotações e modificadores podem vir em qualquer ordem

Eu falei sobre este mesmo assunto no artigo sobre anotações do Java.

Talvez seja surpreendente o fato do seguinte método compilar sem erros:

public @Banda({
    @Integrante("Iommi"),
    @Integrante("Butler"),
    @Integrante("Osbourne"),
    @Integrante("Ward")
}) final String naoFacaIsto() {
  return "Não escreva códigos assim...";
}

No código acima temos uma anotação entre os modificadores public e final do método. Como dito, o método compila sem erros.

Eu sempre pensei que anotações deveriam ficar em cima do método:

@Pegue("/a")
public final String methodA() {
  return "Prefira esta forma";
}

Veja como a anotação fica imediatamente acima da declaração do método.

Na verdade, para o compilador Java, a anotação é apenas mais um modificar do método. Em outras palavras, anotações e modificadores podem aparecer em qualquer ordem.

O método abaixo compila sem erros:

public @Pegue("/b") final String methodB() {
  return "Compila, mas evite!";
}

O método seguinte também compila sem erros:

public final @Pegue("/c") String methodC() {
  return "Compila, mas evite também!";
}

As duas últimas formas não são idiomáticas, no entanto. Assim, evite-as.

2. Métodos que retornam arrays

Abaixo, temos um exemplo de um método que retorna um array:

public String[] exemplo01() {
  return new String[] {
      "Vou-me", "embora", "pra", "Pasárgada"
  };
}

Nada demais, correto?

Mas o método abaixo é equivalente ao acima:

public String exemplo02()[] {
  return new String[] {
      "Vou-me", "embora", "pra", "Pasárgada"
  };
}

Notou a diferença? Os colchetes aparecem após os parênteses! O método compila sem erros.

E se for um array com mais de uma dimensão? Sem problemas:

public String[] exemplo03()[] {
  return new String[][] {
      {"Vou-me", "embora", "pra", "Pasárgada"},
      {"No", "meio", "do", "caminho"}
  };
}

Ou seja, é possível colocar parte dos colchetes junto ao tipo de retorno e outra parte depois dos parênteses.

A coisa fica ainda mais peculiar se adicionarmos anotações de tipo.

O método seguinte compila sem erros:

@Target(ElementType.TYPE_USE)
@interface Eita {}

@Target(ElementType.TYPE_USE)
@interface Vixe {}

public @Eita String @Eita [] exemplo04() @Eita @Vixe [] {
  return new String[][] {
      {"Vou-me", "embora", "pra", "Pasárgada"},
      {"No", "meio", "do", "caminho"}
  };
}

Assim como o seguinte método:

public @Eita String @Eita [] exemplo05() @Eita @Vixe [] {
  return new @Eita String @Vixe [] @Eita @Vixe [] {
      {"Vou-me", "embora", "pra", "Pasárgada"},
      {"No", "meio", "do", "caminho"}
  };
}

E por falar em anotações de tipo, vamos ao último item de hoje: o parâmetro receptor.

3. O parâmetro receptor (receiver parameter)

Desde o Java 8, você pode declarar um parâmetro opcional e puramente sintático em todos os métodos de instância. O exemplo seguinte é uma implementação válida de método toString:

public class Receptor {
  @Override
  public String toString(Receptor this) {
    return "Um exemplo de parâmetro receptor";
  }
}

Note que o nome do parâmetro é a palavra reservada this. Esse parâmetro é formalmente chamado de parâmetro receptor.

Para demonstrar que o método toString acima é válido, escrevemos o seguinte código:

public static void main(String[] args) {
  var obj = new Receptor();

  System.out.println(obj.toString());
}

O programa acima imprime:

Um exemplo de parâmetro receptor

Isto é, a chamada ao método toString sem parâmetros executa aquele método lá de cima que declara um parâmetro.

Confuso?

Se quiser saber mais sobre o parâmetro receptor, leia este artigo (em inglês) que escrevi.

Por agora, saiba que ele uma construção puramente sintática e não tem significado semântico. Em outras palavras, ele não é um parâmetro formal de fato.

Qual o uso?

Então para que serve o parâmetro receptor?

O único intuito do parâmetro receptor é permitir anotar o tipo no qual o método está sendo chamado:

@Target(ElementType.TYPE_USE)
public @interface Started {}

@Target(ElementType.TYPE_USE)
public @interface Configured {}

public class Service {
  public void configure() {}

  public void start(@Configured Service this) {}

  public void stop(@Started Service this) {}
}

No exemplo acima, uma ferramenta de análise estática hipotética poderia verificar, em tempo de compilação:

Novamente, eu explico o parâmetro receptor em mais detalhes neste artigo.

Está tudo na especificação da linguagem

Tudo isto está definido na Especificação da Linguagem Java ou JLS no inglês.

As declarações de método são definidas na seção 8.4:

MethodDeclaration:
  {MethodModifier} MethodHeader MethodBody

MethodHeader:
  Result MethodDeclarator [Throws]
  TypeParameters {Annotation} Result MethodDeclarator [Throws]
  
MethodDeclarator:
  Identifier ( [ReceiverParameter ,] [FormalParameterList] ) [Dims]
  
ReceiverParameter:
  {Annotation} UnannType [Identifier .] this

Bora analisar cada um dos três itens apresentados neste artigo.

Modificadores

A produção MethodDeclaration permite zero ou mais MethodModifier:

MethodDeclaration:
  {MethodModifier} MethodHeader MethodBody

A última, por sua vez, é definida na seção 8.4.3:

MethodModifier:
  (one of)
  Annotation public protected private
  abstract static final synchronized native strictfp

Note que Annotation faz parte da definição dos modificadores.

Assim, em uma declaração de método, anotações e modificadores podem aparecer em qualquer ordem.

Métodos que retornam arrays

Veja a produção MethodDeclarator:

MethodDeclarator:
  Identifier ( [ReceiverParameter ,] [FormalParameterList] ) [Dims]

Ela indica que, após a lista de parâmetros do método, opcionalmente podem aparecer Dims.

E Dims nada mais são do que os colchetes dos arrays:

Dims:
  {Annotation} [ ] {{Annotation} [ ]}

Parâmetro receptor

Vejamos novamente a produção MethodDeclarator:

MethodDeclarator:
  Identifier ( [ReceiverParameter ,] [FormalParameterList] ) [Dims]

Ela indicar que, antes da lista de parâmetros formais, opcionalmente pode aparecer um ReceiverParameter.

Este último é dado por:

ReceiverParameter:
  {Annotation} UnannType [Identifier .] this

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.