CodeGym /Java Blog /Willekeurig /Innerlijke klassen in een lokale methode
John Squirrels
Niveau 41
San Francisco

Innerlijke klassen in een lokale methode

Gepubliceerd in de groep Willekeurig
Hoi! Laten we het hebben over een ander soort geneste klassen. Ik heb het over lokale klassen (methode-lokale innerlijke klassen). Voordat we erin duiken, moeten we eerst hun plaats in de structuur van geneste klassen onthouden. Innerlijke klassen in een lokale methode - 2Uit ons diagram kunnen we zien dat lokale klassen een ondersoort zijn van innerlijke klassen, waarover we in eerdere materialen in detail hebben gesproken . Lokale klassen hebben echter een aantal belangrijke kenmerken en verschillen met gewone innerlijke klassen. Het belangrijkste zit in hun declaratie: een lokale klasse wordt alleen gedeclareerd in een codeblok. Meestal bevindt deze declaratie zich in een methode van de buitenste klasse. Het kan er bijvoorbeeld zo uitzien:

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
   }
}
BELANGRIJK!Als u Java 7 hebt geïnstalleerd, wordt deze code niet gecompileerd wanneer deze in IDEA wordt geplakt. De redenen hiervoor bespreken we aan het einde van de les. Kortom, hoe lokale lessen werken, is sterk afhankelijk van de taalversie. Als deze code niet voor u compileert, kunt u de taalversie in IDEA wijzigen in Java 8, of het woord toevoegen finalaan de methodeparameter zodat het er zo uitziet: validatePhoneNumber(final String number). Daarna zal alles werken. Dit is een klein programma dat telefoonnummers valideert. De validatePhoneNumber()methode neemt een string als invoer en bepaalt of het een telefoonnummer is. En binnen deze methode hebben we onze lokale PhoneNumberklasse verklaard. Je zou redelijkerwijs kunnen vragen waarom. Waarom zouden we precies een klasse binnen een methode declareren? Waarom geen gewone innerlijke klasse gebruiken? Toegegeven, we hadden dePhoneNumberklasse een innerlijke klasse. Maar de uiteindelijke oplossing hangt af van de structuur en het doel van uw programma. Laten we ons voorbeeld uit een les over innerlijke klassen terughalen:

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!");
       }
   }
}
Daarin hebben we HandleBareen binnenklasse van de fiets gemaakt. Wat is het verschil? Allereerst is de manier waarop de klasse wordt gebruikt anders. De HandleBarklasse in het tweede voorbeeld is een complexere entiteit dan de PhoneNumberklasse in het eerste voorbeeld. Ten eerste HandleBarheeft public righten leftmethods (dit zijn geen setters/getters). Ten tweede is het onmogelijk om van tevoren te voorspellen waar we het nodig hebben en wat de buitenste Bicycleklasse ervan is. Er kunnen tientallen verschillende plaatsen en methoden zijn, zelfs in een enkel programma. Maar met de PhoneNumberklas is alles veel eenvoudiger. Ons programma is heel eenvoudig. Het heeft maar één doel: controleren of een nummer een geldig telefoonnummer is. In de meeste gevallen onzePhoneNumberValidatorzal niet eens een op zichzelf staand programma zijn, maar eerder een onderdeel van de autorisatielogica voor een groter programma. Verschillende websites vragen bijvoorbeeld vaak om een ​​telefoonnummer wanneer gebruikers zich aanmelden. Als u onzin invoert in plaats van cijfers, meldt de website een foutmelding: "Dit is geen telefoonnummer!" De ontwikkelaars van een dergelijke website (of beter gezegd, het autorisatiemechanisme van de gebruiker) kunnen iets soortgelijks als de onze bevattenPhoneNumberValidatorin hun code. Met andere woorden, we hebben één buitenste klasse met één methode, die op één plaats in het programma zal worden gebruikt en nergens anders. En als het wordt gebruikt, verandert er niets aan: één methode doet zijn werk - en dat is alles. In dit geval, omdat alle logica in één methode is verzameld, zal het veel handiger en juister zijn om daar een extra klasse in te kapselen. Het heeft geen eigen methoden behalve een getter en setter. In feite hebben we alleen gegevens van de constructor nodig. Het is niet betrokken bij andere methoden. Dienovereenkomstig is er geen reden om informatie erover mee te nemen buiten de enige methode waarop het wordt gebruikt. We hebben ook een voorbeeld gegeven waarin een lokale klasse wordt gedeclareerd in een methode, maar dit is niet de enige optie. Het kan eenvoudig worden gedeclareerd in een codeblok:

public class PhoneNumberValidator {
  
   {
       class PhoneNumber {

           private String phoneNumber;

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

   }

   public void validatePhoneNumber(String phoneNumber) {

      
       // ...number validation code
   }
}
Of zelfs in de forlus!

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
   }
}
Maar dergelijke gevallen zijn uiterst zeldzaam. In de meeste gevallen gebeurt de declaratie binnen de methode. Dus we hebben verklaringen bedacht en we hebben het ook gehad over de "filosofie" :) Welke extra kenmerken en verschillen hebben lokale klassen in vergelijking met innerlijke klassen? Een object van een lokale klasse kan niet worden gemaakt buiten de methode of het blok waarin het is gedeclareerd. Stel je voor dat we een generatePhoneNumber()methode nodig hebben die een willekeurig telefoonnummer genereert en een PhoneNumberobject retourneert. In onze huidige situatie kunnen we zo'n methode niet maken in onze validatorklasse:

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() {

   }

}
Een ander belangrijk kenmerk van lokale klassen is de mogelijkheid om toegang te krijgen tot lokale variabelen en methodeparameters. Voor het geval je het vergeten bent, een variabele die binnen een methode gedeclareerd is, staat bekend als een "lokale" variabele. Dat wil zeggen, als we om de een of andere reden een lokale String usCountryCodevariabele binnen de validatePhoneNumber()methode maken, hebben we er toegang toe vanuit de lokale PhoneNumberklasse. Er zijn echter veel subtiliteiten die afhangen van de versie van de taal die in het programma wordt gebruikt. Aan het begin van de les merkten we op dat de code voor een van de voorbeelden misschien niet compileert in Java 7, weet je nog? Laten we nu eens kijken naar de redenen hiervoor :) In Java 7 heeft een lokale klasse alleen toegang tot een lokale variabele of methodeparameter als ze zijn gedeclareerd zoals finalin de methode:

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 genereert de compiler twee fouten. En hier is alles in orde:

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
}
Nu weet je waarom de code vanaf het begin van de les niet zou compileren: in Java 7 heeft een lokale klasse alleen toegang tot finalmethodeparameters en finallokale variabelen. In Java 8 is het gedrag van lokale klassen veranderd. In deze versie van de taal heeft een lokale klasse niet alleen toegang tot finallokale variabelen en parameters, maar ook tot de effective-final. Effective-finalis een variabele waarvan de waarde niet is veranderd sinds de initialisatie. In Java 8 kunnen we bijvoorbeeld eenvoudig de usCountryCodevariabele op de console weergeven, zelfs als dat niet het geval is final. Het belangrijkste is dat de waarde ervan niet verandert. In het volgende voorbeeld werkt alles zoals het hoort:

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
}
Maar als we de waarde van de variabele onmiddellijk na initialisatie wijzigen, zal de code niet compileren.

public void validatePhoneNumber(String number) {

  String usCountryCode = "+1";
  usCountryCode = "+8";

    class PhoneNumber {

       public void printUsCountryCode() {

           // Error!
           System.out.println(usCountryCode);
       }

    }

   // ...number validation code
}
Geen wonder dat een lokale klasse een ondersoort is van het concept van de innerlijke klasse! Ze hebben ook gemeenschappelijke kenmerken. Een lokale klasse heeft toegang tot alle (zelfs private) velden en methoden van de buitenste klasse: zowel statisch als niet-statisch. Laten we bijvoorbeeld een statisch String phoneNumberRegexveld toevoegen aan onze validatorklasse:

public class PhoneNumberValidator {

   private static String phoneNumberRegex = "[^0-9]";

   public void validatePhoneNumber(String phoneNumber) {
       class PhoneNumber {
          
           // ......
       }
   }
}
Validatie wordt uitgevoerd met behulp van deze statische variabele. De methode controleert of de doorgegeven tekenreeks tekens bevat die niet overeenkomen met de reguliere expressie " [^0-9]" (dat wil zeggen elk teken dat geen cijfer van 0 tot 9 is). We hebben eenvoudig toegang tot deze variabele vanuit de lokale PhoneNumberklasse. Schrijf bijvoorbeeld een getter:

public String getPhoneNumberRegex() {
  
   return phoneNumberRegex;
}
Lokale klassen zijn vergelijkbaar met innerlijke klassen, omdat ze geen statische leden kunnen definiëren of declareren. Lokale klassen in statische methoden kunnen alleen verwijzen naar statische leden van de omringende klasse. Als u bijvoorbeeld een variabele (veld) van de omsluitende klasse niet als statisch definieert, genereert de Java-compiler een fout: "Er kan niet naar een niet-statische variabele worden verwezen vanuit een statische context." Lokale klassen zijn niet statisch, omdat ze toegang hebben tot instantieleden in het omsluitende blok. Als gevolg hiervan kunnen ze de meeste soorten statische declaraties niet bevatten. U kunt een interface binnen een blok niet declareren: interfaces zijn inherent statisch. Deze code compileert niet:

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
   }
}
Maar als een interface binnen een buitenste klasse wordt gedeclareerd, PhoneNumberkan de klasse deze implementeren:

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 initializers (initialisatieblokken) of interfaces kunnen niet worden gedeclareerd in lokale klassen. Maar lokale klassen kunnen statische leden hebben, op voorwaarde dat het constante variabelen zijn ( static final). En nu weet je over lokale lessen, mensen! Zoals je kunt zien, hebben ze veel verschillen met gewone innerlijke klassen. We moesten ons zelfs verdiepen in de kenmerken van specifieke versies van de taal om te begrijpen hoe ze werken :) In de volgende les zullen we het hebben over anonieme innerlijke klassen — de laatste groep geneste klassen. Veel succes met je studie! :)
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION