KOTLIN: A BETTER JAVA FROM JETBRAINS

Luca Franceschini

PhD seminars, 20 September 2018

GOALS

  • Concise syntax
  • Object-orientation
  • Statically typed
  • Multiplatform
  • Interoperable with Java
  • IDE support

JAVA IS FINE

but...

LET'S WRITE A JAVA CLASS

public class Student {
    int idNumber;
    String name, email;
}
    

MINIMIZE ACCESSIBILITY

public class Student {
    private int idNumber;
    private String name, email;
}
    

WE NEED A CONSTRUCTOR

public class Student {
    private int idNumber;
    private String name, email;

    public Student(int idNumber, String name, String email) {
        this.idNumber = idNumber;
        this.name = name;
        this.email = email;
    }
}

NULL CHECKS

public class Student {
    private int idNumber;
    private String name, email;

    public Student(int idNumber, String name, String email) {
        this.idNumber = idNumber;
        this.name = Objects.requireNonNull(name);
        this.email = Objects.requireNonNull(email);
    }
}

USE ACCESSOR METHODS

public class Student {
    private int idNumber;
    private String name, email;

    public Student(int idNumber, String name, String email) {
        this.idNumber = idNumber;
        this.name = Objects.requireNonNull(name);
        this.email = Objects.requireNonNull(email);
    }

    public int idNumber() { return idNumber; }
    public String name()  { return name;     }
    public String email() { return email;    }
}

MINIMIZE MUTABILITY

public class Student {
    private final int idNumber;
    private final String name, email;

    public Student(int idNumber, String name, String email) {
        this.idNumber = idNumber;
        this.name = Objects.requireNonNull(name);
        this.email = Objects.requireNonNull(email);
    }

    public int idNumber() { return idNumber; }
    public String name()  { return name;     }
    public String email() { return email;    }
}

INHERITANCE IS HARD

public final class Student {
    private final int idNumber;
    private final String name, email;

    public Student(int idNumber, String name, String email) {
        this.idNumber = idNumber;
        this.name = Objects.requireNonNull(name);
        this.email = Objects.requireNonNull(email);
    }

    public int idNumber() { return idNumber; }
    public String name()  { return name;     }
    public String email() { return email;    }
}

CORRECTLY OVERRIDE EQUALS

public final class Student {
    private final int idNumber;
    private final String name, email;

    public Student(int idNumber, String name, String email) {
        this.idNumber = idNumber;
        this.name = Objects.requireNonNull(name);
        this.email = Objects.requireNonNull(email);
    }

    public int idNumber() { return idNumber; }
    public String name()  { return name;     }
    public String email() { return email;    }

    @Override public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Student)) return false;
        Student s = (Student) o;
        return idNumber == s.idNumber && name.equals(s.name) && email.equals(s.email);
    }
}

... THEN ALSO HASHCODE

public final class Student {
    private final int idNumber;
    private final String name, email;

    public Student(int idNumber, String name, String email) {
        this.idNumber = idNumber;
        this.name = Objects.requireNonNull(name);
        this.email = Objects.requireNonNull(email);
    }

    public int idNumber() { return idNumber; }
    public String name()  { return name;     }
    public String email() { return email;    }

    @Override public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Student)) return false;
        Student s = (Student) o;
        return idNumber == s.idNumber && name.equals(s.name) && email.equals(s.email);
    }

    @Override public int hashCode() {
        int result = Integer.hashCode(idNumber);
        result = 31 * result + name.hashCode();
        result = 31 * result + email.hashCode();
        return result;
    }
}

ALWAYS OVERRIDE TOSTRING

public final class Student {
    private final int idNumber;
    private final String name, email;

    public Student(int idNumber, String name, String email) {
        this.idNumber = idNumber;
        this.name = Objects.requireNonNull(name);
        this.email = Objects.requireNonNull(email);
    }

    public int idNumber() { return idNumber; }
    public String name()  { return name;     }
    public String email() { return email;    }

    @Override public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Student)) return false;
        Student s = (Student) o;
        return idNumber == s.idNumber && name.equals(s.name) && email.equals(s.email);
    }

    @Override public int hashCode() {
        int result = Integer.hashCode(idNumber);
        result = 31 * result + name.hashCode();
        result = 31 * result + email.hashCode();
        return result;
    }

    @Override public String toString() {
        return String.format("Student{idNumber=%d,name=%s,email=%s}", idNumber, name, email);
    }
}

BOILERPLATE!

DATA CLASSES

data class Student(
    val idNumber: Int,
    val name: String,
    val email: String)
  • constructor
  • null-safety
  • immutability
  • getters

Automatically gives

  • equals()
  • hashCode()
  • copy()
  • toString()

OBJECT-ORIENTATION

  • All types in the same hierarchy
    - top is
  • No primitive types
    - like Python
  • No implicit conversions
    - remember JavaScript?
  • Explicit ones usually not needed either
    - type inference does its thing
Any

... and abstract classes, interfaces, enums etc.

STATIC TYPING
AND
CONCISENESS?

Type inference!

val strings = setOf("a", "b", "c", "cc")

val (idNumber, name, email) = student

for ((key, value) in map) { ... }

max(strings, { a, b -> a.length < b.length })

FLOW-SENSITIVE ANALYSIS

A.k.a. "smart casts"

if (x is String && x.length > 0) {
    print(x.length)
}

NULL SAFETY

No "billion dollars mistake"

val s: String = null // compilation error
  • No null pointer exceptions
  • No null checking
  • Compile-time null-safety

NULLABLE TYPES

If you really need them

val s: String = null // compilation error
val s2: String? = null // ok

Still some guarantees:

s2.length // error: variable 's2' can be null

HOW TO USE NULLABLES

(without making everything nullable)

  • Safe call:

     
  • Elvis operator:

     
  • Not-null assertion:

     
  • Flow-sensitive analysis
student?.email?.length
student?.email ?: "default@email.com"
student!!.email
if (student != null) { ...

WHY NULLS IN THE FIRST PLACE?

Java interoperability

import foo.Bar;

var x: String? = Bar.method()

Getters, setters and boolean methods follow standard naming conventions

MULTIPLATFORM

Kotlin/JVM

Kotlin/JS

Kotlin/JVM

Kotlin/Native

Kotlin/Native

The Java promise: "write once, run everywhere"

MULTIPLATFORM PROJECTS

  • Common modules
    - Code not specific to any platform
    - Declaration (no implementation) of platform specific code
    - Only Kotlin code, only Kotlin libraries

     
  • Platform modules
    - Implementation of declared platform specific code
    - Also platform specific libraries
    - Also JVM languages if targeting JVM

EXPECT/ACTUAL

(old ideas...)

package foo

expect class Foo {
    fun method()
}
package foo

actual class Foo {
    actual fun method() {
        ...
    }
}

Common code

Platform code

CONCLUSION

Things I like:

  • Static typing + inference
  • Null safety
  • Syntax

 

Things I don't:

  • Weird default behavior with Java

 

Other nice things:

  • Coroutines

QUESTIONS?

Kotlin

By Luca Franceschini

Kotlin

  • 700