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.
Dal 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:
Nella prossima lezione parleremo delle classi interne anonime — l'ultimo gruppo di classi nidificate. Buona fortuna per i tuoi studi. :)

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 final
al 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 PhoneNumber
classe 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 ilPhoneNumber
classe 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 HandleBar
una classe interna della bici. Qual è la differenza? Prima di tutto, il modo in cui viene utilizzata la classe è diverso. La HandleBar
classe nel secondo esempio è un'entità più complessa rispetto alla PhoneNumber
classe nel primo esempio. Innanzitutto, HandleBar
ha public right
e left
metodi (questi non sono setter/getter). In secondo luogo, è impossibile prevedere in anticipo dove potremmo averne bisogno e la sua Bicycle
classe esterna. Potrebbero esserci dozzine di luoghi e metodi diversi, anche in un unico programma. Ma con la PhoneNumber
classe, 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 nostroPhoneNumberValidator
non 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 nostroPhoneNumberValidator
nel 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 for
giro!
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 PhoneNumber
oggetto. 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 usCountryCode
variabile locale all'interno del validatePhoneNumber()
metodo per qualche motivo, possiamo accedervi dalla PhoneNumber
classe 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 final
nel 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 final
parametri del metodo e final
alle variabili locali. In Java 8, il comportamento delle classi locali è cambiato. In questa versione del linguaggio, una classe locale ha accesso non solo a final
variabili 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 usCountryCode
variabile 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 phoneNumberRegex
campo 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 PhoneNumber
classe 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 PhoneNumber
classe 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 :)
GO TO FULL VERSION