Intro to Kotlin

Jeff Butler

https://github.com/jeffgbutler/practical-functional-kotlin

https://jeffgbutler.github.io/practical-functional-kotlin/

About Me

  • Enterprise Architect for DMI in Indianapolis
  • Committer to Apache iBatis/MyBatis since 2005
  • Creator of MyBatis Generator and MyBatis Dynamic SQL
  • Voracious reader, adventurous eater, lawn care enthusiast

My Dilemma

  • How do I teach a new programming language in an hour?
  • Answer - I don't
  • This talk is a little bit about why I think Kotlin is important, and then we'll do a whirl wind tour of some code

Java is not just about Java

"Most people talk about Java the language, and this may sound odd coming from me, but I could hardly care less. At the core of the Java ecosystem is the JVM."
James Gosling, Creator of the Java Programming Language(2011, TheServerSide.com)

What's Right with Java?

  • The JVM runs on virtually every platform - from your credit card to the largest mainframe
  • Cross platform compatibility has been a hallmark of Java from the very beginning
  • Huge investment in a highly stable and performant runtime
  • Many important and large systems are written in Java
  • The open source movement flourished with Java
  • Many important architectural concepts in use today have come from the Java/Open Source space

What's Wrong with Java?

  • Many Java code bases are unmitigated disasters
  • Many Java developers are undisciplined
  • Enterprise systems - EJBs, WebSphere, etc.
  • The Java language suffered from years of inattention
  • Java 8 is great, but very late and many clients have not upgraded
Java is a good language...there are some issues...you can make things as God awful as you want... it does allow people to create incredible messes (Gosling)

Kotlin is one of the most Java-ish things ever to happen to Java

What do I mean?

Java developers are skeptical...

  • We dislike the date/time API -> JodaTime
  • We dislike Core J2EE Patterns, EJBs, and JSF -> Spring Framework
  • We dislike Entity EJBs -> Hibernate
  • We dislike WebSphere -> JBoss
  • We dislike the Java language...

Other JVM Languages

  • Groovy (Dynamic) - Pivotal donated to Apache where projects go to die
  • Clojure (Lisp on JVM)
  • Scala (static types, functional, ML on the JVM)
  • Dozens more

¯\_(ツ)_/¯

Along Comes Kotlin

I dream that late one night, some JetBrains folks decide they have finally had enough of Java's lethargy. They figure they can build something better.

Kotlin Milestones

  • July 2011: Announced by JetBrains
  • February 2012: Open Sourced with Apache license
  • February 2016: Kotlin 1.0 Announced
  • March 2017: Kotlin 1.1 - JavaScript target environment
  • May 2017: Google announces first class support for Kotlin on Android
  • November 2017: Kotlin 1.2 - Shared Code Between JVM and JS
  • December 2017: Pivotal announces first class support for Kotlin in Spring Development

Why Kotlin?

  • Kotlin is not an academic language - it is practical
  • Kotlin does not make you change the way you think (unlike Scala and Clojure)
  • Seamless Java interop (the killer feature)
  • Makes Android development less painful (Android is the killer app for Kotlin)
  • JDK 6!

Building Kotlin

  • Same structure as Java - packages, classes, etc.
  • Builds with Maven/Gradle/Ant
  • Java and Kotlin can live side by side in the same project

Is it Just Syntax Sugar?

In some ways, Kotlin is all about syntax sugar

Some of it is a little like Lombok - but not really

All high level languages are just syntax sugar for assembler anyway - so we'll just get over it :)

Kotlin Language Features

  • Semicolons are optional and discouraged
  • Multiple classes in files
  • Very relaxed about classes, file names, etc.
  • Codifies many conventions leading to more concise code
  • Type inference
  • Lambdas before Java 8
  • Null Safety
  • Extension Functions
  • DSL Construction

Tour of Kotlin

Now we'll look at some code...

Variable Declaration

          
    val foo = "Fred"  // String type inferred, immutable, non-nullable
    var bar = "Barney"  // String type inferred, mutable, non-nullable
    var baz: String? = "Betty" // String type, mutable, nullable
          
        

Variables do not need to be in a class!

Function Definition

          
    fun helloWorld1(s: String): Unit {  // Unit is like void, and not necessary in this case
        println("Hello World!")
    }

    fun helloWorld2(s: String) = println("Hello World!")  // one line functions don't need braces

    fun add1(a: Int, b: Int): Int {
        return a + b
    }

    fun add2(a: Int, b: Int) = a + b  // return type of Int is inferred
          
        

Functions do not need to be in a class!

Simple Classes

          
    class MyCalculator1 (val firstName: String, val lastName: String) {
        fun sayHello() = "Hello!"
    }
          
        

Notes:

  • Classes are public by default
  • Functions are public by default
  • Constructor takes two non-nullable strings
  • Automatic getters for the constructor parameters

Interfaces

          
    interface Friendly {
        fun sayHello(): String

        fun isFriendly() = true
    }
          
        

Notes:

  • Interfaces are public by default
  • Functions are public by default
  • Methods can be abstract, or can have an implementation (like Java8 Default methods)
  • Methods can be private (like Java9)

Classes

          
    class MyCalculator2 (val firstName: String, val lastName: String) : Friendly {
        override fun sayHello(): String {
            return "Hi!  My name is " + fullName
        }

        val fullName: String
            get() = firstName + " " + lastName  // a "getter" in Kotlin
    }
          
        

Notes:

  • Implements Friendly, overrides sayHello()

Data Classes

          
    data class Person(val firstName : String, val lastName : String)
          
        

This class ...

  • is public, final
  • is null safe
  • is immutable
  • has getters, toString, equals, hashCode
  • has copy functions
  • has destructuring functions

If Expressions

Java

            
    public String renderGreeting(Boolean isFriendly) {
        if (isFriendly) {
            return "Hello!";
        } else {
            return "Go Away!";
        }
    }
            
          

Kotlin

            
    fun renderGreeting1(friendly: Boolean): String {
        return if (friendly) {
            "Hello!"
        } else {
            "Go Away!"
        }
    }
    
    fun renderGreeting2(friendly: Boolean) =
      if (friendly) "Hello!" else "Go Away!"
            
          

Notes

  • No need to create a variable, or to have multiple return statements
  • Because of this, Kotlin does not have a ternary operator

When Expressions - A Better Switch

Java

            
    public String calculateLastName(String firstName) {
      String rc;
      
      switch(firstName) {
      case "Fred":
      case "Wilma":
        rc = "Flintstone";
        break;
        
      case "Barney":
      case "Betty":
        rc = "Rubble";
        break;
      
      default:
        rc = "Unknown";
        break;
      }
      return rc;
    }
            
          

Kotlin

            
    fun calculateLastName(firstName: String): String {
        return when(firstName) {
            "Fred", "Wilma" -> "Flintstone"
            "Barney", "Betty" -> "Rubble"
            else -> { // can be a code block
                return "Unknown"
            }
        }
    }

    fun renderLifeStage1(age: Int): String {
        return when(age) {
            in 0..12 -> "Child"
            in 13..19 -> "Teenager"
            else -> "Adult"
        }
    }
            
          

When Expressions - Alternate Syntax

          
    fun renderLifeStage2(age: Int): String {
        return when {
            age < 13 -> "Child"
            age in 13..19 -> "Teenager"
            else -> "Adult"
        }
    }
          
        

Note: Any boolean expression is allowed

Sealed Classes

Sealed classes create a fixed hierarchy

          
  // all sealed classes must be in the same file...
  sealed class Animal(val name: String)
  class Dog(name: String, val akcNumber: Int) : Animal(name)
  class Cat(name: String, val cfaNumber: Int) : Animal(name)

  fun speak(animal: Animal): String {
      return when (animal) {
          is Dog -> "Bark"
          is Cat -> "Meow"
          // no otherwise/else!
      }
  }

  var myDog = Dog("Bob Barker", 22)  // no new keyword!
  var talk = speak(myDog)   // Bark
          
        

Avoids an ugly instanceof check

Kotlin Extension Functions

Same outcome as visitor, but far simpler to understand

            
data class Attribute (val name: String, val value: String)

// declare a "render" extension function
// extension functions can be in different packages,
// different JARs, different projects
fun Attribute.render(): String {
    // extension methods have access non-private
    // attributes and methods

    // this is a Kotlin string template
    return """$name="$value" """
}
            
          
            
val att = Attribute("foo", "bar")

val renderedString = att.render() // foo="bar"
            
          

Extension Functions and Smart Casting

          
  sealed class Animal(val name: String)
  class Dog(name: String, val akcNumber: Int) : Animal(name)
  class Cat(name: String, val cfaNumber: Int) : Animal(name)

  // add an extension method to Animal
  fun Animal.speak(): String {
      return when (this) {
          // string template preview!
          is Dog -> "Bark! My name is $name and my number is $akcNumber"
          is Cat -> "Meow! My name is $name and my number is $cfaNumber"
      }
  }

  var myDog = Dog("Bob Barker", 22)
  var talk = myDog.speak()  // Bark! My name is Bob Barker and my number is 22
          
        

Null Safety in Java

Every Java reference can be null. This requires a lot of null checking or Optional wrapping.

            
  public class Person {
    private Address mailingAddress;
    public Address getMailingAddress() {
      return mailingAddress;
    }
  }

  public class Address {
    private String secondAddresLine;
    public String getSecondAddressLine() {
      return secondAddresLine;
    }
  }
            
          
            
  public String getSecondMailingAddress(Person person) {
    if (person == null) {
      return "";
    }
    
    if (person.getMailingAddress() == null) {
      return "";
    }
    
    if (person.getMailingAddress().getSecondAddressLine()
        == null) {
      return "";
    }
    
    return person.getMailingAddress().getSecondAddressLine()
      .trim();
  }
  
            
          

Null Safety in Kotlin

Kotlin references can only be null if specifically declared to allow null

            
  data class Person (val mailingAddress: Address?)

  data class Address (val secondAddressLine: String?)
            
          
            
  String getSecondMailingAddress(person: Person?) =
    person?.mailingAddress?.secondAddressLine?.trim() ?: ""
            
          
  • Declare a null capable type with a following ?
  • The ?. operator is null safe navigation
  • The ?: operator (Elvis) is null coalescing

String Templates

          
  val bob = Person(homeAddress = Address("Main Street"), name = "Bob")

  val bobString1 = "My name is ${bob.name}, my home address is ${bob.homeAddress.line1}"

  // triple quoted string - preserves formatting
  val bobString2 = """|My name is "${bob.name}"
                      |  My home address is ${bob.homeAddress.line1}"""
                   .trimMargin("|")   // "|" is default and can be omitted
          
        

Operator Overloading

          
  class BankAccount (var balance: Double = 0.0){
      operator fun plusAssign(amount: Double) {  // overloads +=
          balance += amount
      }
  }
  
  val myAccount = BankAccount()
  myAccount.plusAssign(22.0)  // non overloaded call
  myAccount += 22.0  // over loaded call
          
        

Many operators can be overloaded - some in very clever ways. For example, if a class implements Comparable, you get <, >, <=, >= for free.

New operators cannot be defined...except infix...

Infix Functions

          
  class BankAccount (var balance: Double = 0.0){
      infix fun deposit(amount: Double) {
          balance += amount
      }
  
      infix fun `!&%`(amount: Double) {
          balance -= amount
      }
  }
  
  val myAccount = BankAccount()
  myAccount.deposit(22.0)  // non infix call
  myAccount deposit 22.0  // infix call
  myAccount `!&%` 33.0
          
        

Overview of Lambdas

  • Kotlin has "proper" function types - no weird SAM interfaces like Java 8
  • Kotlin can interact with Java SAM interfaces mostly automatically
  • Map/Filter/Reduce implemented via extension functions on the standard Java collections
  • Not lazy by default - so this is purely declarative vs. imperative
  • Can be made lazy
  • Works in Java 6

Simple Lambda

          
    @Test
    fun `simple lambda`() {
        val names = listOf("Fred", "Wilma", "Barney", "Betty")

        var str = names.filter ({ s -> s.startsWith("B") })
                .joinToString(",")
        assertThat(str).isEqualTo("Barney,Betty")
    }
          
        

Simple Lambda With "it"

          
    @Test
    fun `lambda with it`() {
        val names = listOf("Fred", "Wilma", "Barney", "Betty")

        var str = names.filter({ it.startsWith("B") })
                .joinToString(",")
        assertThat(str).isEqualTo("Barney,Betty")
    }
          
        

Note the "it" variable name

Lambda Pipeline

          
    @Test
    fun `lambda pipeline`() {
        val names = listOf("Fred", "Wilma", "Barney", "Betty")

        var str = names.filter({ it.startsWith("B") })
                .map({ it.toUpperCase() })
                .joinToString(",")
        assertThat(str).isEqualTo("BARNEY,BETTY")
    }
          
        

Combination Functions

          
    @Test
    fun `lambda combination functions`() {
        val names = listOf("Fred", "Wilma", "Barney", "Betty")

        var str = names.filter({ it.startsWith("B") })
                .joinToString(",", transform = { it.toUpperCase() })
        assertThat(str).isEqualTo("BARNEY,BETTY")
    }
          
        

Lambda Outside Parenthesis

          
    @Test
    fun `lambda outside parenthesis`() {
        val names = listOf("Fred", "Wilma", "Barney", "Betty")

        var str = names.filter { it.startsWith("B") }
                .map { it.toUpperCase() }
                .joinToString(",")
        assertThat(str).isEqualTo("BARNEY,BETTY")
    }
          
        

Method Reference

          
    @Test
    fun `method references`() {
        val names = listOf("Fred", "Wilma", "Barney", "Betty")

        var str = names.filter { it.startsWith("B") }
                .map(String::toUpperCase)
                .joinToString(",")
        assertThat(str).isEqualTo("BARNEY,BETTY")
    }
          
        

Function Variables and Local Functions

          
    @Test
    fun `function variables and local functions`() {
        val upperCase = { s: String -> s.toUpperCase() }  // function variable
        fun String.startsWithB() = startsWith("B")  // local extension function

        val names = listOf("Fred", "Wilma", "Barney", "Betty")

        var str = names.filter { it.startsWithB() }
                .map { upperCase(it) }
                .joinToString(",")
        assertThat(str).isEqualTo("BARNEY,BETTY")
    }
          
        

Higher Order Functions

          
    @Test
    fun `higher order functions`() {
        val names = listOf("Fred", "Wilma", "Barney", "Betty")

        var str = names.filter { it.startsWith("B") }
                .map(upperCaseIt())
                .joinToString(",")
        assertThat(str).isEqualTo("BARNEY,BETTY")
    }

    // return a function that takes a String and returns a String
    // "(String) -> String" is the return type
    private fun upperCaseIt(): (String) -> String = { it.toUpperCase() }
    private fun upperCaseIt2() = { s: String -> s.toUpperCase() }  // using inferred type
          
        

Type Safe Builders and DSL Construction

The "crown jewel" of Kotlin. Uses "clean syntax" features to create a natural looking DSL

  • Extension Functions
  • Operator overloading
  • Infix calls
  • Lambdas Outside Parenthesis
  • Functions with Receivers (some other talk)

DSL Example

          
fun result(args: Array<String>) =
    html {
        head {
            title {+"XML encoding with Kotlin"}
        }
        body {
            h1 {+"XML encoding with Kotlin"}
            p  {+"this format can be used as an alternative markup to XML"}

            a(href = "http://kotlinlang.org") {+"Kotlin"}

            p {
                +"This is some"
                b {+"mixed"}
                +"text. For more see the"
                a(href = "http://kotlinlang.org") {+"Kotlin"}
                +"project"
            }
            p {+"some text"}

            p {
                for (arg in args)
                    +arg
            }
        }
    }
          
        

Next Steps

  • Install an IDE
    • IntelliJ IDEA (community edition is OK) - Best
    • Eclipse with JetBrains Kotlin Plugin - Not Terrible
    • VS Code? No support except syntax highlighting and Maven
  • Do the Kotlin Koans workshop: https://github.com/Kotlin/kotlin-koans
  • Docs: https://kotlinlang.org/docs/reference/
  • "Kotlin in Action" - decent book
  • Look for seams where Kotlin can fit in to your existing projects (tests are an obvious start)