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:
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
:
var
).int
, Dubbel
, Flyta
, och så vidare. 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).
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.
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 med
, hash-kod
, att stränga
, kopia
, 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).
är lika med
MetodDenna 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.
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.
att stränga
MetodDenna 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!
kopia
MetodMed 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.
I 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.
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 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
.
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.
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
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 Cirkel
, Triangel
, och Rektangel
.
Förseglade klasser i Kotlin har följande tilläggsregler:
abstrakt
till en förseglad klass, men det här är överflödigt eftersom förseglade klasser är abstrakta som standard.öppna
eller slutlig
modifieringsmedel. 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.
I denna handledning lärde du dig mer om kurser i Kotlin. Vi omfattade följande om klassegenskaper:
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!