Kotlin From Scratch Avancerade egenskaper och klasser

Kotlin är ett modernt programmeringsspråk som kompilerar till Java bytecode. Det är gratis och öppen källkod, och lovar att göra kodning för Android ännu roligare.  

I den föregående artikeln lärde du dig om klasser och objekt i Kotlin. I den här handledningen fortsätter vi att lära oss mer om egenskaper och även titta på avancerade klasser i Kotlin genom att utforska följande:

  • seninitierade egenskaper
  • inline egenskaper 
  • förlängningsegenskaper
  • data, enum, kapslade och förseglade klasser

1. Senast initierade egenskaper

Vi kan deklarera en icke-null egendom i Kotlin som sen-initialiseras. Det betyder att en icke-null-egenskap inte kommer att initieras vid deklarationstiden med en värde-faktisk initialisering kommer inte att hända via någon konstruktör-men i stället kommer den att initieras sen med en metod eller beroendeinjektion.

Låt oss titta på ett exempel för att förstå denna unika egenskapsmodifierare. 

class Presenter privat varförvar: Repository? = null roligt initRepository (repo: Repository): Enhet this.repository = repo klass Repository fun saveAmount (mängd: Double)  

I koden ovan förklarade vi en mutable nullable förvaret egendom som är av typ Repository-inuti klassen Presentatör-och vi initierade sedan denna egenskap till null under deklarationen. Vi har en metod initRepository () i Presentatör klass som återinitierar den här egenskapen senare med en faktisk Repository exempel. Observera att den här egenskapen också kan tilldelas ett värde med hjälp av en beroendeinjektor som Dagger.     

Nu för oss att åberopa metoder eller egenskaper på detta förvaret egendom, vi måste göra en nollkontroll eller använd säker samtal operatör. Varför? Eftersom det förvaret egendom är av nollställd typ (Repository?). (Om du behöver en uppfriskning om nollställbarhet i Kotlin, vänligen besök Nullability, Loops och Conditions).

// Inside Presenter klass roligt spara (belopp: Dubbel) depot?. SaveAmount (mängd)

För att undvika att göra nollkontroller varje gång vi behöver anropa en fastighets metod kan vi markera den egenskapen med lateinit modifierare - det betyder att vi har förklarat egenskapen (som är en förekomst av en annan klass) som sen-initialiseras (vilket innebär att egendomen kommer att initialiseras senare).  

class Presenter privat lateinit var repository: Repository // ...

Så länge vi väntar tills fastigheten har fått ett värde, är vi säkra på att komma åt fastighetsmetoderna utan att göra några nollkontroller. Egenskapsinitialiseringen kan ske antingen i en setter-metod eller genom beroendeinsprutning. 

repository.saveAmount (mängd)

Observera att om vi försöker komma åt metoder för fastigheten innan den har initierats får vi en kotlin.UninitializedPropertyAccessException i stället för en NullPointerException. I det här fallet kommer undantagsmeddelandet att vara "lateinit property repository har inte initialiserats". 

Observera också följande begränsningar som ställs in när fördröjning av en egenskapsinitiering med lateinit:

  • Den måste vara mutabel (deklarerad med var).
  • Egenskapstypen kan inte vara en primitiv typ, till exempel, int, Dubbel, Flyta, och så vidare. 
  • Fastigheten kan inte ha en anpassad getter eller setter.

2. Inline Properties

I avancerade funktioner introducerade jag i kö modifierare för högre orderfunktioner-detta hjälper till att optimera alla högre orderfunktioner som accepterar en lambda som en parameter. 

I Kotlin kan vi också använda detta i kö modifierare på egenskaper. Genom att använda denna modifierare optimeras tillgången till fastigheten.

Låt oss se ett praktiskt exempel. 

klass Student val nickName: String get () println ("Nick name retrieved") returnera "koloCoder" rolig huvud (args: Array) val student = Student () print (student.nickName)

I koden ovan har vi en normal egendom, ÖKNAMN, det har inte den i kö modifieringsmedel. Om vi ​​dekompilerar kodavsnittet använder du Visa Kotlin Bytecode funktionen (om du är i IntelliJ IDEA eller Android Studio, använd Verktyg > Kotlin > Visa Kotlin Bytecode) ser vi följande Java-kod:

offentliga slutklass Student @NotNull public final String getNickName () String var1 = "Nicknamn hämtat"; System.out.println (var1); returnera "koloCoder";  offentliga slutklass InlineFunctionKt public static final void main (@NotNull String [] args) Intrinsics.checkParameterIsNotNull (args, "args"); Student student = ny student (); String var2 = student.getNickName (); System.out.print (var2); 

I den genererade Java-koden ovan (vissa element i den genererade koden avlägsnades för korthetens skull) kan du se det inuti main () metod skapade kompilatorn en Studerande objekt, kallas getNickName () metod och tryckte sedan ut returvärdet.  

Låt oss nu ange egenskapen som i kö istället och jämföra den genererade bytekoden.

// ... inline val nickName: String // ... 

Vi sätter bara in i kö modifier före variabelmodifieraren: var eller val. Här är bytekoden som genereras för denna inline-egenskap:

// ... offentliga statiska slutgiltiga huvud (@NotNull String [] args) Intrinsics.checkParameterIsNotNull (args, "args"); Student student = ny student (); String var3 = "Nicknamn hämtat"; System.out.println (var3); String var2 = "koloCoder"; System.out.print (var2);  // ... 

Återigen togs en del kod bort, men det viktigaste att notera är att main () metod. Kompilatorn har kopierat fastigheten skaffa sig() funktionskroppen och klistrade in den i samtalstjänsten (denna mekanism liknar inline-funktioner). 

Vår kod har optimerats på grund av att det inte finns något behov av att skapa ett objekt och anropa egenskapen getter-metoden. Men, som diskuteras i inline-funktionerna, hade vi en större bytecode än tidigare - så använd försiktigt. 

Observera också att den här mekanismen fungerar för egenskaper som inte har ett backfält (kom ihåg, ett bakfält är bara ett fält som används av egenskaper när du vill ändra eller använda fältdata). 

3. Förlängningsegenskaper 

I avancerade funktioner diskuterade jag också förlängningsfunktioner, vilket ger oss möjligheten att utöka en klass med ny funktionalitet utan att behöva ärva från den klassen. Kotlin ger också en liknande mekanism för egenskaper som kallas förlängningsegenskaper

val String.upperCaseFirstLetter: String get () = this.substring (0, 1) .toUpperCase (). plus (this.substring (1))

I posten Avancerade funktioner definierade vi en uppercaseFirstLetter () förlängningsfunktion med mottagartyp Sträng. Här har vi konverterat den till en förstoringsegenskap på toppnivå istället. Observera att du måste definiera en getter-metod på din egendom för att detta ska fungera. 

Så med denna nya kunskap om förlängningsegenskaper kommer du veta att om du någonsin önskade att en klass borde ha en egendom som inte var tillgänglig, kan du skapa en förlängningsegenskap för den klassen. 

4. Dataklasser

Låt oss börja med en typisk Java-klass eller POJO (Vanligt gammalt Java-objekt). 

offentlig klass BlogPost privat sista String title; privat slutlig URI url; privat sista strängbeskrivning; privat slutdatum datum publicera datum // ... konstruktör som inte ingår i korthetens skull @Override public boolean equals (Object o) if (this == o) return true; om (o == null || getClass ()! = o.getClass ()) returnera false; BlogPost blogPost = (BlogPost) o; om (titel! = null?! title.equals (blogPost.title): blogPost.title! = null) returnera false; om (url! = null?! url.equals (blogPost.url): blogPost.url! = null) returnera false; om (beskrivning! = null?! description.equals (blogPost.description): blogPost.description! = null) returnera false; returnera publishDate! = null? publishDate.equals (blogPost.publishDate): blogPost.publishDate == null;  @Override public int hashCode () int result = title! = Null? title.hashCode (): 0; resultat = 31 * resultat + (url! = null? url.hashCode (): 0); resultat = 31 * resultat + (beskrivning! = null? description.hashCode (): 0); result = 31 * result + (publishDate! = null? publishDate.hashCode (): 0); returresultat;  @Override public String toString () returnera "BlogPost " + "title = '" + title +' \ "+", url = "+ url +", + beskrivning + "\" + ", publishDate =" + publicera datum + '';  // ... setters och getters ignoreras också för korthetens skull

Som du kan se behöver vi explicit koda klassegenskaper accessorer: getter och setter, liksom hash-kodär lika med, och att stränga metoder (även om IntelliJ IDEA, Android Studio eller AutoValue-biblioteket kan hjälpa oss att generera dem). Vi ser denna typ av boilerplate-kod mestadels i datalagret för ett typiskt Java-projekt. (Jag tog bort fälttillträdarna och konstruktören för korthetens skull). 

Den snygga saken är att Kotlins team gav oss med data modifierare för klasser för att eliminera skrivning av dessa panna.

Låt oss nu skriva den föregående koden i Kotlin istället.

dataklass BlogPost (var titel: String, var url: URI, var description: String, var publishDate: Date)

Grymt bra! Vi anger bara data modifierare före klass nyckelord för att skapa en dataklass-precis som vad vi gjorde i vårt inlägg Kotlin klass ovan. Nu den är lika medhash-kodatt strängakopia, och flera komponentmetoder skapas under huven för oss. Observera att en dataklass kan förlänga andra klasser (detta är en ny egenskap hos Kotlin 1.1). 

De är lika med Metod

Denna metod jämför två objekt för jämlikhet och returnerar sant om de är lika eller falska på annat sätt. Med andra ord jämförs det om de två klassinstanserna innehåller samma data. 

student.equals (student3) // med == i Kotlin student == student3 // samma som att använda lika med ()

I Kotlin, med hjälp av likhetsoperatören == kommer att ringa är lika med metod bakom kulisserna.

De hash-kod Metod 

Denna metod returnerar ett heltal värde som används för snabb lagring och hämtning av data lagrad i en hashbaserad samlingsdatastruktur, till exempel i HashMap och HashSet insamlingstyper.  

De att stränga Metod

Denna metod returnerar a Sträng representation av ett objekt. 

dataklass Person (var firstName: String, var lastName: String) val person = Person ("Chike", "Mgbemena") println (person) // utskrifter "Person (firstName = Chike, lastName = Mgbemena)"

Genom att bara ringa klassinstansen får vi ett strängobjekt som returneras till oss-Kotlin ringer objektet att stränga() under huven för oss. Men om vi inte sätter data sökord i, se vad vår objektsträngrepresentation skulle vara: 

com.chike.kotlin.classes.Person@2f0e140b

Mycket mindre informativ!

De kopia Metod

Med den här metoden kan vi skapa en ny instans av ett objekt med alla samma egenskapsvärden. Med andra ord skapar den en kopia av objektet. 

val person1 = Person ("Chike", "Mgbemena") println (person1) // Person (firstName = Chike, lastName = Mgbemena) val person2 = person1.copy () println (person2) // Person (firstName = Chike, LastName = Mgbemena)

En cool sak om kopia Metoden i Kotlin är möjligheten att ändra egenskaper under kopiering. 

val person3 = person1.copy (lastName = "Onu") println (person3) // Person3 (firstName = Chike, lastName = Onu)

Om du är en Java-kodare liknar den här metoden klona() metod du redan är bekant med. Men Kotlin kopia Metoden har mer kraftfulla funktioner. 

Destruktiva förklaringen

Person klass, har vi också två metoder som automatiskt genereras för oss av kompilatorn på grund av data sökord placerat i klassen. Dessa två metoder är prefixade med "komponent", följt av ett nummer suffix: COMPONENT1 ()COMPONENT2 (). Var och en av dessa metoder representerar de enskilda egenskaperna hos typen. Observera att suffixet motsvarar ordningen för de egenskaper som deklarerats i den primära konstruktören.

Så, i vårt exempel kallar vi COMPONENT1 () kommer att returnera förnamnet och ringa COMPONENT2 () kommer att returnera efternamnet.

println (person3.component1 ()) // Chike println (person3.component2 ()) // Onu

Att ringa fastigheterna med denna stil är svår att förstå och läsa, men så kallar egendomsnamnet uttryckligen mycket bättre. Dessa implicita skapade egenskaper har dock en mycket användbar avsikt: de låter oss göra en destruktiv deklaration, där vi kan tilldela varje komponent till en lokal variabel.

val (firstName, lastName) = Person ("Angelina", "Jolie") println (firstName + "" + efternamn) // Angelina Jolie

Det vi har gjort här är att direkt tilldela de första och andra egenskaperna (förnamn och efternamn) av Person skriv till variablerna förnamn och efternamn respektive. Jag diskuterade också denna mekanism som kallas destruktiva deklarationen i den sista delen av paketet Paket och grundläggande funktioner. 

5. Nested Classes

I posten Fler kul med funktioner sa jag till dig att Kotlin har stöd för lokala eller kapslade funktioner, en funktion som deklareras i en annan funktion. Tja, Kotlin stödjer också på samma sätt kapslade klasser - en klass skapad i en annan klass. 

klass OuterClass class NestedClass fun nestedClassFunc () 

Vi kallar till och med den nestade klassens offentliga funktioner som ses nedan - en kapslad klass i Kotlin motsvarar a statisk nestad klass i Java. Observera att kapslade klasser inte kan lagra en referens till sin yttre klass. 

val nestedClass = OuterClass.NestedClass () nestedClass.nestedClassFunc ()

Vi kan också ställa in den nestade klassen som privat. Det betyder att vi bara kan skapa en instans av NestedClass inom ramen för OuterClass

Inre klass

Inre klasser kan å andra sidan referera till den yttre klassen som deklarerades. För att skapa en inre klass placerar vi inre sökord före klass sökord i en kapslad klass. 

klass OuterClass () val oCPropt: String = "Yo" inre klass InnerClass roligt innerClassFunc () val outerClass = this @ OuterClass-utskrift (outerClass.oCPropt) // utskrifter "Yo"

Här hänvisar vi till OuterClass från InnerClass genom att använda  detta @ OuterClass.

6. Enum-klasser

Enumtypen förklarar en uppsättning konstanter representerade av identifierare. Denna speciella typ av klass skapas av sökordet enum som anges före klass nyckelord. 

enum klass Land NIGERIA, GHANA, CANADA

För att hämta ett enumvärde baserat på dess namn (precis som i Java) gör vi det här:

Country.valueOf ( "NIGERIA")

Eller vi kan använda Kotlin enumValueOf() hjälparmetod för att få tillgång till konstanter på ett generiskt sätt:

enumValueOf("NIGERIA")

Vi kan också få alla värden (som för en Java enum) så här:

Country.values ​​()

Slutligen kan vi använda Kotlin EnumValues() hjälparmetod för att få alla enumposter på ett generiskt sätt:

EnumValues()

Detta returnerar en array som innehåller enum-posterna.  

Enum Constructors

Precis som en normal klass, den enum typen kan ha sin egen konstruktör med egenskaper associerade med varje enumkonstant. 

enum klass Land (val callingCode: Int) NIGERIA (234), USA (1), GHANA (233)

I Land enum typ primära konstruktör, definierade vi den oföränderliga egenskapen callingCodes för varje enumkonstant. I var och en av konstanterna passerade vi ett argument till konstruktören. 

Vi kan sedan få tillgång till fastighetsegenskapen så här:

val land = Country.NIGERIA print (country.callingCode) // 234

7. Förseglade klasser

En förseglad klass i Kotlin är en abstrakt klass (du tänker aldrig skapa objekt från den) vilka andra klasser kan utöka. Dessa underklasser definieras inuti den förseglade klasskroppen - i samma fil. Eftersom alla dessa underklasser definieras inuti den förseglade klasskroppen kan vi känna till alla möjliga underklasser genom att bara se filen. 

Låt oss se ett praktiskt exempel. 

// shape.kt förseglad klass Formklass Cirkel: Form () Klass Triangel: Form () Klass Rektangel: Form ()

För att deklarera en klass som förseglad lägger vi in sluten modifierare före klass modifierare i klassdeklarationen header-i vårt fall, förklarade vi Form klass som sluten. En förseglad klass är ofullständig utan dess underklasser - precis som en typisk abstrakt klass - så vi måste deklarera de enskilda underklassen inuti samma fil (shape.kt I detta fall). Observera att du inte kan definiera en underklass av en förseglad klass från en annan fil. 

I vår kod ovan har vi angett att Form klassen kan endast förlängas av klasserna CirkelTriangel, och Rektangel.

Förseglade klasser i Kotlin har följande tilläggsregler:

  • Vi kan lägga till modifieraren abstrakt till en förseglad klass, men det här är överflödigt eftersom förseglade klasser är abstrakta som standard.
  • Förseglade klasser kan inte ha öppna eller slutlig modifieringsmedel. 
  • Vi är också fria att deklarera dataklasser och objekt som underklasser till en förseglad klass (de måste fortfarande deklareras i samma fil). 
  • Förseglade klasser får inte ha offentliga byggare - deras konstruktörer är privata som standard. 

Klasser som sträcker sig underklasser av en förseglad klass kan placeras antingen i samma fil eller annan fil. Den förseglade klassens underklass måste markeras med öppna modifierare (du lär dig mer om arv i Kotlin i nästa post). 

// employee.kt förseglad klass Medarbetare öppen klass Artist: Medarbetare () // musician.kt class Musiker: Artist ()

En förseglad klass och dess underklasser är verkligen praktiska i en när uttryck. Till exempel:

roligt vadIsIt (form: Shape) = när (form) är Circle -> println ("En cirkel") är Triangle -> println ("En triangel") är rektangel -> println ("En rektangel")

Här är kompilatorn smart för att vi ska täcka allt som är möjligt när fall. Det betyder att det inte finns något behov av att lägga till annan klausul. 

Om vi ​​skulle göra följande istället:

roligt vadIsIt (form: Shape) = när (form) är Circle -> println ("En cirkel") är Triangle -> println ("A triangle")

Koden skulle inte sammanställas, eftersom vi inte har inkluderat alla möjliga fall. Vi skulle ha följande fel:

Kotlin: "när" uttryck måste vara uttömmande, lägg till nödvändig "är rektangel" gren eller "annars" gren istället.

Så vi kunde antingen inkludera är rektangel fall eller inkludera annan klausul att slutföra när uttryck. 

Slutsats

I denna handledning lärde du dig mer om kurser i Kotlin. Vi omfattade följande om klassegenskaper:

  • sen initiering
  • inline egenskaper 
  • förlängningsegenskaper

Du lärde dig också om några coola och avancerade klasser som data, enum, nestade och förseglade klasser. I nästa handledning i Kotlin From Scratch-serien introduceras gränssnitt och arv i Kotlin. Ses snart!

För att lära mig mer om Kotlins språket rekommenderar jag att du besöker Kotlins dokumentation. Eller kolla in några av våra andra Android App-utvecklingsposter här på Envato Tuts!