CodeGym /Blog Java /Random-PL /Klasy wewnętrzne w metodzie lokalnej
Autor
Oleksandr Miadelets
Head of Developers Team at CodeGym

Klasy wewnętrzne w metodzie lokalnej

Opublikowano w grupie Random-PL
Cześć! Porozmawiajmy o innym rodzaju klas zagnieżdżonych. Mówię o klasach lokalnych (metoda-lokalne klasy wewnętrzne). Zanim zagłębimy się w to, musimy najpierw zapamiętać ich miejsce w strukturze klas zagnieżdżonych. Klasy wewnętrzne w metodzie lokalnej - 2Z naszego diagramu widać, że klasy lokalne są podgatunkiem klas wewnętrznych, o których szczegółowo mówiliśmy w poprzednich materiałach . Jednak klasy lokalne mają wiele ważnych cech i różnic w stosunku do zwykłych klas wewnętrznych. Najważniejsze jest w ich deklaracji: klasa lokalna jest deklarowana tylko w bloku kodu. Najczęściej deklaracja ta znajduje się wewnątrz jakiejś metody klasy zewnętrznej. Na przykład może to wyglądać tak:

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
   }
}
WAŻNY!Jeśli masz zainstalowaną Javę 7, ten kod nie skompiluje się po wklejeniu do IDEA. Porozmawiamy o przyczynach tego na końcu lekcji. Krótko mówiąc, sposób działania klas lokalnych w dużym stopniu zależy od wersji języka. Jeśli ten kod się nie skompiluje, możesz zmienić wersję językową w IDEA na Javę 8 lub dodać słowo finaldo parametru metody, aby wyglądało to tak: validatePhoneNumber(final String number). Po tym wszystko będzie działać. Jest to mały program, który sprawdza numery telefonów. Jej validatePhoneNumber()metoda pobiera ciąg znaków jako dane wejściowe i określa, czy jest to numer telefonu. Wewnątrz tej metody zadeklarowaliśmy naszą PhoneNumberklasę lokalną. Można rozsądnie zapytać, dlaczego. Dlaczego właściwie mielibyśmy deklarować klasę wewnątrz metody? Dlaczego nie użyć zwykłej klasy wewnętrznej? To prawda, mogliśmy zrobićPhoneNumberklasa wewnętrzna. Ale ostateczne rozwiązanie zależy od struktury i celu twojego programu. Przypomnijmy sobie nasz przykład z lekcji o klasach wewnętrznych:

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!");
       }
   }
}
W nim zrobiliśmy HandleBarwewnętrzną klasę roweru. Co za różnica? Po pierwsze, sposób użycia klasy jest inny. Klasa HandleBarw drugim przykładzie jest bardziej złożoną jednostką niż PhoneNumberklasa w pierwszym przykładzie. Po pierwsze, HandleBarma metody publiczne righti leftmetody (nie są to metody ustawiające/pobierające). Po drugie, nie można z góry przewidzieć, gdzie możemy go potrzebować i jego Bicycleklasy zewnętrznej. Może być dziesiątki różnych miejsc i metod, nawet w jednym programie. Ale z PhoneNumberklasą wszystko jest znacznie prostsze. Nasz program jest bardzo prosty. Ma tylko jeden cel: sprawdzić, czy numer jest prawidłowym numerem telefonu. W większości przypadków naszPhoneNumberValidatornie będzie nawet samodzielnym programem, ale raczej częścią logiki autoryzacji dla większego programu. Na przykład różne strony internetowe często proszą o podanie numeru telefonu podczas rejestracji. Jeśli zamiast liczb wpiszesz jakieś bzdury, strona zgłosi błąd: „To nie jest numer telefonu!” Twórcy takiej strony (a raczej jej mechanizmu autoryzacji użytkowników) mogą zawrzeć coś podobnego do naszegoPhoneNumberValidatorw ich kodzie. Innymi słowy mamy jedną klasę zewnętrzną z jedną metodą, która będzie używana w jednym miejscu programu i nigdzie indziej. A jeśli jest używany, to nic się w nim nie zmieni: jedna metoda robi swoje — i to wszystko. W tym przypadku, ponieważ cała logika jest zebrana w jednej metodzie, o wiele wygodniej i lepiej będzie umieścić tam dodatkową klasę. Nie ma własnych metod poza getterem i setterem. W rzeczywistości potrzebujemy tylko danych od konstruktora. Nie jest zaangażowany w inne metody. W związku z tym nie ma powodu, aby przenosić informacje na ten temat poza jedyną metodę, w której jest stosowana. Podaliśmy również przykład, w którym w metodzie jest zadeklarowana klasa lokalna, ale nie jest to jedyna opcja. Można to po prostu zadeklarować w bloku kodu:

public class PhoneNumberValidator {
  
   {
       class PhoneNumber {

           private String phoneNumber;

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

   }

   public void validatePhoneNumber(String phoneNumber) {

      
       // ...number validation code
   }
}
Lub nawet w forpętli!

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
   }
}
Ale takie przypadki są niezwykle rzadkie. W większości przypadków deklaracja nastąpi wewnątrz metody. Dogadaliśmy się więc z deklaracjami, rozmawialiśmy też o „filozofii” :) Jakie dodatkowe cechy i różnice mają klasy lokalne w porównaniu z klasami wewnętrznymi? Obiekt klasy lokalnej nie może zostać utworzony poza metodą lub blokiem, w którym został zadeklarowany. Wyobraźmy sobie, że potrzebujemy generatePhoneNumber()metody, która wygeneruje losowy numer telefonu i zwróci PhoneNumberobiekt. W naszej obecnej sytuacji nie możemy stworzyć takiej metody w naszej klasie walidatora:

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

   }

}
Inną ważną cechą klas lokalnych jest możliwość dostępu do zmiennych lokalnych i parametrów metod. Jeśli zapomniałeś, zmienna zadeklarowana wewnątrz metody jest znana jako zmienna „lokalna”. Oznacza to, że jeśli z jakiegoś powodu utworzymy String usCountryCodezmienną lokalną wewnątrz validatePhoneNumber()metody, możemy uzyskać do niej dostęp z PhoneNumberpoziomu klasy lokalnej. Istnieje jednak wiele subtelności, które zależą od wersji języka używanego w programie. Na początku lekcji zauważyliśmy, że kod dla jednego z przykładów może się nie skompilować w Javie 7, pamiętasz? Rozważmy teraz przyczyny takiego stanu rzeczy :) W Javie 7 klasa lokalna może uzyskać dostęp do zmiennej lokalnej lub parametru metody tylko wtedy, gdy są one zadeklarowane tak, jak finalw metodzie:

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
}
Tutaj kompilator generuje dwa błędy. I wszystko jest w porządku tutaj:

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
}
Teraz już wiesz, dlaczego kod z początku lekcji nie chciał się skompilować: w Javie 7 klasa lokalna ma dostęp tylko do finalparametrów metody i finalzmiennych lokalnych. W Javie 8 zmieniło się zachowanie klas lokalnych. W tej wersji języka klasa lokalna ma dostęp nie tylko do finalzmiennych i parametrów lokalnych, ale także do tych, które są effective-final. Effective-finaljest zmienną, której wartość nie zmieniła się od inicjalizacji. Na przykład w Javie 8 możemy łatwo wyświetlić usCountryCodezmienną na konsoli, nawet jeśli nie jest final. Co ważne, jego wartość się nie zmienia. W poniższym przykładzie wszystko działa tak, jak powinno:

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
}
Ale jeśli zmienimy wartość zmiennej natychmiast po inicjalizacji, kod się nie skompiluje.

public void validatePhoneNumber(String number) {

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

    class PhoneNumber {

       public void printUsCountryCode() {

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

    }

   // ...number validation code
}
Nic dziwnego, że klasa lokalna jest podgatunkiem koncepcji klasy wewnętrznej! Mają też wspólne cechy. Klasa lokalna ma dostęp do wszystkich (nawet prywatnych) pól i metod klasy zewnętrznej: zarówno statycznych, jak i niestatycznych. Na przykład dodajmy String phoneNumberRegexpole statyczne do naszej klasy walidatora:

public class PhoneNumberValidator {

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

   public void validatePhoneNumber(String phoneNumber) {
       class PhoneNumber {
          
           // ......
       }
   }
}
Walidacja zostanie przeprowadzona przy użyciu tej zmiennej statycznej. Metoda sprawdza, czy przekazany ciąg znaków zawiera znaki, które nie pasują do wyrażenia regularnego „ [^0-9]” (czyli dowolny znak, który nie jest cyfrą od 0 do 9). Możemy łatwo uzyskać dostęp do tej zmiennej z PhoneNumberklasy lokalnej. Na przykład napisz gettera:

public String getPhoneNumberRegex() {
  
   return phoneNumberRegex;
}
Klasy lokalne są podobne do klas wewnętrznych, ponieważ nie mogą definiować ani deklarować żadnych statycznych elementów członkowskich. Klasy lokalne w metodach statycznych mogą odwoływać się tylko do statycznych członków klasy otaczającej. Na przykład, jeśli nie zdefiniujesz zmiennej (pola) otaczającej klasy jako statycznej, kompilator Java wygeneruje błąd: „Nie można odwoływać się do zmiennej niestatycznej z kontekstu statycznego”. Klasy lokalne nie są statyczne, ponieważ mają dostęp do członków instancji w otaczającym bloku. W rezultacie nie mogą zawierać większości typów deklaracji statycznych. Nie możesz zadeklarować interfejsu wewnątrz bloku: interfejsy są z natury statyczne. Ten kod się nie kompiluje:

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
   }
}
Ale jeśli interfejs jest zadeklarowany wewnątrz klasy zewnętrznej, PhoneNumberklasa może go zaimplementować:

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
   }
}
Inicjatory statyczne (bloki inicjalizacyjne) lub interfejsy nie mogą być deklarowane w klasach lokalnych. Ale klasy lokalne mogą mieć statycznych członków, pod warunkiem, że są to zmienne stałe ( static final). A teraz wiesz o lokalnych klasach, ludzie! Jak widać, różnią się one od zwykłych klas wewnętrznych. Musieliśmy nawet zagłębić się w funkcje poszczególnych wersji języka, aby zrozumieć, jak działają :) W następnej lekcji porozmawiamy o anonimowych klasach wewnętrznych — ostatniej grupie klas zagnieżdżonych. Powodzenia w nauce! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION