Hallo! Lassen Sie uns über eine andere Art verschachtelter Klassen sprechen. Ich spreche von lokalen Klassen (methodenlokale innere Klassen). Bevor wir eintauchen, müssen wir uns zunächst an ihren Platz in der Struktur verschachtelter Klassen erinnern. Aus unserem Diagramm können wir ersehen, dass lokale Klassen eine Unterart innerer Klassen sind, über die wir in früheren Materialien ausführlich gesprochen haben . Lokale Klassen weisen jedoch eine Reihe wichtiger Merkmale und Unterschiede zu gewöhnlichen inneren Klassen auf. Die Hauptsache liegt in ihrer Deklaration: Eine lokale Klasse wird nur in einem Codeblock deklariert. Meistens befindet sich diese Deklaration innerhalb einer Methode der äußeren Klasse. Es könnte zum Beispiel so aussehen:
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
}
}
WICHTIG!Wenn Sie Java 7 installiert haben, wird dieser Code beim Einfügen in IDEA nicht kompiliert. Über die Gründe dafür sprechen wir am Ende der Lektion. Kurz gesagt, die Funktionsweise lokaler Klassen hängt stark von der Sprachversion ab. Wenn dieser Code nicht für Sie kompiliert werden kann, können Sie entweder die Sprachversion in IDEA auf Java 8 umstellen oder das Wort final
zum Methodenparameter hinzufügen, sodass es wie folgt aussieht: validatePhoneNumber(final String number)
. Danach wird alles funktionieren. Dies ist ein kleines Programm, das Telefonnummern validiert. Seine validatePhoneNumber()
Methode verwendet eine Zeichenfolge als Eingabe und bestimmt, ob es sich um eine Telefonnummer handelt. Und innerhalb dieser Methode haben wir unsere lokale PhoneNumber
Klasse deklariert. Man könnte sich berechtigterweise fragen, warum. Warum genau sollten wir eine Klasse innerhalb einer Methode deklarieren? Warum nicht eine gewöhnliche innere Klasse verwenden? Stimmt, das hätten wir machen könnenPhoneNumber
Klasse eine innere Klasse. Die endgültige Lösung hängt jedoch von der Struktur und dem Zweck Ihres Programms ab. Erinnern wir uns an unser Beispiel aus einer Lektion über innere Klassen:
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!");
}
}
}
Darin haben wir HandleBar
eine innere Klasse des Fahrrads geschaffen. Was ist der Unterschied? Erstens ist die Art und Weise, wie die Klasse verwendet wird, unterschiedlich. Die HandleBar
Klasse im zweiten Beispiel ist eine komplexere Entität als die PhoneNumber
Klasse im ersten Beispiel. Erstens HandleBar
verfügt es über öffentliche right
und left
Methoden (diese sind keine Setter/Getter). Zweitens ist es unmöglich, im Voraus vorherzusagen, wo wir es und seine äußere Bicycle
Klasse benötigen könnten. Selbst in einem einzigen Programm kann es Dutzende verschiedener Orte und Methoden geben. Aber mit der PhoneNumber
Klasse ist alles viel einfacher. Unser Programm ist sehr einfach. Es dient nur einem Zweck: zu überprüfen, ob eine Nummer eine gültige Telefonnummer ist. In den meisten Fällen unserePhoneNumberValidator
wird nicht einmal ein eigenständiges Programm sein, sondern eher ein Teil der Autorisierungslogik für ein größeres Programm. Beispielsweise fragen verschiedene Websites bei der Anmeldung häufig nach einer Telefonnummer. Wenn Sie anstelle von Nummern irgendeinen Unsinn eingeben, meldet die Website einen Fehler: „Dies ist keine Telefonnummer!“ Die Entwickler einer solchen Website (oder besser gesagt ihres Benutzerautorisierungsmechanismus) können etwas Ähnliches wie unseres einbauenPhoneNumberValidator
in ihrem Code. Mit anderen Worten: Wir haben eine äußere Klasse mit einer Methode, die an einer Stelle im Programm und nirgendwo anders verwendet wird. Und wenn es genutzt wird, ändert sich daran nichts: Eine Methode erfüllt ihren Zweck – und das ist alles. Da in diesem Fall die gesamte Logik in einer Methode zusammengefasst ist, ist es viel bequemer und sinnvoller, dort eine zusätzliche Klasse zu kapseln. Es verfügt über keine eigenen Methoden außer einem Getter und einem Setter. Tatsächlich benötigen wir nur Daten vom Konstruktor. Bei anderen Methoden ist es nicht beteiligt. Dementsprechend gibt es keinen Grund, Informationen darüber außerhalb der einzigen Methode zu nutzen, in der sie verwendet werden. Wir haben auch ein Beispiel gegeben, in dem eine lokale Klasse in einer Methode deklariert wird, aber das ist nicht die einzige Option. Es kann einfach in einem Codeblock deklariert werden:
public class PhoneNumberValidator {
{
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
}
public void validatePhoneNumber(String phoneNumber) {
// ...number validation code
}
}
Oder sogar auf dem for
Laufenden!
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
}
}
Aber solche Fälle sind äußerst selten. In den meisten Fällen erfolgt die Deklaration innerhalb der Methode. Also haben wir die Deklarationen ausgearbeitet und auch über die „Philosophie“ gesprochen :) Welche zusätzlichen Merkmale und Unterschiede haben lokale Klassen im Vergleich zu inneren Klassen? Ein Objekt einer lokalen Klasse kann nicht außerhalb der Methode oder des Blocks erstellt werden, in der es deklariert ist. Stellen Sie sich vor, wir benötigen eine generatePhoneNumber()
Methode, die eine zufällige Telefonnummer generiert und ein PhoneNumber
Objekt zurückgibt. In unserer aktuellen Situation können wir eine solche Methode nicht in unserer Validator-Klasse erstellen:
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() {
}
}
Ein weiteres wichtiges Merkmal lokaler Klassen ist die Möglichkeit, auf lokale Variablen und Methodenparameter zuzugreifen. Falls Sie es vergessen haben: Eine innerhalb einer Methode deklarierte Variable wird als „lokale“ Variable bezeichnet. String usCountryCode
Das heißt, wenn wir aus irgendeinem Grund eine lokale Variable innerhalb der Methode erstellen validatePhoneNumber()
, können wir über die lokale PhoneNumber
Klasse darauf zugreifen. Allerdings gibt es viele Feinheiten, die von der im Programm verwendeten Sprachversion abhängen. Zu Beginn der Lektion haben wir festgestellt, dass der Code für eines der Beispiele möglicherweise nicht in Java 7 kompiliert wird. Erinnern Sie sich? Betrachten wir nun die Gründe dafür :) In Java 7 kann eine lokale Klasse nur dann auf eine lokale Variable oder einen Methodenparameter zugreifen, wenn diese wie final
in der Methode deklariert sind:
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
}
Hier generiert der Compiler zwei Fehler. Und hier ist alles in Ordnung:
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
}
Jetzt wissen Sie, warum der Code vom Anfang der Lektion nicht kompiliert werden konnte: In Java 7 hat eine lokale Klasse nur Zugriff auf final
Methodenparameter und final
lokale Variablen. In Java 8 hat sich das Verhalten lokaler Klassen geändert. In dieser Version der Sprache hat eine lokale Klasse nicht nur Zugriff auf final
lokale Variablen und Parameter, sondern auch auf solche, die effective-final
. Effective-final
ist eine Variable, deren Wert sich seit der Initialisierung nicht geändert hat. In Java 8 können wir beispielsweise die usCountryCode
Variable problemlos auf der Konsole anzeigen, auch wenn dies nicht der Fall ist final
. Wichtig ist, dass sich sein Wert nicht ändert. Im folgenden Beispiel funktioniert alles wie es soll:
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
}
Wenn wir jedoch den Wert der Variablen unmittelbar nach der Initialisierung ändern, wird der Code nicht kompiliert.
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
usCountryCode = "+8";
class PhoneNumber {
public void printUsCountryCode() {
// Error!
System.out.println(usCountryCode);
}
}
// ...number validation code
}
Kein Wunder, dass eine lokale Klasse eine Unterart des Konzepts der inneren Klasse ist! Sie haben auch gemeinsame Merkmale. Eine lokale Klasse hat Zugriff auf alle (auch privaten) Felder und Methoden der äußeren Klasse: sowohl statische als auch nicht statische. Fügen wir beispielsweise ein statisches String phoneNumberRegex
Feld zu unserer Validator-Klasse hinzu:
public class PhoneNumberValidator {
private static String phoneNumberRegex = "[^0-9]";
public void validatePhoneNumber(String phoneNumber) {
class PhoneNumber {
// ......
}
}
}
Die Validierung wird mithilfe dieser statischen Variablen durchgeführt. Die Methode prüft, ob die übergebene Zeichenfolge Zeichen enthält, die nicht mit dem regulären Ausdruck „ [^0-9]
“ übereinstimmen (d. h. jedes Zeichen, das keine Ziffer von 0 bis 9 ist). Wir können von der lokalen PhoneNumber
Klasse aus leicht auf diese Variable zugreifen. Schreiben Sie zum Beispiel einen Getter:
public String getPhoneNumberRegex() {
return phoneNumberRegex;
}
Lokale Klassen ähneln inneren Klassen, da sie keine statischen Mitglieder definieren oder deklarieren können. Lokale Klassen in statischen Methoden können nur auf statische Mitglieder der einschließenden Klasse verweisen. Wenn Sie beispielsweise eine Variable (ein Feld) der umschließenden Klasse nicht als statisch definieren, generiert der Java-Compiler einen Fehler: „Eine nicht statische Variable kann nicht aus einem statischen Kontext referenziert werden.“ Lokale Klassen sind nicht statisch, da sie Zugriff auf Instanzmitglieder im umschließenden Block haben. Daher können sie die meisten Arten statischer Deklarationen nicht enthalten. Sie können eine Schnittstelle nicht innerhalb eines Blocks deklarieren: Schnittstellen sind von Natur aus statisch. Dieser Code lässt sich nicht kompilieren:
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
}
}
Wenn jedoch eine Schnittstelle innerhalb einer äußeren Klasse deklariert wird, PhoneNumber
kann die Klasse sie implementieren:
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
}
}
Statische Initialisierer (Initialisierungsblöcke) oder Schnittstellen können nicht in lokalen Klassen deklariert werden. Aber lokale Klassen können statische Mitglieder haben, vorausgesetzt, es handelt sich um konstante Variablen ( static final
). Und jetzt wisst ihr Bescheid über lokale Kurse, Leute! Wie Sie sehen können, unterscheiden sie sich stark von gewöhnlichen inneren Klassen. Wir mussten uns sogar mit den Funktionen bestimmter Versionen der Sprache befassen, um zu verstehen, wie sie funktionieren :) In der nächsten Lektion werden wir über anonyme innere Klassen sprechen – die letzte Gruppe verschachtelter Klassen. Viel Erfolg im Studium! :) :)
GO TO FULL VERSION