CodeGym /Blogue Java /Random-PT /Classes internas em um método local
John Squirrels
Nível 41
San Francisco

Classes internas em um método local

Publicado no grupo Random-PT
Oi! Vamos falar sobre outro tipo de classes aninhadas. Estou falando de classes locais (classes internas de método local). Antes de mergulhar, devemos primeiro lembrar seu lugar na estrutura de classes aninhadas. Classes internas em um método local - 2Em nosso diagrama, podemos ver que as classes locais são uma subespécie de classes internas, sobre as quais falamos em detalhes em materiais anteriores . No entanto, as classes locais têm vários recursos e diferenças importantes em relação às classes internas comuns. O principal está em sua declaração: Uma classe local é declarada apenas em um bloco de código. Na maioria das vezes, essa declaração está dentro de algum método da classe externa. Por exemplo, pode ser assim:

public class PhoneNumberValidator {

   public void validatePhoneNumber(String number) {

        class PhoneNumber {

           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }

           public String getPhoneNumber() {
               return phoneNumber;
           }

           public void setPhoneNumber(String phoneNumber) {
               this.phoneNumber = phoneNumber;
           }
       }

       // ...number validation code
   }
}
IMPORTANTE!Se você tiver o Java 7 instalado, esse código não será compilado quando colado no IDEA. Falaremos sobre as razões para isso no final da lição. Resumindo, como as classes locais funcionam é altamente dependente da versão do idioma. Se esse código não for compilado para você, você pode alternar a versão do idioma no IDEA para Java 8 ou adicionar a palavra finalao parâmetro do método para que fique assim: validatePhoneNumber(final String number). Depois disso, tudo funcionará. Este é um pequeno programa que valida números de telefone. Seu validatePhoneNumber()método recebe uma string como entrada e determina se é um número de telefone. E dentro desse método, declaramos nossa PhoneNumberclasse local. Você pode razoavelmente perguntar por quê. Por que exatamente declararíamos uma classe dentro de um método? Por que não usar uma classe interna comum? Verdade, poderíamos ter feito oPhoneNumberclasse uma classe interna. Mas a solução final depende da estrutura e propósito do seu programa. Vamos relembrar nosso exemplo de uma lição sobre classes internas:

public class Bicycle {

   private String model;
   private int maxWeight;

   public Bicycle(String model, int maxWeight) {
       this.model = model;
       this.maxWeight = maxWeight;
   }
  
   public void start() {
       System.out.println("Let's go!");
   }

   public class HandleBar {

       public void right() {
           System.out.println("Steer right!");
       }

       public void left() {

           System.out.println("Steer left!");
       }
   }
}
Nele, fizemos HandleBaruma aula interna da moto. Qual é a diferença? Em primeiro lugar, a maneira como a classe é usada é diferente. A HandleBarclasse no segundo exemplo é uma entidade mais complexa do que a PhoneNumberclasse no primeiro exemplo. Primeiro, tem métodos HandleBarpublic righte (estes não são setters/getters). leftEm segundo lugar, é impossível prever com antecedência onde podemos precisar dele e de sua Bicycleclasse externa. Pode haver dezenas de locais e métodos diferentes, mesmo em um único programa. Mas com a PhoneNumberturma tudo fica muito mais simples. Nosso programa é muito simples. Tem apenas um propósito: verificar se um número é um número de telefone válido. Na maioria dos casos, nossoPhoneNumberValidatornem será um programa autônomo, mas sim uma parte da lógica de autorização para um programa maior. Por exemplo, vários sites geralmente solicitam um número de telefone quando os usuários se inscrevem. Se você inserir algum absurdo em vez de números, o site informará um erro: "Este não é um número de telefone!" Os desenvolvedores de tal site (ou melhor, seu mecanismo de autorização de usuário) podem incluir algo semelhante ao nossoPhoneNumberValidatorem seu código. Em outras palavras, temos uma classe externa com um método, que será usado em um local do programa e em nenhum outro. E se for usado, nada mudará nele: um método faz seu trabalho - e é isso. Nesse caso, como toda a lógica está reunida em um método, será muito mais conveniente e apropriado encapsular uma classe adicional ali. Ele não possui métodos próprios, exceto um getter e um setter. Na verdade, só precisamos dos dados do construtor. Não está envolvido em outros métodos. Portanto, não há razão para levar informações sobre ele fora do único método em que é usado. Também demos um exemplo em que uma classe local é declarada em um método, mas esta não é a única opção. Pode ser declarado simplesmente em um bloco de código:

public class PhoneNumberValidator {
  
   {
       class PhoneNumber {

           private String phoneNumber;

           public PhoneNumber(String phoneNumber) {
               this.phoneNumber = phoneNumber;
           }
       }

   }

   public void validatePhoneNumber(String phoneNumber) {

      
       // ...number validation code
   }
}
Ou até mesmo no forloop!

public class PhoneNumberValidator {
  

   public void validatePhoneNumber(String phoneNumber) {

       for (int i = 0; i < 10; i++) {

           class PhoneNumber {

               private String phoneNumber;

               public PhoneNumber(String phoneNumber) {
                   this.phoneNumber = phoneNumber;
               }
           }
          
           // ...some logic
       }

       // ...number validation code
   }
}
Mas esses casos são extremamente raros. Na maioria dos casos, a declaração acontecerá dentro do método. Então, descobrimos as declarações e também conversamos sobre a "filosofia" :) Quais recursos e diferenças adicionais as classes locais têm em comparação com as classes internas? Um objeto de uma classe local não pode ser criado fora do método ou bloco no qual é declarado. Imagine que precisamos de um generatePhoneNumber()método que gere um número de telefone aleatório e retorne um PhoneNumberobjeto. Em nossa situação atual, não podemos criar tal método em nossa classe validadora:

public class PhoneNumberValidator {

   public void validatePhoneNumber(String number) {

        class PhoneNumber {

           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }

           public String getPhoneNumber() {
               return phoneNumber;
           }

           public void setPhoneNumber(String phoneNumber) {
               this.phoneNumber = phoneNumber;
           }
       }

       // ...number validation code
   }

   // Error! The compiler does not recognize the PhoneNumber class
   public PhoneNumber generatePhoneNumber() {

   }

}
Outra característica importante das classes locais é a capacidade de acessar variáveis ​​locais e parâmetros de método. Caso você tenha esquecido, uma variável declarada dentro de um método é conhecida como variável "local". Ou seja, se criarmos uma String usCountryCodevariável local dentro do validatePhoneNumber()método por algum motivo, podemos acessá-la a partir da PhoneNumberclasse local. Porém, existem muitas sutilezas que dependem da versão da linguagem utilizada no programa. No início da aula, observamos que o código de um dos exemplos pode não compilar no Java 7, lembra? Agora vamos considerar as razões para isso :) No Java 7, uma classe local pode acessar uma variável local ou parâmetro de método somente se eles forem declarados como finalno método:

public void validatePhoneNumber(String number) {

   String usCountryCode = "+1";

   class PhoneNumber {

       private String phoneNumber;

       // Error! The method parameter must be declared as final!
       public PhoneNumber() {
           this.phoneNumber = number;
       }

       public void printUsCountryCode() {

           // Error! The local variable must be declared as final!
           System.out.println(usCountryCode);
       }

   }

   // ...number validation code
}
Aqui o compilador gera dois erros. E está tudo em ordem aqui:

public void validatePhoneNumber(final String number) {

   final String usCountryCode = "+1";

    class PhoneNumber {

       private String phoneNumber;

       
       public PhoneNumber() {
           this.phoneNumber = number;
       }

       public void printUsCountryCode() {

           System.out.println(usCountryCode);
       }

    }

   // ...number validation code
}
Agora você sabe por que o código do início da lição não compilava: no Java 7, uma classe local tem acesso apenas a finalparâmetros de método e finalvariáveis ​​locais. No Java 8, o comportamento das classes locais mudou. Nesta versão da linguagem, uma classe local tem acesso não apenas a finalvariáveis ​​e parâmetros locais, mas também àqueles que são effective-final. Effective-finalé uma variável cujo valor não mudou desde a inicialização. Por exemplo, no Java 8, podemos exibir facilmente a usCountryCodevariável no console, mesmo que não seja final. O importante é que seu valor não mude. No exemplo a seguir, tudo funciona como deveria:

public void validatePhoneNumber(String number) {

  String usCountryCode = "+1";

    class PhoneNumber {

       public void printUsCountryCode() {

           // Java 7 would produce an error here
           System.out.println(usCountryCode);
       }

    }

   // ...number validation code
}
Mas se alterarmos o valor da variável imediatamente após a inicialização, o código não será compilado.

public void validatePhoneNumber(String number) {

  String usCountryCode = "+1";
  usCountryCode = "+8";

    class PhoneNumber {

       public void printUsCountryCode() {

           // Error!
           System.out.println(usCountryCode);
       }

    }

   // ...number validation code
}
Não é de admirar que uma classe local seja uma subespécie do conceito de classe interna! Eles também têm características comuns. Uma classe local tem acesso a todos os campos (mesmo privados) e métodos da classe externa: estáticos e não estáticos. Por exemplo, vamos adicionar um String phoneNumberRegexcampo estático à nossa classe validadora:

public class PhoneNumberValidator {

   private static String phoneNumberRegex = "[^0-9]";

   public void validatePhoneNumber(String phoneNumber) {
       class PhoneNumber {
          
           // ......
       }
   }
}
A validação será realizada usando esta variável estática. O método verifica se a string passada contém caracteres que não correspondem à expressão regular " [^0-9]" (ou seja, qualquer caractere que não seja um dígito de 0 a 9). Podemos acessar facilmente essa variável da PhoneNumberclasse local. Por exemplo, escreva um getter:

public String getPhoneNumberRegex() {
  
   return phoneNumberRegex;
}
As classes locais são semelhantes às classes internas, porque não podem definir ou declarar nenhum membro estático. Classes locais em métodos estáticos só podem fazer referência a membros estáticos da classe envolvente. Por exemplo, se você não definir uma variável (campo) da classe delimitadora como estática, o compilador Java gerará um erro: "Variável não estática não pode ser referenciada a partir de um contexto estático." As classes locais não são estáticas porque têm acesso a membros de instância no bloco delimitador. Como resultado, eles não podem conter a maioria dos tipos de declarações estáticas. Você não pode declarar uma interface dentro de um bloco: as interfaces são inerentemente estáticas. Este código não compila:

public class PhoneNumberValidator {
   public static void validatePhoneNumber(String number) {
       interface I {}
      
       class PhoneNumber implements I{
           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }
       }

       // ...number validation code
   }
}
Mas se uma interface for declarada dentro de uma classe externa, a PhoneNumberclasse poderá implementá-la:

public class PhoneNumberValidator {
   interface I {}
  
   public static void validatePhoneNumber(String number) {
      
       class PhoneNumber implements I{
           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }
       }

       // ...number validation code
   }
}
Inicializadores estáticos (blocos de inicialização) ou interfaces não podem ser declarados em classes locais. Mas as classes locais podem ter membros estáticos, desde que sejam variáveis ​​constantes ( static final). E agora vocês já sabem sobre as aulas locais, pessoal! Como você pode ver, eles têm muitas diferenças em relação às classes internas comuns. Nós até tivemos que nos aprofundar nos recursos de versões específicas da linguagem para entender como elas funcionam :) Na próxima lição, falaremos sobre classes internas anônimas — o último grupo de classes aninhadas. Boa sorte em seus estudos! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION