Kotlin for Java Developers
Agenda (non-linear)
• Basic language concepts
• Examples of the Kotlin Standard Library
• Coroutines
• Language performance characteristics
• Goal of this talk: to introduce Kotlin, not to teach it
What is Kotlin?
• Introduced in 2011 by JetBrains (the Intellij folks)
• Goal was to offer a “better Java”, not unlike Scala, but without Scala’s
slow compilation times
• Compiles to JVM bytecode, as well as JavaScript and native binaries
for various operating systems, including Android and iOS
• Kotlin can call Java libraries, and Java can call Kotlin libraries (with
limitations)
• https://kotlinfoundation.org/ - Backed by JetBrains, Google, Gradle
and more.
Why Kotlin?
• Whereas Scala adds “everything”, Kotlin adds “just enough”
• Well thought out “syntactic sugar”makes for a much more expressive
language, without becoming inaccessible
• While still evolving, backwards compatibility is prioritized
• Excellent Intellij support, but also a good Eclipse plugin
• Intellij can convert your Java code to Kotlin for you!
• The language of choice for Android development, and the the default
DSL for new Gradle builds
HelloWorld.kt
fun main() {
println("Hello, World")
}
public final class HelloWorldKt {
public static final void main() {
System.out.println("Hello, World");
}
public static void main(String[] var0) {
main();
}
}
Semicolon
inference
HelloWorld2.kt
class HelloWorld2 {
companion object {
@JvmStatic
fun main(args:
Array<String>) {
println("Hello, World")
}
}
}
How Kotlin
does static
public final class HelloWorld2 {
public static final Companion Companion =
new Companion();
public static final void main(String[] args) {
Companion.main(args);
}
public static final class Companion {
public final void main(String[] args) {
System.out.println("Hello, World");
}
private Companion() {
// object in Kotlin is a singleton
}
}
}
Functions
fun double(n: Int): Int {
return n * 2
}
Type follows name
Single expression function
Type inference
Equivalent to : Unit
fun quadruple(n: Int) = n * 4
fun say(text: String): Unit {
println(text)
}
fun shout(text: String) {
println(text.toUpperCase())
}
fun triple(n: Int): Int = n * 3
Default and named parameters
fun log(message: String, level: String = "INFO", logger: String = "DEFAULT") =
println("$logger [$level] $message")
fun main() {
log("all is well")
log("uh oh", "WARN")
log(logger = "THERMAL", level = "FATAL", message = "Fire!")
}
• Convenient way to do method overloading
• Named arguments in constructors largely eliminate the need for the Builder
Pattern
DEFAULT [INFO] all is well
DEFAULT [WARN] uh oh
THERMAL [FATAL] Fire!
class Person(val name: String, var age: Int, heightInInches: Int) {
private val heightInCm: Int = (heightInInches * 2.54).toInt()
val heightInMeters: Double
get() = heightInCm / 100.0
fun failHeightInMeters(): Double = heightInInches * 0.0254
// ❌Unresolved reference: heightInInches
}
fun main() {
val founder = Person("Jeff", 58, 67)
val name = founder.name
// time marches on
founder.age = 59
}
Classes
public static final void main() {
Person founder = new Person(”Jeff", 58, 67);
String name = founder.getName(); // not field reference!
// time marches on
founder.setAge(55); // not field reference!
}
Final
property
Mutable
property
No “new”
Just a constructor
parameter, not a property
Data Classes
data class RgbColor(val red: Int, val blue: Int, val green: Int)
Class body optional
fun main() {
val yellow = RgbColor(255, 255, 0)
println(yellow) // prints "RgbColor(red=255, blue=255, green=0)"
if (yellow == RgbColor(255, 255, 0)) { // auto-generated equals
println("matches")
}
val magenta = yellow.copy(blue = 0, green = 255)
val (r, g, b) = yellow // destructuring
}
.equals(…)
Null Safety – Optional as a language feature
class Contact(val name: String, val phone: String?)
fun produce() {
val noPhone = Contact("Bob", null) // OK
val noName = Contact(null, "867-5309") // ❌Null can not be a value of a non-null type String
}
fun consume(contact: Contact) {
val nameLength: String = contact.name.length // OK
val phoneLength = contact.phone.length
// ❌ Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
val nullablePhoneLength = contact.phone?.length // type: Int?
val jenny = Contact("Jenny", "867-5309")
val phone: String = jenny.phone!! // could throw NPE
}
Nullable
Non-nullable
Branching
fun salutations(arriving: Boolean) {
if(arriving) {
println("hello")
}
else {
println("goodbye")
}
}
fun salutations2(arriving: Boolean): String = if(arriving) "hello" else "goodbye" //ternary
fun size(item: Any): Int = when(item) {
"meaning of life" -> 42
is String -> item.length
is Collection<*> -> item.size
is Number -> item.toInt()
else -> throw IllegalArgumentException("cannot determine size of $item")
}
item is “smart cast”
String template
Looping
fun repeatGreet(times: Int) {
for (i in 1..times) {
println("hello $i")
}
}
fun repeatGreetExpanded(times: Int) {
val range = IntRange(1, times)
for (i in range) {
println("hello $i")
}
}
public void repeatGreet(int times) {
for (int i = 1; i <= times; i++) {
System.out.println("hello");
}
}
public void repeatGreetExpanded(int times) {
IntRange range = new IntRange(1, times);
int var1 = range.getLast();
for (int i = 1; i <= var1; i++) {
System.out.println("hello");
}
}
Extension methods
fun Int.double() = this * 2
fun Int?.add(summand: Int) = (this ?: 0) + summand
fun main() {
println(5.double()) // 10
println(5.add(3)) // 8
println(null.add(3)) // 3
}
• Syntactic Sugar for static methods whose first argument is the
receiver type
• Extension methods must be imported to be used
• Many StdLib extension methods are auto-imported.
Can apply to null
Functional Parameters / Lambdas
functional
signature
fun transform(n: Int, transformer: (Int) -> Int) {
println("$n maps to ${transformer(n)}")
}
fun main() {
transform(3, {i -> i * 2})
transform(4) {i -> i * 3}
transform(5) { it * 4 }
fun square(n: Int) = n * n
transform(6, ::square)
}
function
invocation
3 maps to 6
4 maps to 12
5 maps to 20
6 maps to 36
last param lambda syntax
“it” for single parameter
method reference
Inline Functions
inline fun <T> maxBy(a: T, b: T, ranker: (T) -> Int) =
if (ranker(a) > ranker(b)) a else b
• If possible, invocations will be replaced with function body
• Especially useful for megamorphic call sites in loops.
• Used extensively in the Kotlin Collections API and other Standard Library
functions (as appropriate)
• Allows for clean syntax without performance penalties
• ⚠️ Changes require recompilation of client code
arrayOf("a", "b", "c", "d").forEach(::println) // traditional for loop
Value Classes – type safety, for free
public final class CustomerId {
private final long id;
private CustomerId(long id) { this.id = id; }
public final long getId() { return this.id; }
public static long constructor_impl (long id) {
return id; }
}
public static final void delete_cos_Kmc(long
custId) {
System.out.println("Hasta la vista, " + custId);
}
public static final void main() {
long customerId =
CustomerId.constructor_impl(123L);
delete_cos_Kmc(customerId);
@JvmInline
value class CustomerId(val id:
Long)
fun delete(custId: CustomerId) {
println("Hasta la vista,
${custId.id}")
}
fun main() {
val customerId =
CustomerId(123)
delete(customerId);
}
Infix Functions
public infix fun Int.downTo(to: Int): IntProgression {
return IntProgression.fromClosedRange(this, to, -1)
}
for (i in 5 downTo 1) { println(i) }
Invokes IntProgression.iterator()
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
mapOf(1 to "one", 2 to "two")
Scope Functions
• Extension methods applied to all types
• Act like language features, but are just library functions
• Provide for useful idioms
Scope Functions - let
public inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
• Particularly useful for working only on non-null objects
someMap.get(key)?.let { println("$key maps to $it") }
Scope Functions - apply
public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
• Useful for configuring objects without creating a temp var
class Order { // as if it came from a Java library
var productName: String? = null
var quantity: Int? = null
}
fun makeOrder() = Order().apply { // "this" refers to Order
this.productName = "BeDazzler"
quantity = 2 // this. not actually needed
}
Function type with receiver
Scope Functions - with
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
• Useful for referencing an object repeatedly
fun describe(order: Order) = with(order) {
"order for $quantity of $productName"
}
Scope Functions - use
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
try {
return block(this)
} finally {
this?.close()
}
}
• Like Java’s try-with-resources
FileOutputStream("foo").use { it.write("Hello".toByteArray()) }
Kotlin Collections API
• Built on top of Java’s Collections API
• Collections have mutable and immutable variants, but only at API
level (“immutable” collections can be cast to mutable ones and
mutated)
• Lots of transformation APIs, implemented as extension methods
• Sequences are similar to java Streams, but are reusable.
• Familiar methods: map, flatMap, filter, groupBy, etc.
• Also: associate, takeWhile, filterNotNull, etc.
• Factory methods: listOf, mutableListOf, mapOf, mutableMapOf, setOf,
etc.
Kotlin Collections API
• Built on top of Java’s Collections API
• Lots of additional methods added as extension methods
• “Immutable” versions of collections
• Tranformation methods available without using Streams
• Sequences for stream-like laziness
Kotlin Collections Basics
val names: List<String> = listOf("Gosling", "Block", "Goetz")
println(names[2]) // operator overload for names.get(2)
val capitols: Map<String, String> = mapOf(
"Utah" to "Salt Lake City",
"Idaho" to "Boise")
println(capitols["Utah"]) // capitols.get("Utah")
val nums: List<Int> = (1 .. 4).toList()
val evens: List<Int> = nums.filter { it % 2 == 0}
val evensAndOdds: Pair<List<Int>, List<Int>> = nums.partition { it %
2 == 0 }
val mod3: Map<Int, List<Int>> = nums.groupBy { it % 3 }
val squares: Map<Int, Int> = nums.associate { it to it * it }
val cubes: Map<Int, Int> = nums.associateWith { it * it * it }
Goetz
Salt Lake City
[1, 2, 3, 4]
[2, 4]
([2, 4], [1, 3])
{1=[1, 4], 2=[2], 0=[3]}
{1=1, 2=4, 3=9, 4=16}
{1=1, 2=8, 3=27, 4=64}
Kotlin Sequences – like Java Streams
fun negate(n:Int): Int {
println("negating $n")
return -n;
}
fun main() {
val seq = (1..1000).asSequence()
seq.map(::negate).take(2).forEach(::println)
println("Unlike Streams, Sequences are
reusable")
seq.map(::negate).take(2).forEach(::println)
}
negating 1
-1
negating 2
-2
Unlike Streams, Sequences are reusable
negating 1
-1
negating 2
-2
Kotlin Collections: Mutablility
val numbers: List<Int> = (1..10).toList()
numbers.add(11) // compiler error: no such method
val mutableNumbers: MutableList<Int> =
numbers.toMutableList()
mutableNumbers.add(11)
ListProcessor.process(numbers)
println(numbers)
println(numbers.javaClass.name)
public class ListProcessor { // java class
public static void process(List<?> list) {
list.remove(0);
}
}
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
[2, 3, 4, 5, 6, 7, 8, 9, 10]
java.util.ArrayList
Coroutines
• Similar to project Loom, but without JVM support
• Special compiler logic to generate reentrant methods
• Support for deferred evaluation
• Support for reactive-type idioms
• Support for actors
Concurrency Example – single thread
fun main() = runBlocking { // run a top-level coroutine
launch { // launch a new coroutine and continue
delay(1000L) // non-blocking delay for 1 second
log("World!")
}
log("Hello")
}
main [320] - Hello
main [1335] - World!
Helper method –
Prints thread name,
elapsed time and message
Concurrency Example – multiple threads
fun main() = runBlocking(Dispatchers.IO) {
for (i in 1..2) {
launch {
log("$i before yield")
yield()
log("$i after yield")
}
}
log("Hello")
}
DefaultDispatcher-worker-2 [309] - 2 before yield
DefaultDispatcher-worker-3 [310] - 1 before yield
DefaultDispatcher-worker-1 [310] - Hello
DefaultDispatcher-worker-3 [314] - 2 after yield
DefaultDispatcher-worker-2 [314] - 1 after yield
Uses elastic thread pool
Different thread
after delay
Under the hood
• Coroutine method is compiled to a class
• Class has a label attribute that determines where to pick up
execution
• Local variables are stored in class fields.
• Essentially, coroutine compilation shifts the burden of the stack onto
the heap.
Under the hood
{
delay(1000L)
log("World!")
}
public final class BasicCoroutine extends SuspendLambda {
private int label = 0;
@Nullable
public final Object invokeSuspend() {
switch (this.label) {
case 0:
DelayKt.delay(1000L, this);
this.label = 1;
return Intrinsics.COROUTINE_SUSPENDED;
case 1:
LoggingKt.log("World!");
return Unit.INSTANCE;
}
}
}
Suspendable functions
fun main() = runBlocking {
launch {
delay(1000L)
log("World!")
}
log("Hello")
}
fun main() = runBlocking {
launch {
world()
}
log("Hello")
}
private suspend fun world() {
delay(1000L)
log("World!")
}
Cannot be called without
suspend modifier
Deferred results with async {…}
private suspend fun fetchQuote(provider: String): Int {
delay(1000) // simulate network delay
log("quote fetched for $provider")
return provider.length
}
fun main() {
runBlocking {
val quote1: Deferred<Int> = async { fetchQuote("StateFarm") }
val quote2: Deferred<Int> = async { fetchQuote("AllState") }
log("awaiting quotes")
val lowestQuote = Math.min(quote1.await(), quote2.await())
log("min quote: $lowestQuote")
}
} main [385] - awaiting quotes
main [1393] - quote fetched for StateFarm
main [1396] - quote fetched for AllState
main [1397] - min quote: 8
Channels
• A Channel<T> supports
• send(value: T)
• receive(): T
• close()
• isClosedForReceive(): Boolean
• A channel has a capacity:
• > 0 – can hold this many elements, then sending blocked
• 0 – a rendezvous channel
• CONFLATED – receive() just returns the most recent sent value; older
values dropped
Channels – example
fun main() {
val channel = Channel<Int>(CONFLATED)
runBlocking {
launch {
for (i in 1..6) {
delay(400)
channel.send(i)
}
channel.close()
}
while (!channel.isClosedForReceive) {
log("received ${channel.receive()}")
delay(1000)
}
}
}
main [718] - received 1
main [1719] - received 3
main [2721] - received 6
Actors
• Actor – a coroutine with state
• Other coroutines communicate with it by sending messages to a
dedicate channel
• An actor is guaranteed to process messages serially, useful for
managing mutable state
Actor Example: Counter
sealed interface CounterMessage
object Increment: CounterMessage
class GetCount(val response: CompletableDeferred<Int>): CounterMessage
fun CoroutineScope.counterActor() = actor<CounterMessage> {
var counter: Int = 0
for (message in channel) {
when(message) {
is Increment -> counter++
is GetCount -> message.response.complete(counter)
}
}
}
Method on CoroutineScope
Actor Example: Counter (continued)
fun main() {
runBlocking(Dispatchers.Default) { // this: CoroutineScope
val actor = counterActor()
val incrementJobs: List<Job> = (1..20).map {
launch {repeat(10000) { actor.send(Increment) } }
}
while(incrementJobs.any(Job::isActive)) {
delay(100)
val response = CompletableDeferred<Int>()
actor.send(GetCount(response))
log("count: ${response.await()}")
}
actor.close() // allow Coroutine to finish
}
}
DefaultDispatcher-worker-8 [454] - count: 10840
DefaultDispatcher-worker-7 [557] - count: 69607
DefaultDispatcher-worker-4 [658] - count: 117329
DefaultDispatcher-worker-11 [761] - count: 166297
DefaultDispatcher-worker-2 [863] - count: 200000
Adopting Kotlin
• https://kotlinlang.org/ is an excellent source.
• Get buy-in from team members first! Brown-bags can help
• Try starting with unit tests
• Use IntelliJ to auto-convert some code; some hand-tuning may be
required.
• Lombok can work with Kotlin, but really isn’t needed.
• Not all classes need to be converted from Java.
• But you may find yourself temped after a bit!
Questions
interface Question {}
interface Audience {
fun questions(): Sequence<Question>
}
fun answerQuestions(audience: Audience) {
for(question in audience.questions()) {
answer(question)
}
}
fun answer(question: Question) {
println("Let me get back to you")
}

KotlinForJavaDevelopers-UJUG.pptx

  • 1.
    Kotlin for JavaDevelopers
  • 2.
    Agenda (non-linear) • Basiclanguage concepts • Examples of the Kotlin Standard Library • Coroutines • Language performance characteristics • Goal of this talk: to introduce Kotlin, not to teach it
  • 3.
    What is Kotlin? •Introduced in 2011 by JetBrains (the Intellij folks) • Goal was to offer a “better Java”, not unlike Scala, but without Scala’s slow compilation times • Compiles to JVM bytecode, as well as JavaScript and native binaries for various operating systems, including Android and iOS • Kotlin can call Java libraries, and Java can call Kotlin libraries (with limitations) • https://kotlinfoundation.org/ - Backed by JetBrains, Google, Gradle and more.
  • 4.
    Why Kotlin? • WhereasScala adds “everything”, Kotlin adds “just enough” • Well thought out “syntactic sugar”makes for a much more expressive language, without becoming inaccessible • While still evolving, backwards compatibility is prioritized • Excellent Intellij support, but also a good Eclipse plugin • Intellij can convert your Java code to Kotlin for you! • The language of choice for Android development, and the the default DSL for new Gradle builds
  • 5.
    HelloWorld.kt fun main() { println("Hello,World") } public final class HelloWorldKt { public static final void main() { System.out.println("Hello, World"); } public static void main(String[] var0) { main(); } } Semicolon inference
  • 6.
    HelloWorld2.kt class HelloWorld2 { companionobject { @JvmStatic fun main(args: Array<String>) { println("Hello, World") } } } How Kotlin does static public final class HelloWorld2 { public static final Companion Companion = new Companion(); public static final void main(String[] args) { Companion.main(args); } public static final class Companion { public final void main(String[] args) { System.out.println("Hello, World"); } private Companion() { // object in Kotlin is a singleton } } }
  • 7.
    Functions fun double(n: Int):Int { return n * 2 } Type follows name Single expression function Type inference Equivalent to : Unit fun quadruple(n: Int) = n * 4 fun say(text: String): Unit { println(text) } fun shout(text: String) { println(text.toUpperCase()) } fun triple(n: Int): Int = n * 3
  • 8.
    Default and namedparameters fun log(message: String, level: String = "INFO", logger: String = "DEFAULT") = println("$logger [$level] $message") fun main() { log("all is well") log("uh oh", "WARN") log(logger = "THERMAL", level = "FATAL", message = "Fire!") } • Convenient way to do method overloading • Named arguments in constructors largely eliminate the need for the Builder Pattern DEFAULT [INFO] all is well DEFAULT [WARN] uh oh THERMAL [FATAL] Fire!
  • 9.
    class Person(val name:String, var age: Int, heightInInches: Int) { private val heightInCm: Int = (heightInInches * 2.54).toInt() val heightInMeters: Double get() = heightInCm / 100.0 fun failHeightInMeters(): Double = heightInInches * 0.0254 // ❌Unresolved reference: heightInInches } fun main() { val founder = Person("Jeff", 58, 67) val name = founder.name // time marches on founder.age = 59 } Classes public static final void main() { Person founder = new Person(”Jeff", 58, 67); String name = founder.getName(); // not field reference! // time marches on founder.setAge(55); // not field reference! } Final property Mutable property No “new” Just a constructor parameter, not a property
  • 10.
    Data Classes data classRgbColor(val red: Int, val blue: Int, val green: Int) Class body optional fun main() { val yellow = RgbColor(255, 255, 0) println(yellow) // prints "RgbColor(red=255, blue=255, green=0)" if (yellow == RgbColor(255, 255, 0)) { // auto-generated equals println("matches") } val magenta = yellow.copy(blue = 0, green = 255) val (r, g, b) = yellow // destructuring } .equals(…)
  • 11.
    Null Safety –Optional as a language feature class Contact(val name: String, val phone: String?) fun produce() { val noPhone = Contact("Bob", null) // OK val noName = Contact(null, "867-5309") // ❌Null can not be a value of a non-null type String } fun consume(contact: Contact) { val nameLength: String = contact.name.length // OK val phoneLength = contact.phone.length // ❌ Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String? val nullablePhoneLength = contact.phone?.length // type: Int? val jenny = Contact("Jenny", "867-5309") val phone: String = jenny.phone!! // could throw NPE } Nullable Non-nullable
  • 12.
    Branching fun salutations(arriving: Boolean){ if(arriving) { println("hello") } else { println("goodbye") } } fun salutations2(arriving: Boolean): String = if(arriving) "hello" else "goodbye" //ternary fun size(item: Any): Int = when(item) { "meaning of life" -> 42 is String -> item.length is Collection<*> -> item.size is Number -> item.toInt() else -> throw IllegalArgumentException("cannot determine size of $item") } item is “smart cast” String template
  • 13.
    Looping fun repeatGreet(times: Int){ for (i in 1..times) { println("hello $i") } } fun repeatGreetExpanded(times: Int) { val range = IntRange(1, times) for (i in range) { println("hello $i") } } public void repeatGreet(int times) { for (int i = 1; i <= times; i++) { System.out.println("hello"); } } public void repeatGreetExpanded(int times) { IntRange range = new IntRange(1, times); int var1 = range.getLast(); for (int i = 1; i <= var1; i++) { System.out.println("hello"); } }
  • 14.
    Extension methods fun Int.double()= this * 2 fun Int?.add(summand: Int) = (this ?: 0) + summand fun main() { println(5.double()) // 10 println(5.add(3)) // 8 println(null.add(3)) // 3 } • Syntactic Sugar for static methods whose first argument is the receiver type • Extension methods must be imported to be used • Many StdLib extension methods are auto-imported. Can apply to null
  • 15.
    Functional Parameters /Lambdas functional signature fun transform(n: Int, transformer: (Int) -> Int) { println("$n maps to ${transformer(n)}") } fun main() { transform(3, {i -> i * 2}) transform(4) {i -> i * 3} transform(5) { it * 4 } fun square(n: Int) = n * n transform(6, ::square) } function invocation 3 maps to 6 4 maps to 12 5 maps to 20 6 maps to 36 last param lambda syntax “it” for single parameter method reference
  • 16.
    Inline Functions inline fun<T> maxBy(a: T, b: T, ranker: (T) -> Int) = if (ranker(a) > ranker(b)) a else b • If possible, invocations will be replaced with function body • Especially useful for megamorphic call sites in loops. • Used extensively in the Kotlin Collections API and other Standard Library functions (as appropriate) • Allows for clean syntax without performance penalties • ⚠️ Changes require recompilation of client code arrayOf("a", "b", "c", "d").forEach(::println) // traditional for loop
  • 17.
    Value Classes –type safety, for free public final class CustomerId { private final long id; private CustomerId(long id) { this.id = id; } public final long getId() { return this.id; } public static long constructor_impl (long id) { return id; } } public static final void delete_cos_Kmc(long custId) { System.out.println("Hasta la vista, " + custId); } public static final void main() { long customerId = CustomerId.constructor_impl(123L); delete_cos_Kmc(customerId); @JvmInline value class CustomerId(val id: Long) fun delete(custId: CustomerId) { println("Hasta la vista, ${custId.id}") } fun main() { val customerId = CustomerId(123) delete(customerId); }
  • 18.
    Infix Functions public infixfun Int.downTo(to: Int): IntProgression { return IntProgression.fromClosedRange(this, to, -1) } for (i in 5 downTo 1) { println(i) } Invokes IntProgression.iterator() public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that) mapOf(1 to "one", 2 to "two")
  • 19.
    Scope Functions • Extensionmethods applied to all types • Act like language features, but are just library functions • Provide for useful idioms
  • 20.
    Scope Functions -let public inline fun <T, R> T.let(block: (T) -> R): R { return block(this) } • Particularly useful for working only on non-null objects someMap.get(key)?.let { println("$key maps to $it") }
  • 21.
    Scope Functions -apply public inline fun <T> T.apply(block: T.() -> Unit): T { block() return this } • Useful for configuring objects without creating a temp var class Order { // as if it came from a Java library var productName: String? = null var quantity: Int? = null } fun makeOrder() = Order().apply { // "this" refers to Order this.productName = "BeDazzler" quantity = 2 // this. not actually needed } Function type with receiver
  • 22.
    Scope Functions -with public inline fun <T, R> with(receiver: T, block: T.() -> R): R { return receiver.block() } • Useful for referencing an object repeatedly fun describe(order: Order) = with(order) { "order for $quantity of $productName" }
  • 23.
    Scope Functions -use public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R { try { return block(this) } finally { this?.close() } } • Like Java’s try-with-resources FileOutputStream("foo").use { it.write("Hello".toByteArray()) }
  • 24.
    Kotlin Collections API •Built on top of Java’s Collections API • Collections have mutable and immutable variants, but only at API level (“immutable” collections can be cast to mutable ones and mutated) • Lots of transformation APIs, implemented as extension methods • Sequences are similar to java Streams, but are reusable. • Familiar methods: map, flatMap, filter, groupBy, etc. • Also: associate, takeWhile, filterNotNull, etc. • Factory methods: listOf, mutableListOf, mapOf, mutableMapOf, setOf, etc.
  • 25.
    Kotlin Collections API •Built on top of Java’s Collections API • Lots of additional methods added as extension methods • “Immutable” versions of collections • Tranformation methods available without using Streams • Sequences for stream-like laziness
  • 26.
    Kotlin Collections Basics valnames: List<String> = listOf("Gosling", "Block", "Goetz") println(names[2]) // operator overload for names.get(2) val capitols: Map<String, String> = mapOf( "Utah" to "Salt Lake City", "Idaho" to "Boise") println(capitols["Utah"]) // capitols.get("Utah") val nums: List<Int> = (1 .. 4).toList() val evens: List<Int> = nums.filter { it % 2 == 0} val evensAndOdds: Pair<List<Int>, List<Int>> = nums.partition { it % 2 == 0 } val mod3: Map<Int, List<Int>> = nums.groupBy { it % 3 } val squares: Map<Int, Int> = nums.associate { it to it * it } val cubes: Map<Int, Int> = nums.associateWith { it * it * it } Goetz Salt Lake City [1, 2, 3, 4] [2, 4] ([2, 4], [1, 3]) {1=[1, 4], 2=[2], 0=[3]} {1=1, 2=4, 3=9, 4=16} {1=1, 2=8, 3=27, 4=64}
  • 27.
    Kotlin Sequences –like Java Streams fun negate(n:Int): Int { println("negating $n") return -n; } fun main() { val seq = (1..1000).asSequence() seq.map(::negate).take(2).forEach(::println) println("Unlike Streams, Sequences are reusable") seq.map(::negate).take(2).forEach(::println) } negating 1 -1 negating 2 -2 Unlike Streams, Sequences are reusable negating 1 -1 negating 2 -2
  • 28.
    Kotlin Collections: Mutablility valnumbers: List<Int> = (1..10).toList() numbers.add(11) // compiler error: no such method val mutableNumbers: MutableList<Int> = numbers.toMutableList() mutableNumbers.add(11) ListProcessor.process(numbers) println(numbers) println(numbers.javaClass.name) public class ListProcessor { // java class public static void process(List<?> list) { list.remove(0); } } [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] [2, 3, 4, 5, 6, 7, 8, 9, 10] java.util.ArrayList
  • 29.
    Coroutines • Similar toproject Loom, but without JVM support • Special compiler logic to generate reentrant methods • Support for deferred evaluation • Support for reactive-type idioms • Support for actors
  • 30.
    Concurrency Example –single thread fun main() = runBlocking { // run a top-level coroutine launch { // launch a new coroutine and continue delay(1000L) // non-blocking delay for 1 second log("World!") } log("Hello") } main [320] - Hello main [1335] - World! Helper method – Prints thread name, elapsed time and message
  • 31.
    Concurrency Example –multiple threads fun main() = runBlocking(Dispatchers.IO) { for (i in 1..2) { launch { log("$i before yield") yield() log("$i after yield") } } log("Hello") } DefaultDispatcher-worker-2 [309] - 2 before yield DefaultDispatcher-worker-3 [310] - 1 before yield DefaultDispatcher-worker-1 [310] - Hello DefaultDispatcher-worker-3 [314] - 2 after yield DefaultDispatcher-worker-2 [314] - 1 after yield Uses elastic thread pool Different thread after delay
  • 32.
    Under the hood •Coroutine method is compiled to a class • Class has a label attribute that determines where to pick up execution • Local variables are stored in class fields. • Essentially, coroutine compilation shifts the burden of the stack onto the heap.
  • 33.
    Under the hood { delay(1000L) log("World!") } publicfinal class BasicCoroutine extends SuspendLambda { private int label = 0; @Nullable public final Object invokeSuspend() { switch (this.label) { case 0: DelayKt.delay(1000L, this); this.label = 1; return Intrinsics.COROUTINE_SUSPENDED; case 1: LoggingKt.log("World!"); return Unit.INSTANCE; } } }
  • 34.
    Suspendable functions fun main()= runBlocking { launch { delay(1000L) log("World!") } log("Hello") } fun main() = runBlocking { launch { world() } log("Hello") } private suspend fun world() { delay(1000L) log("World!") } Cannot be called without suspend modifier
  • 35.
    Deferred results withasync {…} private suspend fun fetchQuote(provider: String): Int { delay(1000) // simulate network delay log("quote fetched for $provider") return provider.length } fun main() { runBlocking { val quote1: Deferred<Int> = async { fetchQuote("StateFarm") } val quote2: Deferred<Int> = async { fetchQuote("AllState") } log("awaiting quotes") val lowestQuote = Math.min(quote1.await(), quote2.await()) log("min quote: $lowestQuote") } } main [385] - awaiting quotes main [1393] - quote fetched for StateFarm main [1396] - quote fetched for AllState main [1397] - min quote: 8
  • 36.
    Channels • A Channel<T>supports • send(value: T) • receive(): T • close() • isClosedForReceive(): Boolean • A channel has a capacity: • > 0 – can hold this many elements, then sending blocked • 0 – a rendezvous channel • CONFLATED – receive() just returns the most recent sent value; older values dropped
  • 37.
    Channels – example funmain() { val channel = Channel<Int>(CONFLATED) runBlocking { launch { for (i in 1..6) { delay(400) channel.send(i) } channel.close() } while (!channel.isClosedForReceive) { log("received ${channel.receive()}") delay(1000) } } } main [718] - received 1 main [1719] - received 3 main [2721] - received 6
  • 38.
    Actors • Actor –a coroutine with state • Other coroutines communicate with it by sending messages to a dedicate channel • An actor is guaranteed to process messages serially, useful for managing mutable state
  • 39.
    Actor Example: Counter sealedinterface CounterMessage object Increment: CounterMessage class GetCount(val response: CompletableDeferred<Int>): CounterMessage fun CoroutineScope.counterActor() = actor<CounterMessage> { var counter: Int = 0 for (message in channel) { when(message) { is Increment -> counter++ is GetCount -> message.response.complete(counter) } } } Method on CoroutineScope
  • 40.
    Actor Example: Counter(continued) fun main() { runBlocking(Dispatchers.Default) { // this: CoroutineScope val actor = counterActor() val incrementJobs: List<Job> = (1..20).map { launch {repeat(10000) { actor.send(Increment) } } } while(incrementJobs.any(Job::isActive)) { delay(100) val response = CompletableDeferred<Int>() actor.send(GetCount(response)) log("count: ${response.await()}") } actor.close() // allow Coroutine to finish } } DefaultDispatcher-worker-8 [454] - count: 10840 DefaultDispatcher-worker-7 [557] - count: 69607 DefaultDispatcher-worker-4 [658] - count: 117329 DefaultDispatcher-worker-11 [761] - count: 166297 DefaultDispatcher-worker-2 [863] - count: 200000
  • 41.
    Adopting Kotlin • https://kotlinlang.org/is an excellent source. • Get buy-in from team members first! Brown-bags can help • Try starting with unit tests • Use IntelliJ to auto-convert some code; some hand-tuning may be required. • Lombok can work with Kotlin, but really isn’t needed. • Not all classes need to be converted from Java. • But you may find yourself temped after a bit!
  • 42.
    Questions interface Question {} interfaceAudience { fun questions(): Sequence<Question> } fun answerQuestions(audience: Audience) { for(question in audience.questions()) { answer(question) } } fun answer(question: Question) { println("Let me get back to you") }