class OuterClass {
...
static class StaticNestedClass {
...
}
class InnerClass {
...
}
}
Te wewnętrzne klasy są nazywane zagnieżdżonymi. Dzielą się na 2 rodzaje:
- Niestatyczne klasy zagnieżdżone. Są one również nazywane klasami wewnętrznymi.
- Statyczne klasy zagnieżdżone.
- lokalna klasa
- anonimowa klasa
W dzisiejszej lekcji omówimy klasy wewnętrzne (znane również jako niestatyczne klasy zagnieżdżone). Są one specjalnie wyróżnione na ogólnym diagramie, żeby się nie zgubić :) Zacznijmy od oczywistego pytania: dlaczego nazywamy je klasami „wewnętrznymi”? Odpowiedź jest dość prosta: ponieważ są tworzone wewnątrz innych klas. Oto przykład:
public class Bicycle {
private String model;
private int weight;
public Bicycle(String model, int weight) {
this.model = model;
this.weight = weight;
}
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!");
}
}
public class Seat {
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
}
Tutaj mamy Bicycleklasę. Ma 2 pola i 1 metodę: start().
Różni się od zwykłej klasy tym, że zawiera dwie klasy: Handlebari Seat. Ich kod jest napisany wewnątrz Bicycleklasy. Są to pełnoprawne klasy: jak widać, każda z nich ma swoje własne metody. W tym momencie możesz mieć pytanie: dlaczego, u licha, mielibyśmy umieszczać jedną klasę w drugiej? Po co robić z nich klasy wewnętrzne? Załóżmy, że w naszym programie potrzebujemy osobnych klas dla pojęć kierownicy i siodełka. Oczywiście nie musimy ich zagnieżdżać! Możemy zrobić zwykłe zajęcia. Na przykład tak:
public class Handlebar {
public void right() {
System.out.println("Steer right!");
}
public void left() {
System.out.println("Steer left");
}
}
public class Seat {
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
Bardzo dobre pytanie! Oczywiście nie ogranicza nas technologia. Zrobienie tego jest z pewnością opcją. Tutaj ważniejsze jest bardziej poprawne zaprojektowanie zajęć z punktu widzenia konkretnego programu i jego przeznaczenia. Klasy wewnętrzne służą do oddzielenia jednostki, która jest nierozerwalnie połączona z inną jednostką. Kierownica, siodełko i pedały są elementami roweru. Oddzielone od roweru nie mają większego sensu. Gdybyśmy rozdzielili wszystkie te koncepcje na klasy publiczne, w naszym programie mielibyśmy taki kod:
public class Main {
public static void main(String[] args) {
Handlebar handlebar = new Handlebar();
handlebar.right();
}
}
Hmm... Znaczenie tego kodu jest nawet trudne do wyjaśnienia. Mamy jakąś niejasną kierownicę (Dlaczego to konieczne? Nie mam pojęcia, szczerze mówiąc). A ten uchwyt skręca w prawo... całkiem sam, bez roweru... z jakiegoś powodu. Oddzielając koncepcję kierownicy od koncepcji roweru, straciliśmy trochę logiki w naszym programie. Używając klasy wewnętrznej, kod wygląda zupełnie inaczej:
public class Main {
public static void main(String[] args) {
Bicycle peugeot = new Bicycle("Peugeot", 120);
Bicycle.Handlebar handlebar = peugeot.new Handlebar();
Bicycle.Seat seat = peugeot.new Seat();
seat.up();
peugeot.start();
handlebar.left();
handlebar.right();
}
}
Wyjście konsoli:
Seat up!
Let's go!
Steer left!
Steer right!
Teraz to, co widzimy, nagle nabiera sensu! :) Stworzyliśmy obiekt rowerowy. Stworzyliśmy dwa „podobiekty” rowerowe — kierownicę i siodełko. Podnieśliśmy siedzenie dla wygody i ruszyliśmy: pedałowanie i sterowanie w razie potrzeby! :) Metody, których potrzebujemy, są wywoływane na odpowiednich obiektach. To wszystko jest proste i wygodne. W tym przykładzie oddzielenie kierownicy i siodełka poprawia enkapsulację (ukrywamy dane o częściach roweru w odpowiedniej klasie) i pozwala nam stworzyć bardziej szczegółową abstrakcję. Spójrzmy teraz na inną sytuację. Załóżmy, że chcemy stworzyć program symulujący sklep rowerowy i części zamienne do rowerów.
W tej sytuacji nasze poprzednie rozwiązanie nie będzie działać. W sklepie rowerowym każda pojedyncza część roweru ma sens, nawet jeśli jest oddzielona od roweru. Na przykład będziemy potrzebować metod takich jak „sprzedaj pedały klientowi”, „kup nowe siodełko” itp. Błędem byłoby stosowanie tutaj klas wewnętrznych — każda pojedyncza część roweru w naszym nowym programie ma znaczenie, które opiera się na własny: można go oddzielić od koncepcji roweru. To jest dokładnie to, na co musisz zwrócić uwagę, jeśli zastanawiasz się, czy powinieneś używać klas wewnętrznych, czy organizować wszystkie jednostki jako osobne klasy. Programowanie zorientowane obiektowo jest dobre, ponieważ ułatwia modelowanie rzeczywistych obiektów. Może to być twoją przewodnią zasadą przy podejmowaniu decyzji, czy użyć klas wewnętrznych. W prawdziwym sklepie części zamienne są oddzielone od rowerów — to jest w porządku. Oznacza to, że jest to również w porządku podczas projektowania programu. Dobra, „filozofię” mamy już za sobą :) A teraz zapoznajmy się z ważnymi „technicznymi” cechami klas wewnętrznych. Oto, co zdecydowanie musisz zapamiętać i zrozumieć:
-
Obiekt klasy wewnętrznej nie może istnieć bez obiektu klasy zewnętrznej.
To ma sens: dlatego stworzyliśmy w naszym programie zajęcia
SeatiHandlebar— aby nie skończyć z osieroconymi kierownicami i siedzeniami.Ten kod się nie kompiluje:
public static void main(String[] args) { Handlebar handlebar = new Handlebar(); }Wynika z tego jeszcze jedna ważna cecha:
-
Obiekt klasy wewnętrznej ma dostęp do zmiennych klasy zewnętrznej.
Na przykład dodajmy
int seatPostDiameterdo naszej klasy zmienną (reprezentującą średnicę sztycy)Bicycle.Następnie w
Seatklasie wewnętrznej możemy stworzyćdisplaySeatProperties()metodę wyświetlającą właściwości siedzenia:public class Bicycle { private String model; private int weight; private int seatPostDiameter; public Bicycle(String model, int weight, int seatPostDiameter) { this.model = model; this.weight = weight; this.seatPostDiameter = seatPostDiameter; } public void start() { System.out.println("Let's go!"); } public class Seat { public void up() { System.out.println("Seat up!"); } public void down() { System.out.println("Seat down!"); } public void displaySeatProperties() { System.out.println("Seat properties: seatpost diameter = " + Bicycle.this.seatPostDiameter); } } }A teraz możemy wyświetlić te informacje w naszym programie:
public class Main { public static void main(String[] args) { Bicycle bicycle = new Bicycle("Peugeot", 120, 40); Bicycle.Seat seat = bicycle.new Seat(); seat.displaySeatProperties(); } }Wyjście konsoli:
Seat properties: seatpost diameter = 40Notatka:nowa zmienna jest deklarowana z najbardziej rygorystycznym modyfikatorem dostępu (
private). A mimo to klasa wewnętrzna ma dostęp! -
Obiekt klasy wewnętrznej nie może zostać utworzony w metodzie statycznej klasy zewnętrznej.
Wyjaśnia to specyfika organizacji klas wewnętrznych. Klasa wewnętrzna może mieć konstruktory z parametrami lub tylko konstruktor domyślny. Ale niezależnie od tego, kiedy tworzymy obiekt klasy wewnętrznej, referencja do obiektu klasy zewnętrznej jest niewidocznie przekazywana do tworzonego obiektu klasy wewnętrznej. W końcu obecność takiego odniesienia do obiektu jest bezwzględnym wymogiem. W przeciwnym razie nie będziemy mogli tworzyć obiektów klasy wewnętrznej.
Ale jeśli metoda klasy zewnętrznej jest statyczna, możemy nie mieć obiektu klasy zewnętrznej! Byłoby to naruszeniem logiki działania klasy wewnętrznej. W tej sytuacji kompilator wygeneruje błąd:
public static Seat createSeat() { // Bicycle.this cannot be referenced from a static context return new Seat(); } -
Klasa wewnętrzna nie może zawierać zmiennych statycznych i metod.
Logika jest taka sama: statyczne metody i zmienne mogą istnieć i być wywoływane lub przywoływane nawet w przypadku braku obiektu.
Ale bez obiektu klasy zewnętrznej nie będziemy mieli dostępu do klasy wewnętrznej.
Wyraźna sprzeczność! Dlatego statyczne zmienne i metody nie są dozwolone w klasach wewnętrznych.
Kompilator wygeneruje błąd, jeśli spróbujesz je utworzyć:
public class Bicycle { private int weight; public class Seat { // An inner class cannot have static declarations public static void displaySeatProperties() { System.out.println("Seat properties: seatpost diameter = " + Bicycle.this.seatPostDiameter); } } } -
Podczas tworzenia obiektu klasy wewnętrznej ważny jest jego modyfikator dostępu.
Klasę wewnętrzną można oznaczyć za pomocą standardowych modyfikatorów dostępu:
public,private,protected, ipackage private.Dlaczego to ma znaczenie?
Wpływa to na to, gdzie możemy tworzyć instancje klasy wewnętrznej w naszym programie.
Jeśli nasza
Seatklasa jest zadeklarowana jakopublic, możemy tworzyćSeatobiekty w dowolnej innej klasie. Jedynym wymaganiem jest istnienie obiektu klasy zewnętrznej.Nawiasem mówiąc, zrobiliśmy to już tutaj:
public class Main { public static void main(String[] args) { Bicycle peugeot = new Bicycle("Peugeot", 120); Bicycle.Handlebar handlebar = peugeot.new Handlebar(); Bicycle.Seat seat = peugeot.new Seat(); seat.up(); peugeot.start(); handlebar.left(); handlebar.right(); } }Łatwo uzyskaliśmy dostęp do
Handlebarklasy wewnętrznej zMainklasy.Jeśli zadeklarujemy klasę wewnętrzną jako
private, będziemy mogli tworzyć obiekty tylko wewnątrz klasy zewnętrznej.Nie możemy już tworzyć
Seatobiektu „na zewnątrz”:private class Seat { // Methods } public class Main { public static void main(String[] args) { Bicycle bicycle = new Bicycle("Peugeot", 120, 40); // Bicycle.Seat has private access in Bicycle Bicycle.Seat seat = bicycle.new Seat(); } }Pewnie już rozumiesz logikę :)
-
Modyfikatory dostępu dla klas wewnętrznych działają tak samo jak dla zwykłych zmiennych.
Modyfikator
protectedzapewnia dostęp do zmiennej instancji w podklasach i klasach, które znajdują się w tym samym pakiecie.protecteddziała również dla klas wewnętrznych. Możemy tworzyćprotectedobiekty klasy wewnętrznej:- w klasie zewnętrznej;
- w swoich podklasach;
- w klasach, które są w tym samym pakiecie.
Jeśli klasa wewnętrzna nie ma modyfikatora dostępu (
package private), można utworzyć obiekty klasy wewnętrznej:- w klasie zewnętrznej;
- w klasach, które są w tym samym pakiecie.
Od dawna znasz modyfikatory, więc tutaj nie ma problemów.
GO TO FULL VERSION