CodeGym /Java Blog /Random-IT /Classi interne in un metodo locale
John Squirrels
Livello 41
San Francisco

Classi interne in un metodo locale

Pubblicato nel gruppo Random-IT
CIAO! Parliamo di un altro tipo di classi nidificate. Sto parlando di classi locali (classi interne metodo-locali). Prima di addentrarci, dobbiamo innanzitutto ricordare il loro posto nella struttura delle classi nidificate. Classi interne in un metodo locale - 2Dal nostro diagramma, possiamo vedere che le classi locali sono una sottospecie delle classi interne, di cui abbiamo parlato in dettaglio nei materiali precedenti . Tuttavia, le classi locali hanno una serie di importanti caratteristiche e differenze rispetto alle ordinarie classi interne. La cosa principale è nella loro dichiarazione: una classe locale è dichiarata solo in un blocco di codice. Molto spesso, questa dichiarazione è all'interno di un metodo della classe esterna. Ad esempio, potrebbe assomigliare a questo:

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 hai installato Java 7, questo codice non verrà compilato quando viene incollato in IDEA. Parleremo delle ragioni di ciò alla fine della lezione. In breve, il funzionamento delle classi locali dipende fortemente dalla versione della lingua. Se questo codice non viene compilato per te, puoi cambiare la versione della lingua in IDEA in Java 8 o aggiungere la parola finalal parametro del metodo in modo che assomigli a questo: validatePhoneNumber(final String number). Dopodiché, tutto funzionerà. Questo è un piccolo programma che convalida i numeri di telefono. Il suo validatePhoneNumber()metodo accetta una stringa come input e determina se si tratta di un numero di telefono. E all'interno di questo metodo, abbiamo dichiarato la nostra PhoneNumberclasse locale. Potresti ragionevolmente chiedere perché. Perché esattamente dovremmo dichiarare una classe all'interno di un metodo? Perché non usare una classe interna ordinaria? Vero, avremmo potuto fare ilPhoneNumberclasse una classe interna. Ma la soluzione finale dipende dalla struttura e dallo scopo del tuo programma. Ricordiamo il nostro esempio da una lezione sulle classi interne:

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!");
       }
   }
}
In esso, abbiamo creato HandleBaruna classe interna della bici. Qual è la differenza? Prima di tutto, il modo in cui viene utilizzata la classe è diverso. La HandleBarclasse nel secondo esempio è un'entità più complessa rispetto alla PhoneNumberclasse nel primo esempio. Innanzitutto, HandleBarha public righte leftmetodi (questi non sono setter/getter). In secondo luogo, è impossibile prevedere in anticipo dove potremmo averne bisogno e la sua Bicycleclasse esterna. Potrebbero esserci dozzine di luoghi e metodi diversi, anche in un unico programma. Ma con la PhoneNumberclasse, tutto è molto più semplice. Il nostro programma è molto semplice. Ha un solo scopo: controllare se un numero è un numero di telefono valido. Nella maggior parte dei casi, il nostroPhoneNumberValidatornon sarà nemmeno un programma autonomo, ma piuttosto una parte della logica di autorizzazione per un programma più ampio. Ad esempio, vari siti Web richiedono spesso un numero di telefono quando gli utenti si registrano. Se inserisci delle sciocchezze invece dei numeri, il sito web segnalerà un errore: "Questo non è un numero di telefono!" Gli sviluppatori di un tale sito Web (o meglio, il suo meccanismo di autorizzazione dell'utente) possono includere qualcosa di simile al nostroPhoneNumberValidatornel loro codice. In altre parole, abbiamo una classe esterna con un metodo, che verrà utilizzato in un punto del programma e da nessun'altra parte. E se viene utilizzato, non cambierà nulla in esso: un metodo fa il suo lavoro - e basta. In questo caso, poiché tutta la logica è raccolta in un metodo, sarà molto più conveniente e opportuno incapsularvi una classe aggiuntiva. Non ha metodi propri tranne un getter e un setter. In effetti, abbiamo bisogno solo dei dati del costruttore. Non è coinvolto in altri metodi. Di conseguenza, non vi è alcun motivo per prendere informazioni su di esso al di fuori dell'unico metodo in cui viene utilizzato. Abbiamo anche fornito un esempio in cui una classe locale è dichiarata in un metodo, ma questa non è l'unica opzione. Può essere dichiarato semplicemente in un blocco di codice:

public class PhoneNumberValidator {
  
   {
       class PhoneNumber {

           private String phoneNumber;

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

   }

   public void validatePhoneNumber(String phoneNumber) {

      
       // ...number validation code
   }
}
O anche nel forgiro!

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
   }
}
Ma tali casi sono estremamente rari. Nella maggior parte dei casi, la dichiarazione avverrà all'interno del metodo. Quindi, abbiamo capito le dichiarazioni e abbiamo anche parlato della "filosofia" :) Quali caratteristiche e differenze aggiuntive hanno le classi locali rispetto alle classi interne? Un oggetto di una classe locale non può essere creato al di fuori del metodo o del blocco in cui è dichiarato. Immagina di aver bisogno di un generatePhoneNumber()metodo che generi un numero di telefono casuale e restituisca un PhoneNumberoggetto. Nella nostra situazione attuale, non possiamo creare un tale metodo nella nostra classe validator:

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

   }

}
Un'altra caratteristica importante delle classi locali è la possibilità di accedere alle variabili locali e ai parametri del metodo. Nel caso l'avessi dimenticato, una variabile dichiarata all'interno di un metodo è nota come variabile "locale". Cioè, se creiamo una String usCountryCodevariabile locale all'interno del validatePhoneNumber()metodo per qualche motivo, possiamo accedervi dalla PhoneNumberclasse locale. Tuttavia, ci sono molte sottigliezze che dipendono dalla versione della lingua utilizzata nel programma. All'inizio della lezione, abbiamo notato che il codice per uno degli esempi potrebbe non essere compilato in Java 7, ricordi? Ora consideriamo le ragioni di ciò :) In Java 7, una classe locale può accedere a una variabile locale o a un parametro di metodo solo se sono dichiarati come finalnel metodo:

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
}
Qui il compilatore genera due errori. E tutto è in ordine qui:

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
}
Ora sai perché il codice dall'inizio della lezione non viene compilato: in Java 7, una classe locale ha accesso solo ai finalparametri del metodo e finalalle variabili locali. In Java 8, il comportamento delle classi locali è cambiato. In questa versione del linguaggio, una classe locale ha accesso non solo a finalvariabili e parametri locali, ma anche a quelli che sono effective-final. Effective-finalè una variabile il cui valore non è cambiato dall'inizializzazione. Ad esempio, in Java 8, possiamo facilmente visualizzare la usCountryCodevariabile sulla console, anche se non è final. L'importante è che il suo valore non cambi. Nell'esempio seguente, tutto funziona come dovrebbe:

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
}
Ma se cambiamo il valore della variabile subito dopo l'inizializzazione, il codice non verrà compilato.

public void validatePhoneNumber(String number) {

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

    class PhoneNumber {

       public void printUsCountryCode() {

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

    }

   // ...number validation code
}
Non c'è da stupirsi che una classe locale sia una sottospecie del concetto di classe interna! Hanno anche caratteristiche comuni. Una classe locale ha accesso a tutti i campi e metodi (anche privati) della classe esterna: sia statici che non statici. Ad esempio, aggiungiamo un String phoneNumberRegexcampo statico alla nostra classe validator:

public class PhoneNumberValidator {

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

   public void validatePhoneNumber(String phoneNumber) {
       class PhoneNumber {
          
           // ......
       }
   }
}
La convalida verrà eseguita utilizzando questa variabile statica. Il metodo controlla se la stringa passata contiene caratteri che non corrispondono all'espressione regolare " [^0-9]" (ovvero, qualsiasi carattere che non sia una cifra da 0 a 9). Possiamo facilmente accedere a questa variabile dalla PhoneNumberclasse locale. Ad esempio, scrivi un getter:

public String getPhoneNumberRegex() {
  
   return phoneNumberRegex;
}
Le classi locali sono simili alle classi interne, perché non possono definire o dichiarare alcun membro statico. Le classi locali nei metodi statici possono fare riferimento solo a membri statici della classe di inclusione. Ad esempio, se non si definisce come statica una variabile (campo) della classe che la contiene, il compilatore Java genera un errore: "Impossibile fare riferimento a una variabile non statica da un contesto statico". Le classi locali non sono statiche, perché hanno accesso ai membri dell'istanza nel blocco di inclusione. Di conseguenza, non possono contenere la maggior parte dei tipi di dichiarazioni statiche. Non puoi dichiarare un'interfaccia all'interno di un blocco: le interfacce sono intrinsecamente statiche. Questo codice non 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
   }
}
Ma se un'interfaccia viene dichiarata all'interno di una classe esterna, la PhoneNumberclasse può 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
   }
}
Gli inizializzatori statici (blocchi di inizializzazione) o le interfacce non possono essere dichiarati nelle classi locali. Ma le classi locali possono avere membri statici, purché siano variabili costanti ( static final). E ora conosci le lezioni locali, gente! Come puoi vedere, hanno molte differenze rispetto alle classi interne ordinarie. Abbiamo anche dovuto approfondire le caratteristiche di versioni specifiche del linguaggio per capire come funzionano :) Nella prossima lezione parleremo delle classi interne anonime — l'ultimo gruppo di classi nidificate. Buona fortuna per i tuoi studi. :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION