Scope funkce v Kotlinu

Cílem block functions je vykonat blok kódu v kontextu daného kódu. To znamená, že pokud zavoláte takovou funkci na objektu s lambdou, vytvoří se rozsah (scope), ve kterém můžete k objektu přistupovat bez uvedení jeho jména. Tyto funkce se mezi sebou liší tím, jak je možné k objektu v rámci scope přistoupit a jaký je výsledek celého výrazu.

Číst dálScope funkce v Kotlinu

Rozdíl mezi year-of-era a week-based-year

V rámci java.time.format.DateTimeFormatter je možné použít pro formátování roku malá y a velká Y. Špatně zvolené y může znamenat špatně dohledatelnou chybu. Malé y znamená year-of-era, což vytvoří očekávaný výstup, kdy 31.12. bude vždy jako poslední den v daném roce. Velké Y znamená week-based-year, což v případě týdnu, který spadá do více roků, vytvoří neočekávaný výstup. Pokud Silvestr (či pár předchozích dnů) připadnou do týdne, který nemá 31.12. v sobotu, budou dny poslední dny roku z přelomového týdne patřit do následujího roku. Příklad bude nejlepší. Takto to funguje na Linuxu, ale např. na Window byl výstup s velkým Y pro rok 2021 stejný jako pro malé y.

Číst dálRozdíl mezi year-of-era a week-based-year

Chyba: TransactionRequiredException Executing an update/delete query

Pro repository test na Micronautovi jsem dostával chybu TransactionRequiredException Executing an update/delete query při exekuci sql skriptu ze souboru.

@Inject
lateinit var entityManager: EntityManager
...
val fileNameString = "/db/script/$fileName"
val sql = this::class.java.getResource(fileNameString)?.readText()
entityManager.createNativeQuery(sql).executeUpdate()

Řešením bylo použití SessionFactory a transakcí

@Inject
lateinit var sessionFactory: SessionFactory
...
val fileNameString = "/db/script/$fileName"
val sql = this::class.java.getResource(fileNameString)?.readText()
sessionFactory.openSession()
    ?.let { session ->
        session.entityManagerFactory.createEntityManager()
            ?.let { entityManager ->
                entityManager.transaction.begin()
                entityManager.createNativeQuery(sql).executeUpdate()
                entityManager.transaction.commit()
            }
    }

Vararg a spread operátory v Kotlinu

Hvězdička (*) je spread operátor, který rozbalí pole do seznamu hodnot.
Parametr vararg umožňuje předat do funkce libovolné množství argumentů. Každá funkce může mít maximálně jeden vararg operátor.

fun main() {
    val lines = arrayOf("First line", "Second line")
    
    printOnLines("")
    
    printOnLines(header = "Header")
    
    printOnLines(header = "Header", lines = lines)

    printOnLines(header = "Header", lines = arrayOf("First line", "Second line"))
    
    printOnLines("Header", *lines)

    printOnLines(header = "Header", *lines)
    
    printOnLines("Header", "First line", "Second line")

    printLines(*lines)
}

fun printOnLines(header: String, vararg lines: String) {
    println(header)
    println("----------------------------")
    lines.forEach {
        println(it)
    }
}

fun printLines(vararg lines: String) {
    lines.forEach { 
        println(it)
    }
}

Měření času v Kotlinu

Pro změření jak dlouho trvá vykonání určitého bloku existují v Kotlinu funkce measureTimeMillis a measureNanoTime. Měřený kód stačí vložit do bloku

val timeMillis = measureTimeMillis {
    ...
}
println("Completed in $timeMillis ms")

val timeNano = measureNanoTime {
    ...
}
println("Completed in $timeNano ns")

Webflux a BlockHound

BlockHound je java agent (software nahraný JVM před zavoláním main metody), který způsobí to, že pokud v rámci neblokujícího vlákna (vlákno, které nemá volat blokující volání) zavoláte metodu, která blokuje vlákno, vyhodí se chyba.

Závislost

implementation("io.projectreactor.tools:blockhound:1.0.4.RELEASE")

Pokud si chceme BlockHound přizpůsobit, vytvoříme třídu, která implementuje BlockHoundIntegration

Číst dálWebflux a BlockHound

Posílání chyb do Sentry

Sentry je primárně zaměřeno na chyby. Je to místo, kam budou všechny pády systému reportovány. Na sentry si musíte vytvořit účet a nastavit projekt tak, aby chyby posílal do sentry a mít je na jednom místě.

build.gradle

implementation("io.sentry:sentry-spring-boot-starter:4.3.0")

application.yml
Tato data dostanete při registraci do sentry.

sentry:
    dsn: https://xxx@yyy.ingest.sentry.io/zzz

Kód, který pošle chybu do sentry.

try {
    throw Exception("This is a v1 test.")
} catch (e: Exception) {
    Sentry.captureException(e)
}

Je možné přidat tento kód do GlobalErrorHandleru.

@ControllerAdvice
@RestController
class GlobalErrorHandler {
    private val logger: Logger = LoggerFactory.getLogger(javaClass)

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    fun handle(e: Exception) {
        logger.error("Error", e)
        Sentry.captureException(e)
    }
}

Kotlin JavaFX Hello World

Build.gradle.kts

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.4.10"
    id("org.openjfx.javafxplugin") version "0.0.9"
}

group = "cz.vitfo.redgui"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

tasks.withType() {
    kotlinOptions.jvmTarget = "11"
}

javafx {
    version = "15.0.1"
    modules = listOf("javafx.controls")
}

Třída dědící z javafx.application.Application

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

class FXApplication : Application() {
    override fun start(stage: Stage) {
        val javaVersion = System.getProperty("java.version")
        val javafxVersion = System.getProperty("javafx.version")
        val l = Label("Hello, JavaFX $javafxVersion, running on Java $javaVersion.")
        val scene = Scene(StackPane(l), 640.0, 480.0)
        stage.scene = scene
        stage.show()
    }

    fun main(args: Array) {
        launch()
    }
}

fun main(args: Array) {
    FXApplication().main(args)
}

Výsledná aplikace

Zdroj: github.com/openjfx/samples/blob/master/HelloFX/Gradle/hellofx/src/main/java/HelloFX.java

Kotlin Swing Hello World

import java.awt.EventQueue
import javax.swing.JFrame

class Application : JFrame() {

    init {
        title = "Hello world"
        defaultCloseOperation = JFrame.EXIT_ON_CLOSE
        setSize(400, 300)
        setLocationRelativeTo(null)
    }
}

fun main() {
    EventQueue.invokeLater {
        val window = Application()
        window.isVisible = true
    }
}

Přehled jednotlivých komponent Swingu: web.mit.edu/6.005/www/sp14/psets/ps4/java-6-tutorial/components.html

Výpis chyby při použítí vykřičníků v Kotlinu

Kotlin kompilátor kontroluje, zda hodnota výrazu může být null a pokud tomu tak je, kód nezkompiluje dokud se v daném místě neprovede kontrola na not null. Programátor si může vynutit, že chce danou hodnotu použít i bez této kontroly pokud si je jist, že hodnota není null pomocí dvou vykřičníků. Pokud hodnota je null, dojde k vyhození kotlin.KotlinNullPointerException.

Nedoporučuje se používat více !! (one celkově se !! mají používat pouze vyjímečně ve zdůvodněných případech) na jednom řádku, protože výpis v logu vám neřekne, která hodnota byla null.

Příklad

class Aa (var b: Bb?)

class Bb (var text: String?)

Výsledky volání

println(a!!.b!!.text!!.toUpperCase())

Exception in thread "main" kotlin.KotlinNullPointerException
	at cz.vitfo.NullKt.main(Null.kt:8)
	at cz.vitfo.NullKt.main(Null.kt)
println(aWithB!!.b!!.text!!.toUpperCase())

Exception in thread "main" kotlin.KotlinNullPointerException
	at cz.vitfo.NullKt.main(Null.kt:8)
	at cz.vitfo.NullKt.main(Null.kt)

Je vidět, že v logu je pouze řádek, kde došlo k vyhození výjimky, nikoliv přesně která část výrazu.