Kotlin is een moderne programmeertaal die compileert met Java bytecode. Het is gratis en open source en belooft om het coderen voor Android nog leuker te maken.
In het vorige artikel leerde je over klassen en objecten in Kotlin. In deze zelfstudie blijven we meer over eigenschappen leren en bekijken we ook geavanceerde typen klassen in Kotlin door het volgende te verkennen:
We kunnen een niet-nul-eigenschap in Kotlin als verklaren late-geïnitialiseerd. Dit betekent dat een niet-nul-eigenschap niet zal worden geïnitialiseerd op het tijdstip van aangifte, met een waarde-daadwerkelijke initialisatie zal niet gebeuren via een constructor, maar in plaats daarvan zal het te laat worden geïnitialiseerd door een methode of afhankelijkheidsinjectie.
Laten we een voorbeeld bekijken om deze unieke eigenschapmodifier te begrijpen.
class Presenter private var repository: Repository? = null fun initRepository (repo: Repository): Unit this.repository = repo class Repository fun saveAmount (amount: Double)
In de bovenstaande code hebben we een veranderlijk nullabel opgegeven bewaarplaats
eigenschap die van het type is bewaarplaats
-in de klas Presentator
-en we hebben deze eigenschap vervolgens tijdens de aangifte op nul gezet. We hebben een methode initRepository ()
in de Presentator
klas die deze eigenschap later opnieuw initialiseert met een echte bewaarplaats
aanleg. Merk op dat deze eigenschap ook een waarde kan worden toegewezen met behulp van een afhankelijkheidsinjector zoals Dagger.
Nu kunnen we hier methoden of eigenschappen aanroepen bewaarplaats
property, moeten we een nulcontrole uitvoeren of de operator voor veilige oproepen gebruiken. Waarom? Omdat het bewaarplaats
eigendom is van nullable type (bewaarplaats?
). (Als je een opfriscursus over nullability in Kotlin nodig hebt, bezoek dan Nullability, Loops en voorwaarden).
// Inside Presenter class fun save (aantal: Double) repository? .SaveAmount (amount)
Om te voorkomen dat elke keer dat we de methode van een accommodatie moeten gebruiken nulcontroles moeten uitvoeren, kunnen we die eigenschap markeren met de lateinit
modifier - dit betekent dat we die eigenschap (die een instantie van een andere klasse is) hebben gedeclareerd als late-geïnitialiseerd (dit betekent dat de eigenschap later wordt geïnitialiseerd).
class Presenter private lateinit var repository: Repository // ...
Nu, zolang we wachten totdat het eigendom een waarde heeft gekregen, zijn we veilig om toegang te krijgen tot de methoden van de accommodatie zonder enige nulcontrole uit te voeren. De initialisatie van de eigenschap kan plaatsvinden in een settermethode of door injectie van afhankelijkheid.
repository.saveAmount (hoeveelheid)
Houd er rekening mee dat als we toegang proberen te krijgen tot de methoden van de property voordat deze is geïnitialiseerd, we een kotlin.UninitializedPropertyAccessException
inplaats van een NullPointerException
. In dit geval is het uitzonderingsbericht "lateinit property repository is niet geïnitialiseerd".
Houd ook rekening met de volgende beperkingen bij het vertragen van een eigenschap-initialisatie met lateinit
:
var
).Int
, Dubbele
, Vlotter
, enzovoorts. In Advanced Functions heb ik het in lijn
modifier voor hogere-orde functies - dit helpt bij het optimaliseren van alle hogere-orde functies die een lambda als een parameter accepteren.
In Kotlin kunnen we dit ook gebruiken in lijn
modifier op eigenschappen. Als u deze modifier gebruikt, wordt de toegang tot de property geoptimaliseerd.
Laten we een praktisch voorbeeld bekijken.
class Student val nickName: String get () println ("Nick name retrieved") geeft "koloCoder" terug fun main (args: Array) val student = Student () print (student.nickName)
In de bovenstaande code hebben we een normale eigenschap, bijnaam
, dat heeft niet de in lijn
modifier. Als we het codefragment decompileren, gebruiken we het Toon Kotlin Bytecode functie gebruiken (als u in IntelliJ IDEA of Android Studio bent Hulpmiddelen > Kotlin > Toon Kotlin Bytecode), zullen we de volgende Java-code zien:
openbare laatste klas Student @NotNull publieke finale String getNickName () String var1 = "Nick name retrieved"; System.out.println (Var1); terug "koloCoder"; openbare eindklasse InlineFunctionKt public static final void main (@NotNull String [] args) Intrinsics.checkParameterIsNotNull (args, "args"); Student student = nieuwe student (); String var2 = student.getNickName (); System.out.print (var2);
In de gegenereerde Java-code hierboven (sommige elementen van de gegenereerde code zijn omwille van de eenvoud verwijderd), kunt u dat binnen de hoofd()
methode die de compiler heeft gemaakt Student
object, genaamd de getNickName ()
methode en drukte vervolgens de retourwaarde ervan af.
Laten we nu de eigenschap als specificeren in lijn
in plaats daarvan en vergelijk de gegenereerde bytecode.
// ... inline val nickName: String // ...
We voegen gewoon de in lijn
modifier voor de variabele modifier: var
of val
. Hier is de bytecode gegenereerd voor deze inline-eigenschap:
// ... public static final void main (@NotNull String [] args) Intrinsics.checkParameterIsNotNull (args, "args"); Student student = nieuwe student (); String var3 = "Bijnaam opgehaald"; System.out.println (var3); String var2 = "koloCoder"; System.out.print (var2); // ...
Nogmaals, sommige code is verwijderd, maar het belangrijkste om op te merken is het hoofd()
methode. De compiler heeft de eigenschap gekopieerd krijgen()
function body en plakte het in de call-site (dit mechanisme is vergelijkbaar met inline-functies).
Onze code is geoptimaliseerd omdat het niet nodig is om een object te maken en de methode van de eigenschap getter te bellen. Maar zoals besproken in de inline functions-post, zouden we een grotere bytecode hebben dan voorheen - dus wees voorzichtig.
Merk ook op dat dit mechanisme zal werken voor eigenschappen die geen achtergrondveld hebben (onthoud dat een achtergrondveld slechts een veld is dat wordt gebruikt door eigenschappen wanneer u die veldgegevens wilt wijzigen of gebruiken).
In Advanced Functions heb ik ook de uitbreidingsfuncties besproken. Deze bieden ons de mogelijkheid om een klasse uit te breiden met nieuwe functionaliteit zonder van die klasse te hoeven erven. Kotlin biedt ook een vergelijkbaar mechanisme voor eigenschappen, genaamd uitbreidingseigenschappen.
val String.upperCaseFirstLetter: String get () = this.substring (0, 1) .toUpperCase (). plus (this.substring (1))
In de post Geavanceerde functies hebben we een gedefinieerd uppercaseFirstLetter ()
uitbreidingsfunctie met receiver-type Draad
. Hier hebben we het in plaats daarvan omgezet naar een extensie-eigenschap op het hoogste niveau. Merk op dat u een gettermethode op uw eigendom moet definiëren om dit te laten werken.
Met deze nieuwe kennis over extensie-eigenschappen weet u dus dat als u ooit wilt dat een klasse een eigenschap zou hebben die niet beschikbaar was, u vrij bent om een extensie-eigenschap van die klasse te maken.
Laten we beginnen met een typische Java-klasse of POJO (Plain Old Java Object).
public class BlogPost private final String-titel; privé uiteindelijke URI-URL; private finale Stringbeschrijving; privé slot Datum publicatiedatum; // ... constructor niet omwille van de beknoptheid opgenomen @Override openbare booleaanse gelijken (Object o) if (this == o) return true; if (o == null || getClass ()! = o.getClass ()) return false; BlogPost blogPost = (BlogPost) o; if (title! = null?! title.equals (blogPost.title): blogPost.title! = null) return false; if (url! = null?! url.equals (blogPost.url): blogPost.url! = null) return false; if (description! = null?! description.equals (blogPost.description): blogPost.description! = null) return false; return publishDate! = null? publishDate.equals (blogPost.publishDate): blogPost.publishDate == null; @Override public int hashCode () int result = title! = Null? title.hashCode (): 0; resultaat = 31 * resultaat + (url! = null? url.hashCode (): 0); resultaat = 31 * resultaat + (description! = null? description.hashCode (): 0); resultaat = 31 * resultaat + (publicationDate! = null? publishDate.hashCode (): 0); terugkeer resultaat; @Override public String toString () return "BlogPost " + "title = '" + title +' \ "+", url = "+ url +", + description + "\" + ", publicationDate =" + publishDate + ''; // ... setters en getters worden ook omwille van de eenvoud genegeerd
Zoals je kunt zien, moeten we de accessoire-eigenschappen van de klassen expliciet coderen: de getter en setter, evenals hashcode
, is gelijk aan
, en toString
methoden (hoewel IntelliJ IDEA, Android Studio of de AutoValue-bibliotheek ons kan helpen deze te genereren). We zien dit soort boilerplate-code meestal in de datalaag van een typisch Java-project. (Ik heb de field accessors en de constructor omwille van de beknoptheid verwijderd).
Het leuke is dat het Kotlin-team ons heeft voorzien van de gegevens
modifier voor klassen om het schrijven van deze boilerplate te elimineren.
Laten we nu de voorgaande code in plaats daarvan in Kotlin schrijven.
gegevensklasse BlogPost (var title: String, var url: URI, var description: String, var publishDate: Date)
Geweldig! We specificeren alleen de gegevens
modifier voor de klasse
zoekwoord om een gegevensklasse te maken, net als wat we in onze hebben gedaan Blogpost
Kotlin-klasse hierboven. Nu de is gelijk aan
, hashcode
, toString
, kopiëren
, en er worden voor ons meerdere methoden voor meerdere componenten gecreëerd. Merk op dat een dataklasse andere klassen kan uitbreiden (dit is een nieuw kenmerk van Kotlin 1.1).
is gelijk aan
MethodeDeze methode vergelijkt twee objecten voor gelijkheid en retourneert true als ze anderszins gelijk of onjuist zijn. Met andere woorden, het vergelijkt of de twee klasseninstanties dezelfde gegevens bevatten.
student.equals (student3) // met de == in Kotlin student == student3 // hetzelfde als gebruiken van gelijken ()
In Kotlin, met behulp van de gelijkheidsoperator ==
zal de is gelijk aan
methode achter de schermen.
hashCode
Methode Deze methode retourneert een geheel getal dat wordt gebruikt voor het snel opslaan en ophalen van gegevens die zijn opgeslagen in een op hash gebaseerde gegevensstructuur voor verzamelingen, bijvoorbeeld in de Hash kaart
en HashSet
collectie types.
toString
MethodeDeze methode retourneert een Draad
weergave van een object.
gegevensklasse Persoon (var firstName: String, var lastName: String) val person = Person ("Chike", "Mgbemena") println (persoon) // prints "Person (firstName = Chike, lastName = Mgbemena)"
Door alleen het klasse-exemplaar aan te roepen, krijgen we een stringobject teruggestuurd - Kotlin roept het object toString ()
onder de motorkap voor ons. Maar als we het niet doen gegevens
sleutelwoord in, zie wat onze objectstringrepresentatie zou zijn:
com.chike.kotlin.classes.Person@2f0e140b
Veel minder informatief!
kopiëren
MethodeMet deze methode kunnen we een nieuw exemplaar van een object maken met dezelfde eigenschapswaarden. Met andere woorden, het maakt een kopie van het object.
val person1 = Persoon ("Chike", "Mgbemena") println (persoon1) // Persoon (firstName = Chike, lastName = Mgbemena) val person2 = person1.copy () println (persoon2) // Persoon (firstName = Chike, lastName = Mgbemena)
Een cool ding over de kopiëren
methode in Kotlin is het vermogen om eigenschappen tijdens het kopiëren te veranderen.
val person3 = person1.copy (lastName = "Onu") println (person3) // Person3 (firstName = Chike, lastName = Onu)
Als u een Java-coder bent, is deze methode vergelijkbaar met de clone ()
methode die u al kent. Maar de Kotlin kopiëren
methode heeft meer krachtige functies.
In de Persoon
klasse, we hebben ook twee methoden automatisch gegenereerd voor ons door de compiler vanwege de gegevens
sleutelwoord geplaatst in de klas. Deze twee methoden worden voorafgegaan door "component", gevolgd door een achtervoegsel: component1 ()
, component2 ()
. Elk van deze methoden vertegenwoordigt de individuele eigenschappen van het type. Merk op dat het achtervoegsel overeenkomt met de volgorde van de eigenschappen die zijn gedeclareerd in de primaire constructor.
Dus in ons voorbeeld bellen component1 ()
zal de voornaam teruggeven en bellen component2 ()
zal de achternaam teruggeven.
println (person3.component1 ()) // Chike println (person3.component2 ()) // Onu
Het is moeilijk om de eigenschappen die deze stijl gebruiken te begrijpen en te lezen, dus het expliciet aanroepen van de eigenschap is veel beter. Deze impliciet gemaakte eigenschappen hebben echter een zeer nuttig doel: ze laten ons een destructureringsverklaring uitvoeren, waarin we elk onderdeel kunnen toewijzen aan een lokale variabele.
val (firstName, lastName) = Persoon ("Angelina", "Jolie") println (firstName + "" + lastName) // Angelina Jolie
Wat we hier hebben gedaan, is om de eerste en tweede eigenschappen (Voornaam
en achternaam
) van de Persoon
type naar de variabelen Voornaam
en achternaam
respectievelijk. Ik besprak ook dit mechanisme dat bekend staat als destructureringsverklaring in het laatste gedeelte van de post Pakketten en basisfuncties.
In het bericht Meer plezier met functies heb ik je verteld dat Kotlin ondersteuning biedt voor lokale of geneste functies, een functie die is gedeclareerd in een andere functie. Nou, Kotlin ondersteunt ook geneste klassen - een klasse die in een andere klas is gemaakt.
class OuterClass class NestedClass fun nestedClassFunc ()
We noemen zelfs de openbare functies van de geneste klasse zoals hieronder te zien - een geneste klasse in Kotlin is gelijk aan a statisch
geneste klasse in Java. Merk op dat geneste klassen geen verwijzing naar hun buitenklasse kunnen opslaan.
val nestedClass = OuterClass.NestedClass () nestedClass.nestedClassFunc ()
We zijn ook vrij om de geneste klasse in te stellen als privé. Dit betekent dat we alleen een instantie van de klasse kunnen maken NestedClass
in het kader van de OuterClass
.
Innerlijke klassen, aan de andere kant, kunnen verwijzen naar de buitenste klasse waarin het werd verklaard. Om een innerlijke klasse te creëren, plaatsen we de binnenste
sleutelwoord vóór de klasse
sleutelwoord in een geneste klasse.
class OuterClass () val oCPropt: String = "Yo" innerlijke klasse InnerClass fun innerClassFunc () val outerClass = this @ OuterClass print (outerClass.oCPropt) // prints "Yo"
Hier verwijzen we naar de OuterClass
van de InnerClass
door het gebruiken van Dit @ OuterClass
.
Een type enum declareert een set constanten die wordt vertegenwoordigd door ID's. Deze speciale soort klasse wordt gemaakt door het sleutelwoord enum
dat is opgegeven vóór de klasse
trefwoord.
enum klasse Land NIGERIA, GHANA, CANADA
Om een enum-waarde op te halen op basis van de naam (net als in Java), doen we dit:
Country.valueOf ( "NIGERIA")
Of we kunnen de Kotlin gebruiken enumValueOf
hulpmethode om constanten op een generieke manier te benaderen:
enumValueOf( "NIGERIA")
We kunnen ook alle waarden (zoals voor een Java-opsomming) als volgt krijgen:
Country.values ()
Eindelijk kunnen we de Kotlin gebruiken enumValues
hulpmethode om alle enum-items op een generieke manier te krijgen:
enumValues()
Dit retourneert een array met de enum-vermeldingen.
Net als een normale klas, de enum
type kan een eigen constructor hebben met eigenschappen die aan elke enum-constante zijn gekoppeld.
enumklasse Land (val callingCode: Int) NIGERIA (234), VS (1), GHANA (233)
In de land
enum type primaire constructor, hebben we de onveranderlijke eigenschap gedefinieerd callingCodes
voor elke enum-constante. In elk van de constanten hebben we een argument doorgegeven aan de constructor.
We kunnen dan als volgt toegang krijgen tot het eigenschap constants:
val country = Country.NIGERIA print (country.callingCode) // 234
Een verzegelde klasse in Kotlin is een abstracte klasse (je hebt nooit de intentie om er objecten van te maken) die andere klassen kunnen uitbreiden. Deze subklassen worden gedefinieerd in de verzegelde klasse-instantie - in hetzelfde bestand. Omdat al deze subklassen zijn gedefinieerd in de verzegelde klasse-instantie, kunnen we alle mogelijke subklassen kennen door het bestand eenvoudig te bekijken.
Laten we een praktisch voorbeeld bekijken.
// shape.kt sealed class Shape class Circle: Shape () class Triangle: Shape () class Rectangle: Shape ()
Om een klasse als verzegeld aan te geven, voegen we de verzegeld
modifier voor de klasse
modifier in de klassedeclaratie-header - in ons geval hebben we de Vorm
klasse als verzegeld
. Een verzegelde klasse is onvolledig zonder zijn subklassen, net als een typische abstracte klasse, dus we moeten de afzonderlijke subklassen binnen hetzelfde bestand declareren (shape.kt in dit geval). Merk op dat u geen subklasse van een verzegelde klasse uit een ander bestand kunt definiëren.
In onze code hierboven hebben we aangegeven dat de Vorm
klasse kan alleen door de klassen worden uitgebreid Cirkel
, Driehoek
, en Rechthoek
.
Verzegelde klassen in Kotlin hebben de volgende aanvullende regels:
abstract
naar een verzegelde klasse, maar dit is overbodig omdat verzegelde klassen standaard abstract zijn.Open
of laatste
wijziger. Klassen die subklassen van een verzegelde klasse uitbreiden, kunnen in hetzelfde bestand of in een ander bestand worden geplaatst. De verzegelde klassenubklasse moet worden gemarkeerd met de Open
modifier (je leert meer over inheritance in Kotlin in de volgende post).
// employee.kt sealed class Medewerker open klasse Artiest: Medewerker () // musician.kt class Musicus: Artist ()
Een verzegelde klasse en zijn subklassen zijn echt handig in a wanneer
uitdrukking. Bijvoorbeeld:
fun whatIsIt (shape: Shape) = when (shape) is Circle -> println ("A circle") is Triangle -> println ("A triangle") is Rectangle -> println ("A rectangle")
Hier is de compiler slim om ervoor te zorgen dat we alles mogelijk maken wanneer
gevallen. Dat betekent dat het niet nodig is om de anders
clausule.
Als we in plaats daarvan het volgende zouden doen:
fun whatIsIt (shape: Shape) = when (shape) is Circle -> println ("A circle") is Triangle -> println ("A triangle")
De code compileert niet, omdat we niet alle mogelijke gevallen hebben opgenomen. We zouden de volgende foutmelding hebben:
Kotlin: 'wanneer' expressie moet uitputtend zijn, voeg noodzakelijk 'is Rectangle' branch of 'else' branch toe.
Dus we kunnen de is rechthoek
case of include de anders
clausule om het te voltooien wanneer
uitdrukking.
In deze tutorial leer je meer over lessen in Kotlin. We hebben het volgende besproken over klasse-eigenschappen:
Ook leerde je over enkele coole en geavanceerde klassen zoals data, enum, geneste en verzegelde klassen. In de volgende tutorial in de Kotlin From Scratch-serie, maak je kennis met interfaces en overerving in Kotlin. Tot ziens!
Voor meer informatie over de Kotlin-taal, raad ik aan de Kotlin-documentatie te bezoeken. Of bekijk enkele van onze andere Android-apps voor app-ontwikkeling hier op Envato Tuts!