CodeGym /Java-Blog /Random-DE /Innere Klassen in einer lokalen Methode
Autor
Oleksandr Miadelets
Head of Developers Team at CodeGym

Innere Klassen in einer lokalen Methode

Veröffentlicht in der Gruppe Random-DE
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 MaterialienInnere Klassen in einer lokalen Methode – 2 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 finalzum 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 PhoneNumberKlasse 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önnenPhoneNumberKlasse 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 HandleBareine innere Klasse des Fahrrads geschaffen. Was ist der Unterschied? Erstens ist die Art und Weise, wie die Klasse verwendet wird, unterschiedlich. Die HandleBarKlasse im zweiten Beispiel ist eine komplexere Entität als die PhoneNumberKlasse im ersten Beispiel. Erstens HandleBarverfügt es über öffentliche rightund leftMethoden (diese sind keine Setter/Getter). Zweitens ist es unmöglich, im Voraus vorherzusagen, wo wir es und seine äußere BicycleKlasse benötigen könnten. Selbst in einem einzigen Programm kann es Dutzende verschiedener Orte und Methoden geben. Aber mit der PhoneNumberKlasse 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 unserePhoneNumberValidatorwird 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 einbauenPhoneNumberValidatorin 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 forLaufenden!

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 PhoneNumberObjekt 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 usCountryCodeDas heißt, wenn wir aus irgendeinem Grund eine lokale Variable innerhalb der Methode erstellen validatePhoneNumber(), können wir über die lokale PhoneNumberKlasse 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 finalin 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 finalMethodenparameter und finallokale 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 finallokale Variablen und Parameter, sondern auch auf solche, die effective-final. Effective-finalist eine Variable, deren Wert sich seit der Initialisierung nicht geändert hat. In Java 8 können wir beispielsweise die usCountryCodeVariable 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 phoneNumberRegexFeld 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 PhoneNumberKlasse 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, PhoneNumberkann 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! :) :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION