CodeGym /Java blogg /Slumpmässig /Inre klasser i en lokal metod
John Squirrels
Nivå
San Francisco

Inre klasser i en lokal metod

Publicerad i gruppen
Hej! Låt oss prata om en annan typ av kapslade klasser. Jag pratar om lokala klasser (metod-lokala inre klasser). Innan vi dyker in måste vi först komma ihåg deras plats i strukturen av kapslade klasser. Inre klasser i en lokal metod - 2Från vårt diagram kan vi se att lokala klasser är en underart av inre klasser, som vi talade om i detalj i tidigare material . Lokala klasser har dock ett antal viktiga egenskaper och skillnader från vanliga inre klasser. Huvudsaken är i deras deklaration: En lokal klass deklareras endast i ett kodblock. Oftast är denna deklaration inuti någon metod i den yttre klassen. Det kan till exempel se ut så här:

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
   }
}
VIKTIG!Om du har Java 7 installerat kommer den här koden inte att kompileras när den klistras in i IDEA. Vi kommer att prata om orsakerna till detta i slutet av lektionen. Kort sagt, hur lokala klasser fungerar är mycket beroende av språkversionen. Om den här koden inte kompilerar åt dig kan du antingen byta språkversionen i IDEA till Java 8, eller lägga till ordet finali metodparametern så att det ser ut så här: validatePhoneNumber(final String number). Efter det kommer allt att fungera. Detta är ett litet program som validerar telefonnummer. Dess validatePhoneNumber()metod tar en sträng som indata och avgör om det är ett telefonnummer. Och inom den här metoden förklarade vi vår lokala PhoneNumberklass. Du kan rimligtvis fråga varför. Varför exakt skulle vi deklarera en klass i en metod? Varför inte använda en vanlig inre klass? Det är sant att vi kunde ha gjort detPhoneNumberklass en inre klass. Men den slutliga lösningen beror på strukturen och syftet med ditt program. Låt oss komma ihåg vårt exempel från en lektion om inre klasser:

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!");
       }
   }
}
I den gjorde vi HandleBaren inre klass av cykeln. Vad är skillnaden? För det första är sättet klassen används på olika. Klassen HandleBari det andra exemplet är en mer komplex enhet än PhoneNumberklassen i det första exemplet. För det första HandleBarhar public rightoch leftmetoder (dessa är inte setters/getters). För det andra är det omöjligt att i förväg förutse var vi kan behöva det och dess yttre Bicycleklass. Det kan finnas dussintals olika platser och metoder, även i ett enda program. Men med PhoneNumberklassen är allt mycket enklare. Vårt program är väldigt enkelt. Den har bara ett syfte: att kontrollera om ett nummer är ett giltigt telefonnummer. I de flesta fall är vårPhoneNumberValidatorkommer inte ens att vara ett fristående program, utan snarare en del av auktoriseringslogiken för ett större program. Till exempel ber olika webbplatser ofta om ett telefonnummer när användare registrerar sig. Om du anger något nonsens istället för siffror kommer webbplatsen att rapportera ett fel: "Detta är inte ett telefonnummer!" Utvecklarna av en sådan webbplats (eller snarare dess användarauktoriseringsmekanism) kan inkludera något som liknar vårPhoneNumberValidatori sin kod. Vi har med andra ord en yttre klass med en metod, som kommer att användas på ett ställe i programmet och ingen annanstans. Och om den används kommer ingenting att förändras i den: en metod gör sitt jobb - och det är det. I det här fallet, eftersom all logik är samlad i en metod, kommer det att vara mycket bekvämare och mer korrekt att kapsla in ytterligare en klass där. Den har inga egna metoder förutom en getter och setter. Faktum är att vi bara behöver data från konstruktören. Det är inte involverat i andra metoder. Följaktligen finns det ingen anledning att ta information om den utanför den enda metod där den används. Vi gav också ett exempel där en lokal klass deklareras i en metod, men detta är inte det enda alternativet. Det kan enkelt deklareras i ett kodblock:

public class PhoneNumberValidator {
  
   {
       class PhoneNumber {

           private String phoneNumber;

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

   }

   public void validatePhoneNumber(String phoneNumber) {

      
       // ...number validation code
   }
}
Eller till och med i forslingan!

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
   }
}
Men sådana fall är extremt sällsynta. I de flesta fall kommer deklarationen att ske inom metoden. Så vi kom på deklarationer, och vi pratade också om "filosofin" :) Vilka ytterligare egenskaper och skillnader har lokala klasser i jämförelse med inre klasser? Ett objekt av en lokal klass kan inte skapas utanför metoden eller blocket där det deklareras. Föreställ dig att vi behöver en generatePhoneNumber()metod som genererar ett slumpmässigt telefonnummer och returnerar ett PhoneNumberobjekt. I vår nuvarande situation kan vi inte skapa en sådan metod i vår validatorklass:

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

   }

}
En annan viktig egenskap hos lokala klasser är möjligheten att komma åt lokala variabler och metodparametrar. Om du glömde, är en variabel som deklareras i en metod känd som en "lokal" variabel. Det vill säga, om vi skapar en lokal String usCountryCodevariabel inuti validatePhoneNumber()metoden av någon anledning, kan vi komma åt den från den lokala PhoneNumberklassen. Det finns dock en hel del finesser som beror på vilken version av språket som används i programmet. I början av lektionen noterade vi att koden för ett av exemplen kanske inte kompileras i Java 7, minns du? Låt oss nu överväga orsakerna till detta :) I Java 7 kan en lokal klass komma åt en lokal variabel eller metodparameter endast om de deklareras som finali metoden:

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
}
Här genererar kompilatorn två fel. Och allt är i sin ordning här:

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 vet du varför koden från början av lektionen inte skulle kompilera: i Java 7 har en lokal klass endast tillgång till finalmetodparametrar och finallokala variabler. I Java 8 har beteendet hos lokala klasser förändrats. I den här versionen av språket har en lokal klass tillgång inte bara till finallokala variabler och parametrar, utan även till de som är effective-final. Effective-finalär en variabel vars värde inte har ändrats sedan initieringen. Till exempel, i Java 8 kan vi enkelt visa usCountryCodevariabeln på konsolen, även om den inte är final. Det viktiga är att dess värde inte förändras. I följande exempel fungerar allt som det ska:

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
}
Men om vi ändrar variabelns värde direkt efter initialisering kommer koden inte att kompileras.

public void validatePhoneNumber(String number) {

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

    class PhoneNumber {

       public void printUsCountryCode() {

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

    }

   // ...number validation code
}
Inte konstigt att en lokal klass är en underart av begreppet inre klass! De har också gemensamma egenskaper. En lokal klass har tillgång till alla (även privata) fält och metoder för den yttre klassen: både statiska och icke-statiska. Låt oss till exempel lägga till ett statiskt String phoneNumberRegexfält i vår valideringsklass:

public class PhoneNumberValidator {

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

   public void validatePhoneNumber(String phoneNumber) {
       class PhoneNumber {
          
           // ......
       }
   }
}
Validering kommer att utföras med denna statiska variabel. Metoden kontrollerar om den skickade strängen innehåller tecken som inte matchar det reguljära uttrycket " " ( [^0-9]det vill säga alla tecken som inte är en siffra från 0 till 9). Vi kan enkelt komma åt denna variabel från den lokala PhoneNumberklassen. Skriv till exempel en getter:

public String getPhoneNumberRegex() {
  
   return phoneNumberRegex;
}
Lokala klasser liknar inre klasser, eftersom de inte kan definiera eller deklarera några statiska medlemmar. Lokala klasser i statiska metoder kan endast referera till statiska medlemmar av den omslutande klassen. Till exempel, om du inte definierar en variabel (fält) i den omslutande klassen som statisk, genererar Java-kompilatorn ett fel: "Icke-statisk variabel kan inte refereras från en statisk kontext." Lokala klasser är inte statiska, eftersom de har tillgång till instansmedlemmar i det omslutande blocket. Som ett resultat kan de inte innehålla de flesta typer av statiska deklarationer. Du kan inte deklarera ett gränssnitt inuti ett block: gränssnitt är i sig statiska. Denna kod kompilerar inte:

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
   }
}
Men om ett gränssnitt deklareras inuti en yttre klass, PhoneNumberkan klassen implementera det:

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
   }
}
Statiska initierare (initieringsblock) eller gränssnitt kan inte deklareras i lokala klasser. Men lokala klasser kan ha statiska medlemmar, förutsatt att de är konstanta variabler ( ) static final. Och nu vet ni om lokala klasser, gott folk! Som du kan se har de många skillnader från vanliga inre klasser. Vi var till och med tvungna att fördjupa oss i funktionerna i specifika versioner av språket för att förstå hur de fungerar :) I nästa lektion kommer vi att prata om anonyma inre klasser — den sista gruppen kapslade klasser. Lycka till i dina studier! :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION