Kotlin

  • Staticky typovaný programovací jazyk
  • Pro JVM, JavaScript i nativní kód
  • 100% interoperabilní s Javou
  • Snadno pochopitelný pro Java vývojáře
  • "Lepší Java"
“Kotlin leverages the existing skills of Java developers and finds the right tradeoffs to provide new features that make Java developers more efficient without going too far.”

~ Sébastien Deleuze, a Pivotal engineer

Kotlin

  • Vyvinutý v JetBrains
  • Kotlin Foundation (JetBrains + Google)
  • Preferovaný jazyk pro vývoj Android aplikací
  • Přináší značné výhody i na backendu
  • Spring 5 přidal přímou podporu Kotlinu
“Kotlin does for the JVM what Spring did to J2EE.”

~ Rod Johnson, Spring creator

JDK release model

Dohání Java Kotlin?

  • Které vlastnosti Kotlinu přibyly do Javy?
  • Jak moc se nové vlastnosti Javy podobají Kotlinu?
  • Má stále smysl přecházet na Kotlin?

Základy syntaxe Kotlinu

Deklarace proměnné

val a: Int = 2           //immutable
var user: User = User()  //mutable
  • mutabilní a imutabilní proměnné
  • nepovinné středníky
  • chybí klíčové slovo new
  • typy vpravo

Typy vpravo?

Type inference

val a = 2
var user = User()
val items: Map<Warehouse, List<OrderItem>> = ...
final var a = 2;
var user = new User();
Map<Warehouse, List<OrderItem>> items = ...

Typy vpravo - napovídání

Inicializace kolekce

val list = listOf(1, 2, 3)          //immutable
val list = mutableListOf(1, 2, 3)   //mutable

val map = mapOf(1 to "one", 2 to "two")         //immutable
val map = mutableMapOf(1 to "one", 2 to "two")  //mutable
List<Integer> list = List.of(1, 2, 3);   //immutable :-o
Map<Integer, String> map = Map.of(1, "one", 2, "two");
Map<Integer, String> map = Map.ofEntries(
    entry(1, "one"),
    entry(2, "two")
);

When

var text: String
when (x) {
    1 -> text = "jedna"
    2 -> text = "dvě"
    else -> {
        text = "mnoho"
    }
}
When je výraz
val text = when (x) {
    1 -> "jedna"
    2 -> "dvě"
    else -> {
        "mnoho"
    }
}

Java 12 - Switch Expression (preview until Java 14?)

var numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    case THURSDAY, SATURDAY     -> 8;
    case WEDNESDAY              -> 9;
};
val numLetters = when (day) {
    MONDAY, FRIDAY, SUNDAY -> 6
    TUESDAY                -> 7
    THURSDAY, SATURDAY     -> 8
    WEDNESDAY              -> 9
}

Java 13 - Yield in switch Expression (preview)

var numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY:
        yield 6;
    case TUESDAY:
        yield 7;
    case THURSDAY:
    case SATURDAY:
        yield 8;
    case WEDNESDAY:
        yield 9;
};
val numLetters = when (day) {
    MONDAY, FRIDAY, SUNDAY -> 6
    TUESDAY                -> 7
    THURSDAY, SATURDAY     -> 8
    WEDNESDAY              -> 9
}

When jako náhrada else if

when {
    x.isOdd() -> println("x je liché")
    x.isEven() -> println("x je sudé")
    else -> throw IllegalNumberException()
}
if (x.isOdd()) {
    System.out.println("x je liché");
} else if (x.isEven()) {
    System.out.println("x je sudé");
} else {
    throw new IllegalNumberException();
}

If je výraz

val max = if (a > b) a else b
Kotlin nemá ternární operátor ? :

Try je výraz

val result = try {
    count()
} catch (e: ArithmeticException) {
    throw IllegalStateException(e)
}

Třídy a funkce

abstract class Shape() {
    abstract fun area(): Double
}

class Circle(private val radius: Double) : Shape() {

    override fun area(): Double {
        return 2 * Math.PI * radius.pow(2)
    }
}
override fun area() = Math.PI * radius.pow(2)

Defaultní parametry funkcí

fun sum(a: Int, b: Int = 0): Int {
    return a + b
}
Pojmenované parametry
val c = sum(a = 5, b = 10)

Defaultní parametry - náhrada za přetěžování funkcí

public int sum(int a, int b) {
    return a + b;
}

public int sum(int a) {
    return sum(a, 0);
}
fun sum(a: Int, b: Int = 0): Int {
    return a + b
}

Defaultní parametry - náhrada za telescoping constructor

public class Pizza {
  public Pizza(int size) { ... }
  public Pizza(int size, boolean cheese) { ... }
  public Pizza(int size, boolean cheese, boolean pepperoni) { ... }
  public Pizza(int size, boolean cheese, boolean pepperoni, boolean bacon) { ... }
  ...
}
class Pizza(
    size: Int,
    cheese: Boolean = false,
    pepperoni: Boolean = false,
    bacon: Boolean = false
) {
    ...
}

Defaultní parametry - náhrada za builder pattern

public class Pizza {
  private int size;
  private boolean cheese;

  public static class Builder {
    //required
    private final int size;

    //optional
    private boolean cheese = false;

    public Builder(int size) {
      this.size = size;
    }

    public Builder addCheese() {
      cheese = true;
      return this;
    }

    public Pizza build() {
      return new Pizza(this);
    }
  }

  private Pizza(Builder builder) {
    size = builder.size;
    cheese = builder.cheese;
  }
}
Pizza pizza = new Pizza.Builder(12)
 .addCheese()
 .build();
class Pizza(
    size: Int,
    cheese: Boolean = false,
    pepperoni: Boolean = false,
    bacon: Boolean = false
) {
    ...
}

val pizza = Pizza(size = 12, cheese = true)

First-class functions

public inline fun measureTimeMillis(block: () -> Unit) : Long {
    val start = System.currentTimeMillis()
    block()
    return System.currentTimeMillis() - start
}
Lambda funkce
val doubled = ints.map({ value -> value * 2 })
val doubled = ints.map { it * 2 }
val elapsedTime = measureTimeMillis {
    doSomeWork()
}

Kotlin je stručný

Properties, primary constructor

public class User {

    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
class User (var name: String)
  • V Javě properties spíše nebudou
  • Řešením je Lombok

Data classes

public class User {

    private String name;

    public User(String name) {...}

    public String getName() {...}

    public void setName(String name) {...}

    @Override
    public boolean equals(Object o) {...}

    @Override
    public int hashCode() {...}

    @Override
    public String toString() {...}

    public User copy(String name) {...}
}
data class User (var name: String)
  • Java - JEP 359: Records
  • Zatím ve stavu Candidate
record User(String name) { }

Multiline strings

val html = """<html>
                  <body>
                      <p>Hello, Text Blocks</p>
                  </body>
              </html>"""
var html = """
            <html>
                <body>
                <p>Hello, Text Blocks</p>
                </body>
            </html>""";
val html = """
            <html>
                <body>
                    <p>Hello, Text Blocks</p>
                </body>
            </html>""".trimIndent()

String interpolation

String s = "Rozměry: " + width + " x " + height + " metrů";String s = String.format("Rozměry: %d x %d metrů", width, height);
val s = "Rozměry: $width x $height metrů"
  • V Javě se o tom diskutuje, ale zatím to není v plánu

Smart casts

if (obj instanceof String) {
    System.out.println(((String) obj).toLowerCase());
}
if (obj is String) {
    println(obj.toLowerCase());
}
  • Java - project Amber
  • JEP 305: Pattern Matching for instanceof (Preview)

Singleton

public class Singleton {

    private static Singleton instance = null;

    private Singleton(){
    }

    private static void getInstance() {
        if(instance == null){
            synchronized (Singleton.class) {
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}
object Singleton

Lazy initialization

private ExpensiveObject expensiveObject;

public ExpensiveObject getExpensiveObject() {
    if(expensiveObject == null) {
        synchronized(this) {
            if(expensiveObject == null) {
                expensiveObject = new ExpensiveObject();
            }
        }
    }

    return expensiveObject;
}
val expensiveObject by lazy  { ExpensiveObject() }

Delegation

class SynchronizedProducer(private val producer: Producer) : Producer by producer {

    private val lock = ReentrantLock()

    override fun produce(): String {
        lock.withLock {
            return producer.produce()
        }
    }
}

Reified generics

List<User> users = mapper.readValue(json, new TypeReference<List<User>>(){});
val users: List<User> = mapper.readValue(json)
val users = mapper.mapper<List<User>>(json)

Reified generics

inline fun <reified T> jacksonTypeRef(): TypeReference<T>
    = object: TypeReference<T>() {}
inline fun <reified T> ObjectMapper.readValue(content: String): T
    = readValue(content, jacksonTypeRef<T>())

Kotlin je bezpečný

Null safety

var notNullString: String = "Hello"
notNullString = null  //compilation error
var nullableString: String? = "Hello"
notNullString = null  //OK

Null-safe navigation

fun getZipForUser(user: User?): String? {
    return user?.address?.zip
}
public String getZipForUser(User user) {
    if (user != null && user.getAddress() != null) {
        return user.getAddress().getZip();
    } else {
        return null;
    }
}

Elvis operator

val displayName = username ?: "N/A"
String displayName = username != null ? username : "N/A";

Elvis operator

fun getUserById(id: Long) : User {
    return userRepository.findOne(id) ?: throw UserNotFoundException()
}
User getUserById(long id) {
    User user = userRepository.findOne(id);
    if (user == null) {
        throw new UserNotFoundException();
    }
    return user;
}

Smart casts for null checks

val user: User? = findUserById(42)
if (user != null) {
    println(user.address)  //user is not null
}

Even smarter casts - contracts

val text: String? = getText()
if (!text.isNullOrBlank()) {
    println(text.length)  //text is not null
}

Jak to řeší Java?

  • Optional
  • Podpora @Nullable a @NotNull anotací v IDE
  • Null-safe navigation operátor není v plánu

Sealed classes

sealed class Expression {
    data class AndExpression(val subExpressions: Set) : Expression()
    data class OrExpression(val subExpressions: Set) : Expression()
    data class NotExpression(val subExpression: Expression) : Expression()
}

fun processExpression(expression: Expression): String =
    when(expression) {
        is AndExpression -> TODO()
        is Expression.OrExpression -> TODO()
        is Expression.NotExpression -> TODO()
    }
  • JEP 360: Sealed Types - Candidate

Kotlin je rozšiřitelný

Extension funkce

public fun String.capitalize(): String {
    return if (isNotEmpty()) {
        substring(0, 1).toUpperCase() + substring(1)
    } else {
        this
    }
}
Nahrazují *Utils (StringUtils, FileUtils, ...) třídy
StringUtils.capitalize(string);
string.capitalize()

Extension funkce ve standardní knihovně

  • String - substringBefore, isNullOrEmpty, removePrefix, ...
  • Collections - filter, map, sum, min, max, groupBy, ...
  • I/O - String.byteInputStream(), InputStream.readBytes()

Extension funkce - konzistence

StringUtils.capitalize(text.trim().replace('\t', ' '));
text.trim().replace('\t', ' ').capitalize()

Extension funkce - objevitelnost

Extension funkce - uklizený kód

  • Extensions pro specifický projekt
  • Extensions v několika souborech podle typu funkcionality
  • Privátní extensions
  • Základní typy mohou zůstat čisté

Kotlin je neblokující

Coroutines

  • operace, která může být pozastavena bez zablokání vlákna
  • light-weight threads
  • asynchronní, neblokující zpracování

Blokující kód

fun postItem(item: Item) {
    val token = login()
    val post = submitPost(token, item)
    processPost(post)
}

fun login(): Token {
    // a blocking code
    return token
}

Asynchronní kód - callbacks

fun postItem(item: Item) {
    loginAsync { token ->
        submitPostAsync(token, item) { post ->
            processPost(post)
        }
    }
}

fun loginAsync(callback: () -> Token) {
    // make request and return immediately
    // arrange callback to be invoked later
}

Asynchronní kód - coroutines

suspend fun postItem(item: Item) {
    val token = login()
    val post = submitPost(token, item)
    processPost(post)
}

suspend fun login(): Token {

}
  • Klíčové slovo suspend
  • Suspend funkce jdou volat pouze z coroutines

Spouštění coroutines

GlobalScope.launch {
    syncData()
}
val deferredData : Deferred<Data> = GlobalScope.async {
    retrieveData()
}

Coroutines jdou zrušit

val job = GlobalScope.launch {
    syncData()
}
job.cancel();

Standarní ošetření výjimek

try {
    GlobalScope.launch {
        syncData()
    }
} catch (e: DataSynchronizationException) {
    //handle exception
}

Defaultně sekveční zpracování

suspend fun loadImage(url: String): Image {
    // blocking load
}

suspend fun getCombinedImage(url1: String, url2: String): Image {
    val image1 = loadImage(url1)
    val image2 = loadImage(url2)
    return combineImages(image1, image2)
}

Paralelní zpracování

suspend fun loadImage(url: String): Image {
    // blocking load
}

suspend fun getCombinedImage(url1: String, url2: String): Image {
    val image1 = GlobalScope.async { loadImage(url1) }
    val image2 = GlobalScope.async { loadImage(url2) }
    return combineImages(image1.await(), image2.await())
}

Structured concurrency

suspend fun loadImage(url: String): Image {
    // blocking load
}

suspend fun getCombinedImage(url1: String, url2: String): Image {
    return coroutineScope {
        val image1 = async { loadImage(url1) }
        val image2 = async { loadImage(url2) }
        combineImages(image1.await(), image2.await())
    }
}

Coroutines v Javě

  • Project Loom
  • Fibers

Kotlin je interoperabilní

Seamless

Prakticky nerozpoznáte Java třídu od Kotlinu
val file = File("temp.tmp")
if (file.exists()) {
    println("File exists")
}

POJO → Properties

public class User {

    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
val user = User("John")
println(user.name)

SAM konverze

//Executor.java
void execute(Runnable command) { ... }
executor.execute { println("runnable") }

Platform types

public class Generator {

    public String generateString() {
        return ...;
    }

}
val s = Generator().generateString()
 // String!
val s: String? = Generator().generateString()
// vždy projde
val s: String = Generator().generateString()
// může vyhodit NPE v runtime

Platform types

public class Generator {

    @NotNull
    public String generateString() {
        return ...;
    }

}
val s = Generator().generateString()
// String
public class Generator {

    @Nullable
    public String generateString() {
        return ...;
    }

}
val s = Generator().generateString()
// String?

Konverze Javy do Kotlinu

Závěr

  • Kotlin budu používat dál ;-)
    • Kotlin jde správnou cestou
    • Dává nám konkurenční výhodu
  • Java postupně přidává některé funkce, ale
    • Bude jí to trvat
    • Bude vždy konzervativní
    • 83% vývojářů je stále na Javě 8
    • Používání ne-LTS verzí má svá úskalí

Odkazy

Děkuji za pozornost.

www.JavaDays.cz
www.gopas.cz