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. Na 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 final
do 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ę PhoneNumber
wewnę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 HandleBar
z drugiego przykładu jest bardziej złożoną jednostką niż PhoneNumber
z pierwszego. Po pierwsze, y HandleBar
ma metody publiczne right
i left
(nie są funkcjami ustawiającymi i pobierającymi). Po drugie, nie jesteśmy w stanie z góry przewidzieć, gdzie Bicycle
bę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ą PhoneNumber
wszystko 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 PhoneNumberValidator
nie 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 russianCountryCode
wewną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 final
parametrów -method i final
zmiennych -local. W Javie 8 zmieniło się zachowanie klas lokalnych. W tej wersji języka klasa lokalna ma dostęp nie tylko do final
zmiennych i parametrów -local, ale także do effective-final
. Effective-final
jest 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 PhoneNumber
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;
}
}
//...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! :)
GO TO FULL VERSION