Kotlin – your 2017 Java replacement

Kotlin – your 2017 Java replacement

Do you dream about life without unexpected nulls? Do you fancy a little bit of functional programming? Are you tired of Java ceremony?

Then maybe 2017 is The Right Moment to choose Kotlin! This JVM language brings you stuff like null-safety and immutability by default. Less boilerplate, more safety. And you can introduce it in your project class after class, module after module, without risky revolution.

I will show you pros and cons of Kotlin, based on last 8 months of my experience. In November 2016 I started a new backend project built on top of Gradle and Spring. Today I still do not regret the decision to write it in Kotlin. Let's see if I convince you! :-)

A246cc5f7f99987d6a33947485e69b15?s=128

Paweł Barszcz

June 21, 2017
Tweet

Transcript

  1. 2.

    ?

  2. 5.
  3. 8.

  4. 9.

  5. 10.
  6. 11.

    Vavr - Functional Java Done Right
 Grzegorz Piwowarek @ Confitura

    2016 more „classic” projects greenfield project
 solo on backend
 head full of ideas April
 2016 October
 2016 Kotlin here, Kotlin there
  7. 13.

    stack Kotlin 1.0 –> 1.1 JUnit 4
 - AssertJ
 -

    JUnitParams Spring Boot 1.3 –> 1.5
 - web
 - security
 - jdbc
 - test
  8. 14.

    8 months later… Kotlin in the whole service team of

    2 devs 3 months on (real) production still no regret
  9. 16.
  10. 17.

    class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random())
  11. 18.

    class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) name –> type
  12. 19.

    class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) fields declared as
 constructor arguments
  13. 20.

    class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) defaults
  14. 21.

    class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) named parameters
  15. 22.

    class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) String interpolation
  16. 23.

    class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) no type, because…
 type inference
  17. 24.

    class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) smart `when` compiles
 if all options are covered
  18. 25.

    class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) expressions everywhere
 `if`, `when`, `try`…
  19. 26.

    class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) function as a single expression
  20. 28.

    var map1: Map<String, Int> = mapOf("A" to 1) val map2:

    MutableMap<String, Int> = mutableMapOf("B" to 2) fun whatever() { map1 += ("A" to 111) map2["B"] = 222 } modification
 mutable collections only shorter = immutable
  21. 29.

    var map1: Map<String, Int> = mapOf("A" to 1) val map2:

    MutableMap<String, Int> = mutableMapOf("B" to 2) fun whatever() { map1 += ("A" to 111) map2["B"] = 222 } reassignment
 `var` only var ≠ val
  22. 31.

    data class Color( val red: Pigment, val green: Pigment, val

    blue: Pigment, val alpha: Alpha = Alpha.none() ) { fun transparent() = copy(alpha = Alpha.medium()) } out of the box: - copy
  23. 32.

    data class Color( val red: Pigment, val green: Pigment, val

    blue: Pigment, val alpha: Alpha = Alpha.none() ) { fun transparent() = copy(alpha = Alpha.medium()) } val (red, green, blue, alpha) = myColor out of the box: - copy - destructuring
  24. 33.

    data class Color( val red: Pigment, val green: Pigment, val

    blue: Pigment, val alpha: Alpha = Alpha.none() ) { fun transparent() = copy(alpha = Alpha.medium()) } out of the box: - copy - destructuring - equal & hashCode - toString
  25. 36.

    everything is final • Java –> „final” on demand
 Kotlin

    –> „open” on demand • Spring, Mockito, … how to proxy? • kotlin-allopen / kotlin-spring
 blog.jetbrains.com/kotlin/2016/12/kotlin-1-0-6-is-here
  26. 37.

    buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-allopen:1.1.2-5" } } apply plugin:

    "kotlin-allopen" allOpen { annotation("com.your.Annotation") } everything is final
  27. 50.

    val project: Project? = projectRepository.findProjectBy(projectId) if (project == null) {

    throw ProjectNotFound() } return project.tasks compiler is smart
  28. 55.

    override fun configure(webSecurity: WebSecurity) { webSecurity .ignoring() .mvcMatchers(/* ... */)

    } override fun configure(webSecurity: WebSecurity?) { webSecurity!! .ignoring() .mvcMatchers(/* ... */) } override fun configure4(webSecurity: WebSecurity?) { webSecurity ?.ignoring() ?.mvcMatchers(/* ... */) } when null comes from Java
  29. 56.

    override fun configure(webSecurity: WebSecurity) { webSecurity .ignoring() .mvcMatchers(/* ... */)

    } override fun configure(webSecurity: WebSecurity?) { webSecurity!! .ignoring() .mvcMatchers(/* ... */) } override fun configure4(webSecurity: WebSecurity?) { webSecurity ?.ignoring() ?.mvcMatchers(/* ... */) } when null comes from Java
  30. 57.

    override fun configure(webSecurity: WebSecurity) { webSecurity .ignoring() .mvcMatchers(/* ... */)

    } override fun configure(webSecurity: WebSecurity?) { webSecurity!! .ignoring() .mvcMatchers(/* ... */) } override fun configure4(webSecurity: WebSecurity?) { webSecurity ?.ignoring() ?.mvcMatchers(/* ... */) } when null comes from Java
  31. 58.

    override fun configure(webSecurity: WebSecurity) { webSecurity .ignoring() .mvcMatchers(/* ... */)

    } override fun configure(webSecurity: WebSecurity?) { webSecurity!! .ignoring() .mvcMatchers(/* ... */) } override fun configure4(webSecurity: WebSecurity?) { webSecurity ?.ignoring() ?.mvcMatchers(/* ... */) } when null comes from Java
  32. 62.

    class ActiveProjectsJson( val projects: List<ProjectJson> ) class ArchivedProjectsJson( val archivedProjects:

    List<ProjectJson> ) class ProjectJson( val id: String, val name: String, val color: String ) class ProjectUpdateJson( val name: String?, val color: String? ) …request/response bodies?
  33. 63.

    class ActiveProjectsJson( val projects: List<ProjectJson> ) class ArchivedProjectsJson( val archivedProjects:

    List<ProjectJson> ) class ProjectJson( val id: String, val name: String, val color: String ) class ProjectUpdateJson( val name: String?, val color: String? ) …request/response bodies?
  34. 64.

    class ActiveProjectsJson( val projects: List<ProjectJson> ) class ArchivedProjectsJson( val archivedProjects:

    List<ProjectJson> ) class ProjectJson( val id: String, val name: String, val color: String ) class ProjectUpdateJson( val name: String?, val color: String? ) …request/response bodies? in one file
 if you prefer
  35. 69.

    jsonObject( "name" to "John", "age" to 29, "phoneNumbers" to jsonArray(

    jsonObject( "type" to "personal", "number" to "123 456 789" ), jsonObject( "type" to "office", "number" to "987 654 321" ) ) ).toString() Kotson
  36. 72.

    …SQL? • me & Hibernate = • me & tables

    coupled with domain entities = • me & types = ❤
  37. 74.

    object ProjectsTable : Table("projects") { val id = long("id").autoIncrement().primaryKey() val

    ownerId = (long("owner") references UsersTable.id) val name = varchar("name", 50) val isArchived = bool("is_archived").default(false) } Exposed
  38. 76.

    ProjectsTable .select { ProjectsTable.id eq projectId } .map { row

    -> Project( id = row[ProjectsTable.id], name = row[ProjectsTable.name], isArchived = row[ProjectsTable.isArchived] ) } .firstOrNull() Exposed
  39. 81.

    return Try { fetchProjectsOverHttp() // may throw an exception }.handle

    { throw ConnectionFailed() }.onEach { response -> logger.info { response } }.flatMap { response -> Try { parse(response) } .handle { throw ResponseParsingFailed() } }.fold({ result -> result.projects },
 { exception -> throw exception })
  40. 82.

    return Try { fetchProjectsOverHttp() // may throw an exception }.handle

    { throw ConnectionFailed() }.onEach { response -> logger.info { response } }.flatMap { response -> Try { parse(response) } .handle { throw ResponseParsingFailed() } }.fold({ result -> result.projects },
 { exception -> throw exception }) wrap value in Try.Sucess
 wrap exception in Try.Failure
  41. 83.

    return Try { fetchProjectsOverHttp() // may throw an exception }.handle

    { throw ConnectionFailed() }.onEach { response -> logger.info { response } }.flatMap { response -> Try { parse(response) } .handle { throw ResponseParsingFailed() } }.fold({ result -> result.projects },
 { exception -> throw exception }) do something
 with Try.Failure
  42. 84.

    return Try { fetchProjectsOverHttp() // may throw an exception }.handle

    { throw ConnectionFailed() }.onEach { response -> logger.info { response } }.flatMap { response -> Try { parse(response) } .handle { throw ResponseParsingFailed() } }.fold({ result -> result.projects },
 { exception -> throw exception }) do something
 with Try.Success
  43. 85.

    return Try { fetchProjectsOverHttp() // may throw an exception }.handle

    { throw ConnectionFailed() }.onEach { response -> logger.info { response } }.flatMap { response -> Try { parse(response) } .handle { throw ResponseParsingFailed() } }.fold({ result -> result.projects },
 { exception -> throw exception }) nested Try logic
 inside `flatMap`
  44. 86.

    return Try { fetchProjectsOverHttp() // may throw an exception }.handle

    { throw ConnectionFailed() }.onEach { response -> logger.info { response } }.flatMap { response -> Try { parse(response) } .handle { throw ResponseParsingFailed() } }.fold({ result -> result.projects },
 { exception -> throw exception }) unwrap value or exception
  45. 94.
  46. 96.

    • maintained by JetBrains • easy to start with •

    nice syntax, immutability, data classes • works well with Spring • Kotson, Exposed, funKTionale • null-safety • low risk, pragmatic approach
  47. 97.

    • maintained by JetBrains • easy to start with •

    nice syntax, immutability, data classes • works well with Spring • Kotson, Exposed, funKTionale • null-safety • low risk, pragmatic approach
  48. 98.
  49. 99.
  50. 100.

    want more? • type aliases • couroutines (async/await/yield) • lazy

    sequences • sealed classes • gradle-script-kotlin • …
  51. 101.

    • Spring Boot 1.5 + Kotlin + customized Exposed (blog

    post + git repo)
 spring.io/blog/2016/03/20/a-geospatial-messenger-with-kotlin-spring-boot-and-postgresql • functional Spring 5 + Kotlin (blog post + git repo)
 spring.io/blog/2017/01/04/introducing-kotlin-support-in-spring-framework-5-0 • reference 
 kotlinlang.org/docs/reference • links, libs, blogs…
 kotlin.link • community 
 slack.kotlinlang.org • these slides 
 speakerdeck.com/nkoder/kotlin-your-2017-java-replacement