JavaRush /Blog Java /Random-PL /Klasy wewnętrzne w metodzie lokalnej

Klasy wewnętrzne w metodzie lokalnej

Opublikowano w grupie Random-PL
Cześć! Porozmawiajmy o innym typie klasy zagnieżdżonej. Mianowicie o klasach lokalnych (Metoda lokalnych klas wewnętrznych). Pierwszą rzeczą, o której musisz pamiętać przed rozpoczęciem nauki, jest ich miejsce w strukturze zagnieżdżonych klas. Klasy wewnętrzne w metodzie lokalnej - 2Na podstawie naszego diagramu możemy zrozumieć, że klasy lokalne są podtypem klas wewnętrznych, o których szczegółowo mówiliśmy w jednym z poprzednich materiałów . Klasy lokalne mają jednak wiele ważnych cech i różnią się od klas wewnętrznych. Klucz tkwi w ich deklaracji: klasa lokalna jest deklarowana tylko w bloku kodu. Najczęściej - 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;
           }
       }

       //...kod валидации номера
   }
}
WAŻNY!Ten kod nie zostanie skompilowany po wklejeniu do IDEA, jeśli masz zainstalowaną Javę 7. O przyczynach tego porozmawiamy pod koniec wykładu. Krótko mówiąc, praca klas lokalnych jest w dużym stopniu uzależniona od wersji językowej. Jeśli ten kod się nie skompiluje, możesz albo zmienić wersję językową w IDEA na Java 8, albo dodać słowo finaldo parametru metody, aby wyglądało to tak: validatePhoneNumber(final String number). Po tym wszystko będzie działać. To mały program - walidator numeru telefonu. Jej metoda validatePhoneNumber()pobiera ciąg znaków jako dane wejściowe i określa, czy jest to numer telefonu. Wewnątrz tej metody zadeklarowaliśmy naszą klasę lokalną PhoneNumber. Możesz zadać logiczne pytanie: dlaczego? Po co deklarować klasę wewnątrz metody? Dlaczego nie użyć zwykłej klasy wewnętrznej? Rzeczywiście można by to zrobić: uczynić klasę PhoneNumberwewnętrzną. Inną rzeczą jest to, że ostateczna decyzja zależy od struktury i celu Twojego programu. Przypomnijmy sobie nasz przykład z wykładu o klasach wewnętrznych:
public class Bicycle {

   private String model;
   private int mawWeight;

   public Bicycle(String model, int mawWeight) {
       this.model = model;
       this.mawWeight = mawWeight;
   }

   public void start() {
       System.out.println("Iść!");
   }

   public class HandleBar {

       public void right() {
           System.out.println("Kierownica w prawo!");
       }

       public void left() {

           System.out.println("Kierownica w lewo!");
       }
   }
}
W nim zrobiliśmy HandleBar(kierownicę) wewnętrzną klasę roweru. Co za różnica? Przede wszystkim w korzystaniu z klasy. Klasa HandleBarz drugiego przykładu jest bardziej złożoną jednostką niż PhoneNumberz pierwszego. Po pierwsze, y HandleBarma metody publiczne righti left(nie są funkcjami ustawiającymi i pobierającymi). Po drugie, nie jesteśmy w stanie z góry przewidzieć, gdzie Bicyclebędziemy go potrzebować i jaka jest jego klasa zewnętrzna - mogą to być dziesiątki różnych miejsc i metod, nawet w ramach tego samego programu. Ale z klasą PhoneNumberwszystko jest znacznie prostsze. Nasz program jest bardzo prosty. Ma tylko jedną funkcję - sprawdza, czy numer jest numerem telefonu. W większości przypadków nasz PhoneNumberValidatornie będzie nawet samodzielnym programem, ale po prostu częścią logiki autoryzacji dla programu głównego. Na przykład na różnych stronach internetowych podczas rejestracji często jesteś proszony o podanie numeru telefonu. A jeśli zamiast liczb wpiszesz jakieś bzdury, witryna wyświetli błąd: „To nie jest numer telefonu!” W celu działania takiej witryny (a raczej mechanizmu autoryzacji użytkownika) jej twórcy mogą włączyć do kodu nasz analog PhoneNumberValidator. 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 tak, to nic się w nim nie zmieni: jedna metoda robi swoje – i tyle. W takim przypadku, ponieważ cała logika pracy jest zebrana w jednej metodzie, znacznie wygodniej i poprawniej będzie umieścić tam dodatkową klasę. Nie ma własnych metod innych niż getter i setter. W zasadzie potrzebujemy tylko danych konstruktora. Nie jest stosowany w innych metodach. Dlatego nie ma powodu rozszerzać informacji na ten temat poza jedną metodę, w której jest stosowana. Podaliśmy przykład zadeklarowania klasy lokalnej w metodzie, ale nie jest to jedyna możliwość. 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) {


       //...kod валидации номера
   }
}
Albo nawet w pętli for!
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;
               }
           }

           //...Jakая-то логика
       }

       //...kod валидации номера
   }
}
Ale takie przypadki są niezwykle rzadkie. W większości przypadków deklaracja nadal będzie występować wewnątrz metody. Zajęliśmy się więc ogłoszeniem, rozmawialiśmy też o „filozofii” :) Jakie jeszcze cechy i różnice mają zajęcia lokalne od zajęć wewnętrznych? Obiekt klasy lokalnej nie może zostać utworzony poza metodą lub blokiem, w którym jest zadeklarowany. Wyobraź sobie, że potrzebujemy metody generatePhoneNumber(), która generuje losowy numer telefonu i zwraca plik PhoneNumber. W obecnej sytuacji nie będziemy mogli 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;
           }
       }

       //...kod валидации номера
   }

   //ошибка! компилятор не понимает, что это за класс - PhoneNumber
   public PhoneNumber generatePhoneNumber() {

   }

}
Kolejną ważną cechą klas lokalnych jest możliwość dostępu do zmiennych lokalnych i parametrów metod. Jeśli zapomniałeś, „lokalny” to zmienna zadeklarowana wewnątrz metody. Oznacza to, że jeśli dla niektórych celów utworzymy zmienną lokalną String russianCountryCodewewnątrz metody validatePhoneNumber(), będziemy mogli uzyskać do niej dostęp z klasy lokalnej PhoneNumber. Jest tu jednak sporo niuansów, które zależą od wersji języka użytego w programie. Na początku wykładu zwróciliśmy uwagę, że kod w jednym z przykładów może nie skompilować się w Javie 7, pamiętasz? Teraz spójrzmy na przyczyny :) W Javie 7 klasa lokalna może uzyskać dostęp do zmiennej lokalnej lub parametru metody tylko wtedy, gdy są one zadeklarowane w metodzie jako final:
public void validatePhoneNumber(String number) {

   String russianCountryCode = "+7";

   class PhoneNumber {

       private String phoneNumber;

       //ошибка! параметр метода должен быть объявлен Jak final!
       public PhoneNumber() {
           this.phoneNumber = number;
       }

       public void printRussianCountryCode() {

           //ошибка! локальная переменная должна быть объявлена Jak final!
           System.out.println(russianCountryCode);
       }

   }

   //...kod валидации номера
}
Tutaj kompilator zgłosił dwa błędy. Ale tutaj wszystko jest w porządku:
public void validatePhoneNumber(final String number) {

   final String russianCountryCode = "+7";

    class PhoneNumber {

       private String phoneNumber;


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

       public void printRussianCountryCode() {

           System.out.println(russianCountryCode);
       }

    }

   //...kod валидации номера
}
Teraz znasz powód, dla którego kod z początku wykładu nie został skompilowany: klasa lokalna w Javie 7 ma dostęp tylko do finalparametrów -method i finalzmiennych -local. 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 -local, ale także do effective-final. Effective-finaljest zmienną, której wartość nie uległa zmianie od czasu inicjalizacji. Na przykład w Javie 8 możemy łatwo wyświetlić zmienną w konsoli russianCountryCode, nawet jeśli nią nie jest final. Najważniejsze, że nie zmienia to jego znaczenia. W tym przykładzie wszystko działa jak należy:
public void validatePhoneNumber(String number) {

  String russianCountryCode = "+7";

    class PhoneNumber {

       public void printRussianCountryCode() {

           //в Java 7 здесь была бы ошибка
           System.out.println(russianCountryCode);
       }

    }

   //...kod валидации номера
}
Jeśli jednak zmienimy wartość zmiennej natychmiast po inicjalizacji, kod nie zostanie skompilowany.
public void validatePhoneNumber(String number) {

  String russianCountryCode = "+7";
  russianCountryCode = "+8";

    class PhoneNumber {

       public void printRussianCountryCode() {

           //błąd!
           System.out.println(russianCountryCode);
       }

    }

   //...kod валидации номера
}
Ale nie bez powodu klasa lokalna jest podtypem klasy wewnętrznej! Mają też punkty wspólne. 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 pole statyczne do naszej klasy walidatora String phoneNumberRegex:
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 do niej ciąg znaków zawiera znaki, które nie pasują do wyrażenia regularnego " [^0-9]" (czyli znak nie jest liczbą od 0 do 9). Możemy łatwo uzyskać dostęp do tej zmiennej z klasy lokalnej PhoneNumber. Na przykład napisz getter:
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 elementów klasy otaczającej. Na przykład, jeśli zmienna (pole) otaczającej klasy nie zostanie zdefiniowana jako statyczna, 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 elementów członkowskich instancji bloku zawierającego. Dlatego nie mogą zawierać większości typów deklaracji statycznych. Nie można zadeklarować interfejsu wewnątrz bloku; Interfejsy mają charakter statyczny. Ten kod się nie skompiluje:
public class PhoneNumberValidator {
   public static void validatePhoneNumber(String number) {
       interface I {}

       class PhoneNumber implements I{
           private String phoneNumber;

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

       //...kod валидации номера
   }
}
Ale jeśli interfejs jest zadeklarowany wewnątrz klasy zewnętrznej, klasa PhoneNumbermoż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;
           }
       }

       //...kod валидации номера
   }
}
Klasy lokalne nie mogą deklarować inicjatorów statycznych (bloków inicjujących) ani interfejsów. Ale klasy lokalne mogą mieć składowe statyczne, pod warunkiem, że są zmiennymi stałymi ( static final). To właśnie są zajęcia lokalne! Jak widać, różnią się one znacznie od klas wewnętrznych. Musieliśmy nawet zgłębić funkcje wersji językowej, żeby zrozumieć, jak one działają :) W następnym wykładzie porozmawiamy o anonimowych klasach wewnętrznych – ostatniej grupie klas zagnieżdżonych. Powodzenia z Twoimi studiami! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION