Inleiding tot de Kotlin-taal

1. Overzicht

In deze tutorial gaan we kijken naar Kotlin, een nieuwe taal in de JVM-wereld, en enkele basisfuncties, waaronder klassen, overerving, voorwaardelijke statements en looping-constructies.

Vervolgens zullen we enkele van de belangrijkste kenmerken bekijken die Kotlin tot een aantrekkelijke taal maken, inclusief nulveiligheid, gegevensklassen, uitbreidingsfuncties en Draad Sjablonen.

2. Maven afhankelijkheden

Om Kotlin in uw Maven-project te gebruiken, moet u de Kotlin-standaardbibliotheek toevoegen aan uw pom.xml:

 org.jetbrains.kotlin kotlin-stdlib 1.0.4 

Om JUnit-ondersteuning voor Kotlin toe te voegen, moet u ook de volgende afhankelijkheden opnemen:

 org.jetbrains.kotlin kotlin-test-junit 1.0.4 test junit junit 4.12 test 

Je kunt de nieuwste versies van kotlin-stdlib, kotlin-test-junit en junit vinden op Maven Central.

Ten slotte moet je de bronmappen en de Kotlin-plug-in configureren om een ​​Maven-build uit te voeren:

 $ {project.basedir} / src / main / kotlin $ {project.basedir} / src / test / kotlin kotlin-maven-plugin org.jetbrains.kotlin 1.0.4 compileren compileren test-compileren test-compileren 

Je kunt de laatste versie van kotlin-maven-plugin vinden in Maven Central.

3. Basissyntaxis

Laten we eens kijken naar de basisbouwstenen van Kotlin-taal.

Er is enige gelijkenis met Java (het definiëren van pakketten gaat bijvoorbeeld op dezelfde manier). Laten we de verschillen eens bekijken.

3.1. Functies definiëren

Laten we een functie definiëren met twee Int-parameters met Int retourtype:

leuke som (a: Int, b: Int): Int {return a + b}

3.2. Lokale variabelen definiëren

Assign-once (alleen-lezen) lokale variabele:

val a: Int = 1 val b = 1 val c: Int c = 1

Let op dat type variabele b wordt afgeleid door een Kotlin-compiler. We zouden ook veranderlijke variabelen kunnen definiëren:

var x = 5 x + = 1

4. Optionele velden

Kotlin heeft een basissyntaxis voor het definiëren van een veld dat nullabel kan zijn (optioneel). Als we willen verklaren dat dat type veld nullabel is, moeten we het achtervoegsel met een vraagteken gebruiken:

val e-mail: String?

Als je een nullable veld hebt gedefinieerd, is het volkomen geldig om een nul naar het:

val e-mail: String? = null

Dat betekent dat in een e-mailveld een nul. Als we zullen schrijven:

val email: String = "waarde"

Vervolgens moeten we een waarde toewijzen aan het e-mailveld in dezelfde verklaring die we e-mail declareren. Het mag geen null-waarde hebben. We gaan terug naar Kotlin nul veiligheid in een later gedeelte.

5. Klassen

Laten we laten zien hoe u een eenvoudige klasse kunt maken om een ​​specifieke categorie van een product te beheren. Onze ItemManager klasse hieronder heeft een standaard constructor die twee velden vult - categorie ID en dbConnection - en een optioneel e-mail veld:

class ItemManager (val categoryId: String, val dbConnection: String) {var email = "" // ...}

Dat ItemManager (...) construct maakt constructor en twee velden in onze klasse: categorie ID en dbConnection

Merk op dat onze constructor de val trefwoord voor zijn argumenten - dit betekent dat de overeenkomstige velden zullen zijn laatste en onveranderlijk. Als we de var trefwoord (zoals we deden bij het definiëren van het e-mail veld), dan zijn die velden veranderlijk.

Laten we een instantie van ItemManager maken met behulp van de standaardconstructor:

ItemManager ("cat_id", "db: // connection")

We zouden kunnen bouwen ItemManager met behulp van benoemde parameters. Het is erg handig als je zoals in dit voorbeeld een functie hebt die twee parameters met hetzelfde type nodig heeft, bijv. Draad, en je wilt een volgorde van hen niet verwarren. Met behulp van naamgevingsparameters kunt u expliciet schrijven welke parameter wordt toegewezen. In de klas ItemManager er zijn twee velden, categorie ID en dbConnection dus naar beide kan worden verwezen met benoemde parameters:

ItemManager (categoryId = "catId", dbConnection = "db: // Connection")

Het is erg handig als we meer argumenten aan een functie moeten doorgeven.

Als u extra constructors nodig heeft, kunt u deze definiëren met de constructeur trefwoord. Laten we een andere constructor definiëren die ook de e-mail veld:

constructor (categoryId: String, dbConnection: String, email: String): this (categoryId, dbConnection) {this.email = email}

Merk op dat deze constructor de standaardconstructor aanroept die we hierboven hebben gedefinieerd voordat het e-mailveld werd ingesteld. En aangezien we al gedefinieerd categorie ID en dbConnection onveranderlijk te zijn met behulp van de val trefwoord in de standaardconstructor, hoeven we de val trefwoord in de aanvullende constructor.

Laten we nu een instantie maken met behulp van de aanvullende constructor:

ItemManager ("cat_id", "db: // connection", "[email protected]")

Als u een instantiemethode wilt definiëren op ItemManager, zou u dit doen met behulp van de pret trefwoord:

fun isFromSpecificCategory (catId: String): Boolean {return categoryId == catId}

6. Erfenis

Standaard zijn de klassen van Kotlin gesloten voor uitbreiding - het equivalent van een gemarkeerde klasse laatste in Java.

Om aan te geven dat een klasse open staat voor extensie, zou je de Open trefwoord bij het definiëren van de klasse.

Laten we een Item klasse die open staat voor extensie:

open class Item (val id: String, val name: String = "unknown_name") {open fun getIdOfItem (): String {return id}}

Merk op dat we ook de getIdOfItem () methode als open. Hierdoor kan het worden overschreven.

Laten we nu het Item class en overschrijf de getIdOfItem () methode:

class ItemWithCategory (id: String, name: String, val categoryId: String): Item (id, name) {override fun getIdOfItem (): String {return id + naam}}

7. Voorwaardelijke verklaringen

In Kotlin, voorwaardelijke verklaring als is een equivalent van een functie die een bepaalde waarde retourneert. Laten we naar een voorbeeld kijken:

fun makeAnalyisOfCategory (catId: String): Unit {val result = if (catId == "100") "Yes" else "No" println (result)}

In dit voorbeeld zien we dat if catId is gelijk aan "100" voorwaardelijk blok retourneert "Ja", anders retourneert het "Nee". De geretourneerde waarde wordt toegewezen aan resultaat.

Je zou een normaal kunnen creëren alsanders blok:

val nummer = 2 if (nummer 10) {println ("nummer is groter dan 10")}

Kotlin heeft ook een erg handig wanneer commando dat werkt als een geavanceerde switch-instructie:

val name = "John" when (name) {"John" -> println ("Hi man") "Alice" -> println ("Hi lady")} 

8. Collecties

Er zijn twee soorten verzamelingen in Kotlin: veranderlijk en onveranderlijk. Wanneer we een onveranderlijke verzameling maken, betekent dit dat deze alleen kan worden gelezen:

val items = listOf (1, 2, 3, 4)

Er is geen add-functie-element op die lijst.

Als we een veranderlijke lijst willen maken die kan worden gewijzigd, moeten we gebruiken mutableListOf () methode:

val rwList = mutableListOf (1, 2, 3) rwList.add (5)

Een veranderlijke lijst heeft toevoegen() methode zodat we er een element aan kunnen toevoegen. Er zijn ook gelijkwaardige methoden voor andere soorten verzamelingen: mutableMapOf (), mapOf (), setOf (), mutableSetOf ()

9. Uitzonderingen

Het mechanisme voor het afhandelen van uitzonderingen lijkt sterk op dat in Java.

Alle uitzonderingsklassen worden verlengd Gooibaar. De uitzondering moet een bericht, stacktrace en een optionele oorzaak hebben. Elke uitzondering in Kotlin is niet aangevinkt, wat betekent dat de compiler ons niet dwingt om ze te vangen.

Om een ​​exception-object te gooien, moeten we de throw-expression gebruiken:

throw-uitzondering ("msg")

Afhandeling van uitzonderingen gebeurt door gebruik te maken van probeer ... vangblok (eindelijk optioneel):

probeer {} catch (e: SomeException) {} eindelijk {}

10. Lambda's

In Kotlin kunnen we lambda-functies definiëren en deze als argumenten doorgeven aan andere functies.

Laten we eens kijken hoe we een eenvoudige lambda kunnen definiëren:

val sumLambda = {a: Int, b: Int -> a + b}

We hebben gedefinieerd sumLambda functie waaraan twee argumenten van het type moeten doorgegeven worden Int als argument en keert terug Int.

We zouden een lambda kunnen doorgeven:

@Test plezier gegevenListOfNumber_whenDoingOperationsUsingLambda_shouldReturnProperResult () {// gegeven val listOfNumbers = listOf (1, 2, 3) // wanneer val som = listOfNumbers.reduce {a, b -> a + b} // dan assertEquals (6, som)}

11. Looping constructies

In Kotlin kon het doorlopen van collecties worden gedaan met behulp van een standaard voor in construeren:

val numbers = arrayOf ("eerste", "tweede", "derde", "vierde")
voor (n in cijfers) {println (n)}

Als we een reeks gehele getallen willen herhalen, kunnen we een bereikconstructie gebruiken:

voor (i in 2..9 stap 2) {println (i)}

Merk op dat het bereik in het bovenstaande voorbeeld aan beide kanten inclusief is. De stap parameter is optioneel en komt overeen met het tweemaal verhogen van de teller in elke iteratie. De output zal volgen:

2 4 6 8

We kunnen een rangeTo () functie die is gedefinieerd op Int les op de volgende manier:

1.rangeTo (10) .map {it * 2}

Het resultaat bevat (merk op dat rangeTo () is ook inclusief):

[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

12. Nul-veiligheid

Laten we eens kijken naar een van de belangrijkste kenmerken van Kotlin: veiligheid, dat is ingebouwd in de taal. Om te illustreren waarom dit handig is, zullen we een eenvoudige service maken die een Item voorwerp:

class ItemService {leuke findItemNameForId (id: String): Item? {val itemId = UUID.randomUUID (). toString () retourneer Item (itemId, "name- $ itemId"); }}

Het belangrijkste om op te merken is het geretourneerde type van die methode. Het is een object gevolgd door het vraagteken. Het is een constructie uit de Kotlin-taal, dat wil zeggen Item geretourneerd van die methode kan null zijn. We moeten die case tijdens het compileren afhandelen en beslissen wat we met dat object willen doen (het is min of meer gelijk aan Java 8 Optioneel type).

Als de handtekening van de methode een type zonder vraagteken heeft:

leuk findItemNameForId (id: String): Item

dan hoeft de aanroepende code geen null-zaak te behandelen, omdat het wordt gegarandeerd door de compiler en de Kotlin-taal, dat het geretourneerde object niet null kan zijn.

Anders, als er een nullable object wordt doorgegeven aan een methode, en dat geval wordt niet afgehandeld, zal het niet compileren.

Laten we een testcase schrijven voor Kotlin-type-veiligheid:

val id = "item_id" val itemService = ItemService () val resultaat = itemService.findItemNameForId (id) assertNotNull (resultaat? .let {it -> it.id}) assertNotNull (resultaat !!. id) 

We zien hier dat na het uitvoeren van de methode findItemNameForId (), het geretourneerde type is van Kotlin Nullable. Om toegang te krijgen tot een veld van dat object (ID kaart), moeten we die case tijdens het compileren afhandelen. Methode laat() wordt alleen uitgevoerd als een resultaat niet-nulbaar is. ikd veld is toegankelijk binnen een lambda-functie omdat het nulveilig is.

Een andere manier om toegang te krijgen tot dat nulbare objectveld is door de Kotlin-operator te gebruiken !!. Het is gelijk aan:

if (result == null) {throwNpe (); } resultaat teruggeven;

Kotlin zal controleren of dat object een nul als dat zo is, zal het een NullPointerException, anders zal het een correct object retourneren. Functie throwNpe () is een interne functie van Kotlin.

13. Dataklassen

Een heel mooie taalconstructie die in Kotlin kan worden gevonden, zijn dataklassen (het is gelijk aan "case class" van Scala-taal). Het doel van dergelijke klassen is om alleen gegevens te bewaren. In ons voorbeeld hadden we een Item klasse die alleen de gegevens bevat:

dataklasse Item (val id: String, val name: String)

De compiler maakt voor ons methoden hashCode (), is gelijk aan (), en toString (). Het is een goede gewoonte om dataklassen onveranderlijk te maken door een val trefwoord. Gegevensklassen kunnen standaard veldwaarden hebben:

dataklasse Item (val id: String, val name: String = "unknown_name")

We zien dat naam veld heeft een standaardwaarde "onbekende_naam".

14. Uitbreidingsfuncties

Stel dat we een klasse hebben die deel uitmaakt van een bibliotheek van derden, maar we willen deze uitbreiden met een aanvullende methode. Met Kotlin kunnen we dit doen door extensiefuncties te gebruiken.

Laten we eens kijken naar een voorbeeld waarin we een lijst met elementen hebben en we willen een willekeurig element uit die lijst nemen. We willen een nieuwe functie toevoegen willekeurig() aan derde partij Lijst klasse.

Zo ziet het eruit in Kotlin:

leuke List.random (): T? {if (this.isEmpty ()) return null return get (ThreadLocalRandom.current (). nextInt (count ()))}

Het belangrijkste dat hier opvalt, is een handtekening van de methode. De methode wordt voorafgegaan door een naam van de klasse waaraan we deze extra methode toevoegen.

Binnen de extensiemethode werken we op een bereik van een lijst en gebruiken daarom dit gaf gebruikstoegang tot lijstinstancemethoden zoals is leeg() of count (). Dan kunnen we bellen willekeurig() methode op elke lijst die binnen dat bereik valt:

leuk getRandomElementOfList (lijst: lijst): T? {return list.random ()}

We hebben een methode gemaakt die een lijst neemt en vervolgens een aangepaste extensiefunctie uitvoert willekeurig() dat was eerder gedefinieerd. Laten we een testcase schrijven voor onze nieuwe functie:

val elements = listOf ("a", "b", "c") val result = ListExtension (). getRandomElementOfList (elementen) assertTrue (elements.contains (resultaat)) 

De mogelijkheid om functies te definiëren die klassen van derden "uitbreiden" is een zeer krachtige functie en kan onze code beknopter en leesbaarder maken.

15. String-sjablonen

Een erg leuke functie van Kotlin-taal is een mogelijkheid om sjablonen voor te gebruiken Draads. Het is erg handig omdat we niet hoeven samen te voegen Draads handmatig:

val firstName = "Tom" val secondName = "Mary" val concatOfNames = "$ firstName + $ secondName" val sum = "vier: $ {2 + 2}" 

We kunnen ook een uitdrukking binnen de ${} blok:

val itemManager = ItemManager ("cat_id", "db: // connection") val result = "functieresultaat: $ {itemManager.isFromSpecificCategory (" 1 ")}"

16. Kotlin / Java-interoperabiliteit

Kotlin - Java-interoperabiliteit is naadloos eenvoudig. Stel dat we een Java-klasse hebben met een methode die werkt op Draad:

class StringUtils {openbare statische String toUpperCase (String naam) {retour naam.toUpperCase (); }}

Nu willen we die code uitvoeren vanuit onze Kotlin-klasse. We hoeven alleen die klasse te importeren en we zouden zonder problemen de Java-methode vanuit Kotlin kunnen uitvoeren:

val name = "tom" val res = StringUtils.toUpperCase (naam) assertEquals (res, "TOM")

Zoals we zien, hebben we de Java-methode van Kotlin-code gebruikt.

Kotlin-code bellen vanuit Java is ook heel eenvoudig. Laten we de eenvoudige Kotlin-functie definiëren:

class MathematicsOperations {fun addTwoNumbers (a: Int, b: Int): Int {return a + b}}

Uitvoeren addTwoNumbers () van Java-code is heel eenvoudig:

int res = nieuwe MathematicsOperations (). addTwoNumbers (2, 4); assertEquals (6, res);

We zien dat de oproep naar Kotlin-code transparant voor ons was.

Wanneer we een methode in java definiëren, is dat retourtype een leegte, in Kotlin zal de geretourneerde waarde een zijn Eenheid type.

Er zijn enkele speciale ID's in de Java-taal ( is, voorwerp, in, ..) dat wanneer ze in Kotlin-code worden gebruikt, moet worden ontsnapt. We zouden bijvoorbeeld een methode kunnen definiëren die een naam heeft voorwerp() maar we moeten onthouden om aan die naam te ontsnappen, aangezien dit een speciale identificatie is in java:

fun `object` (): String {return" this is object "}

Dan kunnen we die methode uitvoeren:

`object` ()

17. Conclusie

Dit artikel geeft een inleiding tot de Kotlin-taal en de belangrijkste kenmerken ervan. Het begint met het introduceren van eenvoudige concepten zoals lussen, voorwaardelijke instructies en het definiëren van klassen. Vervolgens worden enkele meer geavanceerde functies weergegeven, zoals uitbreidingsfuncties en nulbeveiliging.

De implementatie van al deze voorbeelden en codefragmenten is te vinden in het GitHub-project.