CodeGym /Blog Java /Random-ES /Clases internas en un método local
Autor
Oleksandr Miadelets
Head of Developers Team at CodeGym

Clases internas en un método local

Publicado en el grupo Random-ES
¡Hola! Hablemos de otro tipo de clases anidadas. Estoy hablando de clases locales (clases internas locales de método). Antes de sumergirnos, primero debemos recordar su lugar en la estructura de clases anidadas. Clases internas en un método local - 2En nuestro diagrama, podemos ver que las clases locales son una subespecie de las clases internas, de las que hablamos en detalle en materiales anteriores . Sin embargo, las clases locales tienen una serie de características y diferencias importantes con respecto a las clases internas ordinarias. Lo principal está en su declaración: una clase local se declara solo en un bloque de código. La mayoría de las veces, esta declaración está dentro de algún método de la clase externa. Por ejemplo, podría verse así:

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!Si tiene instalado Java 7, este código no se compilará cuando se pegue en IDEA. Hablaremos de las razones de esto al final de la lección. En resumen, el funcionamiento de las clases locales depende en gran medida de la versión del idioma. Si este código no se compila para usted, puede cambiar la versión de idioma en IDEA a Java 8 o agregar la palabra finalal parámetro del método para que se vea así: validatePhoneNumber(final String number). Después de eso, todo funcionará. Este es un pequeño programa que valida números de teléfono. Su validatePhoneNumber()método toma una cadena como entrada y determina si se trata de un número de teléfono. Y dentro de este método, declaramos nuestra PhoneNumberclase local. Usted podría preguntar razonablemente por qué. ¿Por qué exactamente declararíamos una clase dentro de un método? ¿Por qué no usar una clase interna ordinaria? Cierto, podríamos haber hecho elPhoneNumberclase una clase interna. Pero la solución final depende de la estructura y el propósito de su programa. Recordemos nuestro ejemplo de una lección sobre clases 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!");
       }
   }
}
En él hicimos HandleBaruna clase interior de la moto. ¿Cual es la diferencia? En primer lugar, la forma en que se usa la clase es diferente. La HandleBarclase del segundo ejemplo es una entidad más compleja que la PhoneNumberclase del primer ejemplo. Primero, HandleBartiene public righty leftmétodos (estos no son setters/getters). En segundo lugar, es imposible predecir de antemano dónde podemos necesitarlo y su Bicycleclase externa. Podría haber docenas de lugares y métodos diferentes, incluso en un solo programa. Pero con la PhoneNumberclase, todo es mucho más sencillo. Nuestro programa es muy simple. Tiene un solo propósito: verificar si un número es un número de teléfono válido. En la mayoría de los casos, nuestroPhoneNumberValidatorni siquiera será un programa independiente, sino una parte de la lógica de autorización para un programa más grande. Por ejemplo, varios sitios web a menudo solicitan un número de teléfono cuando los usuarios se registran. Si ingresa alguna tontería en lugar de números, el sitio web informará un error: "¡Este no es un número de teléfono!" Los desarrolladores de dicho sitio web (o más bien, su mecanismo de autorización de usuario) pueden incluir algo similar a nuestroPhoneNumberValidatoren su código. En otras palabras, tenemos una clase externa con un método, que se usará en un lugar del programa y en ningún otro lugar. Y si se usa, nada cambiará en él: un método hace su trabajo, y eso es todo. En este caso, debido a que toda la lógica se reúne en un solo método, será mucho más conveniente y apropiado encapsular una clase adicional allí. No tiene métodos propios excepto getter y setter. De hecho, solo necesitamos datos del constructor. No está involucrado en otros métodos. En consecuencia, no hay motivo para tomar información al respecto fuera del único método en el que se utiliza. También dimos un ejemplo en el que se declara una clase local en un método, pero esta no es la única opción. Se puede declarar simplemente en un bloque 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
   }
}
¡O incluso en el forbucle!

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
   }
}
Pero tales casos son extremadamente raros. En la mayoría de los casos, la declaración ocurrirá dentro del método. Entonces, descubrimos las declaraciones y también hablamos sobre la "filosofía" :) ¿Qué características y diferencias adicionales tienen las clases locales en comparación con las clases internas? Un objeto de una clase local no se puede crear fuera del método o bloque en el que se declara. Imagine que necesitamos un generatePhoneNumber()método que genere un número de teléfono aleatorio y devuelva un PhoneNumberobjeto. En nuestra situación actual, no podemos crear dicho método en nuestra clase de validación:

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() {

   }

}
Otra característica importante de las clases locales es la capacidad de acceder a variables locales y parámetros de métodos. En caso de que lo hayas olvidado, una variable declarada dentro de un método se conoce como variable "local". Es decir, si por alguna razón creamos una String usCountryCodevariable local dentro del validatePhoneNumber()método, podemos acceder a ella desde la PhoneNumberclase local. Sin embargo, hay muchas sutilezas que dependen de la versión del lenguaje utilizado en el programa. Al comienzo de la lección, notamos que el código de uno de los ejemplos puede no compilarse en Java 7, ¿recuerdas? Ahora consideremos las razones de esto :) En Java 7, una clase local puede acceder a una variable local o parámetro de método solo si se declaran como finalen el 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
}
Aquí el compilador genera dos errores. Y todo está en orden aquí:

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
}
Ahora sabe por qué el código del comienzo de la lección no se compilaría: en Java 7, una clase local solo tiene acceso a los finalparámetros del método y finallas variables locales. En Java 8, el comportamiento de las clases locales ha cambiado. En esta versión del lenguaje, una clase local tiene acceso no solo a finalvariables y parámetros locales, sino también a aquellos que son effective-final. Effective-finales una variable cuyo valor no ha cambiado desde la inicialización. Por ejemplo, en Java 8, podemos mostrar fácilmente la usCountryCodevariable en la consola, incluso si no es final. Lo importante es que su valor no cambia. En el siguiente ejemplo, todo funciona como debería:

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
}
Pero si cambiamos el valor de la variable inmediatamente después de la inicialización, el código no se compilará.

public void validatePhoneNumber(String number) {

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

    class PhoneNumber {

       public void printUsCountryCode() {

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

    }

   // ...number validation code
}
¡No es de extrañar que una clase local sea una subespecie del concepto de clase interna! También tienen características comunes. Una clase local tiene acceso a todos los campos y métodos (incluso privados) de la clase externa: tanto estáticos como no estáticos. Por ejemplo, agreguemos un String phoneNumberRegexcampo estático a nuestra clase de validación:

public class PhoneNumberValidator {

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

   public void validatePhoneNumber(String phoneNumber) {
       class PhoneNumber {
          
           // ......
       }
   }
}
La validación se realizará utilizando esta variable estática. El método comprueba si la cadena pasada contiene caracteres que no coinciden con la expresión regular " [^0-9]" (es decir, cualquier carácter que no sea un dígito del 0 al 9). Podemos acceder fácilmente a esta variable desde la PhoneNumberclase local. Por ejemplo, escribe un captador:

public String getPhoneNumberRegex() {
  
   return phoneNumberRegex;
}
Las clases locales son similares a las clases internas, porque no pueden definir ni declarar ningún miembro estático. Las clases locales en métodos estáticos solo pueden hacer referencia a miembros estáticos de la clase adjunta. Por ejemplo, si no define una variable (campo) de la clase adjunta como estática, el compilador de Java genera un error: "No se puede hacer referencia a la variable no estática desde un contexto estático". Las clases locales no son estáticas porque tienen acceso a los miembros de la instancia en el bloque adjunto. Como resultado, no pueden contener la mayoría de los tipos de declaraciones estáticas. No puede declarar una interfaz dentro de un bloque: las interfaces son inherentemente estáticas. Este código no 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
   }
}
Pero si se declara una interfaz dentro de una clase externa, la PhoneNumberclase puede implementarla:

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
   }
}
Los inicializadores estáticos (bloques de inicialización) o las interfaces no se pueden declarar en clases locales. Pero las clases locales pueden tener miembros estáticos, siempre que sean variables constantes ( static final). ¡Y ahora saben acerca de las clases locales, amigos! Como puede ver, tienen muchas diferencias con respecto a las clases internas ordinarias. Incluso tuvimos que profundizar en las funciones de versiones específicas del lenguaje para comprender cómo funcionan :) En la próxima lección, hablaremos sobre las clases internas anónimas, el último grupo de clases anidadas. ¡Buena suerte en tus estudios! :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION