Kotlin – 10 důvodů, proč ho mám rád

Kotlin má velkou řadu funkcí, které je snadné si zamilovat. V tomto článku vám prozradím svých deset nejoblíbenějších a vysvětlím, proč jsou tak super.

1. Null Safety

Kotlin je takzvaný null safety jazyk.

 

class Owner {
  
  var adress: String = ""
  var telephone: String = ""
  var email: String? = null
}

 

V tomto příkladě nemůžete proměnným address a telephone přiřadit hodnotu null. V případě, že jim nastavíte hodnotu null, tak již během kompilace budete na tuto skutečnost upozorněni:

Compilation errorPokud chcete proměnné přiřadit hodnotu null, pak je nutné to explicitně nadefinovat. To uděláte přidáním “?” za typ proměnné (viz příklad emailu jako proměnné výše).

 

var a: String = "abc"
a = null // compilation error

var b: String? = "abc"
b = null // ok

val y: String = null // Does not compile.

 

Pokud tedy explicitně řeknete, že proměnná může obsahovat i null hodnotu, tak se vám může stát, že se potkáte se starou známou NullPointerException (NPE), kterou jistě každý z nás moc dobře zná z Javy.

val x: String? = "Hi"
x.length // Does not compile.

 

Abyste vyřešili kompilační chybu x.length, můžete použít if-statement:

 

if (x != null) {
  x.length // Compiles! Not idiomatic just to get length!
}

 

Nebo to můžete udělat ještě lépe prostřednictvím tzv. “safe calls”, které vrátí hodnotu volání, pokud volající není nulový (jinak se vrátí null).

x?.length


/*
You can tell to compiler that you know the variable cannot be null 
at run-time via non-null operator "!!"
*/
val len = x!!.length // Will throw if null.

Elvis Operator

Pomocí funkce “Elvis Operator” můžete pracovat s nulovými typy efektivněji.

// Elvis operator.
val len = x?.length ?: -1

V tomto případě bude použita length, pokud x není nulové, jinak bude použito -1 (výchozí hodnota).

 

2. Datová třída

Pokud vytváříte např. business logiku, perzistentní vrstvu apod., vytváříte většinou doménové objekty, třídy, které slouží pouze pro uchovávání dat (POJO). Uveďme si to na následujícím příkladu. Máme třídu Customer, která bude uchovávat data o id zákazníka, jméně a emailu.

V jazyce Java:

public class Customer {

    private int id;
    private String name;
    private String email;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

Jazyk Kotlin má pro tyto účely speciální označení třídy, tzv. datová třída (data class).

data class Customer(val id: Int, val name: String, val email: String)

Kompilátor Kotlinu pak k data class vygeneruje:

  • funkce hasCode(), equals(), toString()
  • přidá funkci copy() (o této funkci mluvím níže)
  • funkce componentN()

Kopírování objektů v datové třídě

Někdy se stane, že potřebujete zkopírovat objekt do nového s různými hodnotami proměnných. K tomu slouží již zmíněná funkce copy(), kterou obsahuje každá třída, která je označena jako datová (data class).

val customerSouhrada = Customer(id = 2001, name = "Vaclav Souhrada", email = "vsouhrada@email.com")
val updatedCstSouhrada = customerSouhrada.copy(email = "vaclav_souhrada@email.com")

 

3. Extension funkce

Kotlin nám umožňuje rozšířit funkčnost existujících tříd bez použití dědičnosti.

fun String.capitalize(): String {
  return this.toUpperCase()
}

Funkce capitalize() je extension funkce třídy String. Uvnitř této extension funkce získáte po zadání klíčového slova “this” přístup k objektu, na který byla aplikována.

fun String.hello() {
  println("Hello, $this!")
}

fun String.and(input: String): String {
  return "${this} $input"
}

Extension funkce nemění třídy, které rozšiřuje. Vše je vyřešeno staticky.

fun main(args: Array<String>) {
  println("vaclav souhrada".capitalize()) // prints VACLAV SOUHRADA

  "Vaclav".hello() // prints 'Hello, Vaclav!'

   var testString = "This is a string".and("This is another")
   println(testString) // prints 'This is a string This is another'
}

Díky extension funkcím je kód v projektech, kde používám Kotlin, daleko čistější. Vyhnu se tak totiž xxUtils(.java) třídám.

Dobrým příkladem pro použití funkce rozšíření by mohla být konverze na JSON. Ukážeme si to na velmi jednoduchém příkladu, který přidává extension funkci do všech “objektů” v Kotlinu (kotlin.Any a java.lang.Object jsou různé typy, ale během spuštění jsou reprezentovány stejnou třídou java.lang.Object).

val gson = Gson()
// ...
fun Any.toJSON(): String {
  return gson.toJson(this)
}
// ...

// Now if we want to convert it to JSON we can just simple call ourObject.toJSON()
val customer = Customer(id = 2001, name = "Vaclav Souhrada", email = "vsouhrada@email.com")
val json = customer.toJSON()

 

4. Smart Cast

Tahle funkce mi neuvěřitelně usnadňuje život.

Jak často používáte přetypování objektů? A jak často jste pak zjistili, že to bylo zbytečné?

Pokud chcete například zkontrolovat objekt, který je instancí typu java.lang.String, abyste vypsali délku řetězce, musíte nejdříve zkontrolovat typ a pak přetypovat objekt na String předtím, než získáte přístup ke konkrétní metodě z objektu String. Když jsem začal pracovat s Kotlinem, nepovažoval jsem to za nic převratného, ale po pár dnech jsem zjistil, že mi tato funkce v Javě vyloženě chybí.

SmartcastKompilátor v Kotlinu je zatraceně chytrý, když přijde na přetypování. Všechny nadbytečné operace zvládne za vás.

Operátor instanceof v Javě se v Kotlinu zapisuje operátorem is. V příkladu výše vidíte, že v Kotlinu nemusíte přetypovávat objekt uvnitř statementu, pokud jste ho už zkontrolovali operátorem is. Nadbytečné přetypování v Javě mi nepřišlo divné, dokud jsem nezačal používat Kotlin.

Další místo, kde vidíte smart cast, je ve výrazech když (when expressions):

SmartcastSmart Cast dohromady s výrazem when dokáže pěkně zpřehlednit kód:

06_smartcast3

Na příkladu výše vidíte, že příchozí eventa, která přišla z Event busu, musí být nejprve zkontrolována, jestli je instancí konkrétního event objektu. Následně je přetypována na příslušný Java objekt. Připouštím, že v Javě to v tomto případě tak hrozně nevypadá, ale podívejte se na stejný kód v Kotlinu:

Smartcast

Myslím, že můžu s čistým svědomím prohlásit, že Kotlin zde vítězí na plné čáře.

 

5. Singleton (Objekt)

Další skvělou vlastností Kotlinu je jednoduchost, s jakou můžete definovat “singleton” objekty. Nejprve si však připomeňme, jak se singleton zapisuje v Javě.

Použití singletonu v Javě může vypadat takto:

public class SingletonInJava {

    private static SingletonInJava INSTANCE;

    public static SingletonInJava getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new SingletonInJava();
        }

        return INSTANCE;
    }   
}

Zápis singletonu v Kotlinu uděláme opravdu jednoduše. Použijeme klíčové slovo object, které umožní definovat objekt, který existuje pouze jako single instance.

object SingletonInKotlin {

}

// And we can call
SingletonInKotlin.doSomething()

 

6. Funkcionální programování

Kombinace použití lambda výrazů a Kotlinu nám značně může ulehčit práci s kolekcemi:

val numbers = arrayListOf(10 ,5 , -9, 9, 11, 5, -6)
val nonNegative = numbers.filter { it >= 0}

println(nonNegative) // [10, 5, 9, 11, 5]

Nebo:

// Sum of all elements: 25
println(numbers.foldRight(0, { a, b -> a + b }))

//20 10 -18 18 22 10 -12
numbers.forEach { println("${it * 2} ") }

val kindOfNumbers: Iterable<String> = numbers.filter { it < 0 }
       .map { "$it is negative" }

println(kindOfNumbers) // [-9 is negative, -6 is negative]

 

7. Typová inference

V Kotlinu nemusíte explicitně definovat typ každé proměnné:

val name = "Vaclav"
val age = 31

// Only need Iterable interface
val list: Iterable<Double> = arrayListOf(1.0, 0.0, 3.1415, 2.718) 
// Type is ArrayList
val arrayList = arrayListOf("Kotlin", "Scala", "Groovy")

 

8. Výchozí argumenty

V Javě často musíte duplikovat kód, abyste definovali různé varianty metod nebo konstruktoru:

public class OperatorInfoInJava {

    private final String uuid;
    private final String name;
    private final Boolean hasAccess;
    private final Boolean isAdmin;
    private final String notes;

    public OperatorInfoInJava(String uuid, String name, Boolean hasAccess, 
                              Boolean isAdmin, String notes) {
        this.uuid = uuid;
        this.name = name;
        this.hasAccess = hasAccess;
        this.isAdmin = isAdmin;
        this.notes = notes;
    }

    public OperatorInfoInJava(String uuid, String name) {
        this(uuid, name, true, false, "");
    }

    public OperatorInfoInJava(String name, Boolean hasAccess) {
        this(UUID.randomUUID().toString(), name, hasAccess, false, "");
    }

Kotlin na to jde jinak. Má podporu zapisování tzv. výchozích argumentů (default arguments).

class OperatorInfoInKotlin(val uuid: String = UUID.randomUUID().toString(),
                           val name: String,
                           val hasAccess: Boolean = true,
                           val isAdmin: Boolean = false,
                           val notes: String = "") {}

 

9. Pojmenované argumenty

Výchozí argumenty se kombinací s pojmenovanými argumenty stávají velice silnou zbraní v boji za přehlednější kód. Můžete to vidět na příkladu vytvoření instance našeho OperatorInfoInKotlin z předchozí části:

OperatorInfoInKotlin(name = "Vaclav")
OperatorInfoInKotlin(name = "Vaclav", hasAccess = false, isAdmin = false, notes = "blabla")

Nyní zpět do Javy.

Z tohoto kódu v Javě bez znalosti třídy OperatorInfoInJava nepoznáte, které hodnoty jsou nastaveny.

// Java
new OperatorInfoInJava("Vaclav", false, false, "blabla");

 

10. Práce s kolekcemi

V Kotlinu máte funkce vyššího řádu, lambda výrazy, přetížení operátora a mnoho dalších užitečných metod pro práci s kolekcí.

Ukážeme si to na jednoduchém příkladu:

“Jaký je průměrný věk zaměstnanců ve firmě v Plzni?”

(kód níže je v jazyce Java 1.6, samozřejmě, že v 1.8 za použítí streamu můžete kód napsat přes lambdu i v Javě )

Extension funkce a funkcionální programování jsou výborným nástrojem a Kotlin má mnoho užitečných rozšíření pro práci s kolekcemi.

public double getAverageAge(@NotNull List<Employee> employees) {
  int ageSum = 0;
  int count= 0;

  for (Employee employee : employees) {
    if ("Pilsen".equals(employee.getCity()) {
      ageSum += employee.getAge();
      count++;
    }
  }

  if (count == 0) return 0

  return ageSum / count;
}

Stejný úkol můžete v Kotlinu provést v následujících krocích.

Nejdříve vyfiltrujete a uchováte všechny zaměstnance, kteří jsou z Plzně.

fun getAverageAge(val employees: List<Employee>): Double {
  return employees.filter{ it.city == City.PILSEN }.map{ it.age }.average()
}

Pro více informací o kolekci si prohlédněte oficiální dokumentaci nebo článek na blogu Antonia Leiva.

O Kotlinu samotném se víc dozvíte v mém předchozím článku Kotlin – jazyk, který je dobré znát.

Které funkce Kotlinu jste při práci nejvíc ocenili vy?

Václav Souhrada
Kotlin & Android Developer at eMan, Czech Kotlin User Group Founder and Organizer

RSS