Kotlin: perhaps this time is the right time
This document summarizes a presentation given at the Jug Day event in Trento, Italy on May 19, 2018. The presenter, Davide Cerbo, has over 10 years of experience in full-stack development. He discusses the history of Java and how Kotlin has evolved to address issues with Java, such as backward compatibility. Kotlin integrates seamlessly with Java at the source code level and reduces lines of code by about 40% compared to Java. It is considered a safer alternative to Java that is also easy to learn for Java developers.
2. Davide Cerbo
10+ years of experience
Full-stack
DevDay co-founder
I write code, I see people
Chi sono
3. Disclaimer
Here we have a lot of println, and some jokes.
My wife said that they aren’t funny.
4. C’era una volta OAK
It was 1992 and in Italy there
was a big scandal: Tangentopoli.
5. C’era una volta OAK Java
Borns in 1995
and now can
drink beers.
6. Il grande problema
Backward
compatibility
(The art of killing your app because the
environment evolves while you can’t)
JDK 1.0 (21 January 1996)
JDK 1.1 (19 February 1997)
J2SE 1.2 (8 December 1998)
J2SE 1.3 (8 May 2000)
J2SE 1.4 (6 February 2002)
J2SE 5.0 (30 September 2004)
Java SE 6 (11 December 2006)
Java SE 7 (28 July 2011)
Java SE 8 (18 March 2014)
Java SE 9 (26 September 2017)
Java Se 10 (20 March 2018)
8. Machine code is not enought?
Assembly languages
Machine code
Logic languages
(Prolog)
Functional languages
(Haskell)
Procedural languages
(C / Pascal)
Object-oriented languages
(Java)
The Case for Kotlin and Ceylon by Russel Winder
https://www.youtube.com/watch?v=cFL_DDXBkJQ
10. So, why not Scala?
Kotlin is a better Java while Scala is more powerful than Java, and
probably than Kotlin. But, Kotlin borns in the industry for the industry
and it evolves with the industry in mind, while Scala borns at the
university and it is adapted to the industry. Unfortunately, I work for
the industry.
https://agilewombat.com/2016/02/01/scala-vs-kotlin/
https://superkotlin.com/kotlin-vs-scala/
11. So, why not, another one time, Scala?
Scala can interact with Java at the library level, meaning that Java
programs can use Scala libraries (objects and functions) and Scala
libraries can use Java libraries (objects and methods). But Scala
and Java programs must be built as separate projects, with distinct
build chains. Kotlin is different because it integrates with Java
programs at the source level. You can mix Java and Kotlin source
files in the same projects with a single build chain.
(The joy of Kotlin - Pierre Yves Saumont)
13. Kotlin is a statically-typed programming
language that runs on the Java Virtual Machine
and also can be compiled to JavaScript source
code or uses the LLVM compiler infrastructure.
15. 04 January 2017
Introducing Kotlin support
in Spring Framework 5.0
https://spring.io/blog/2017/01/04/introducing-kotlin-support-in-spring-framework-5-0
https://github.com/sdeleuze/spring-kotlin-deepdive
16. 17 May 2017
Kotlin on Android. Now official
https://blog.jetbrains.com/kotlin/2017/05/kotlin-on-android-now-official/
http://nilhcem.com/swift-is-like-kotlin/
18. 5 June 2017
kotlin-react borns!
https://github.com/JetBrains/kotlin-wrappers/commits/master/kotlin-react
https://github.com/JetBrains/create-react-kotlin-app
19. May 2018
ThoughtWorks suggests to adopt Kotlin in
its Technlogy Radar
https://www.thoughtworks.com/radar/languages-and-frameworks/kotlin
May
2017
Nov
2017
May
2018
TrialAsses Adopt
20.
21. Var o Val
var serie = "B"
var a = "Salernitana will win Serie $serie"
val b = "Salernitana will win Serie $serie"
IMMUTABLE
23. JVM != Java
class Hello {
fun sayHello(): String {
return "hello!"
}
}
public final class Hello {
// access flags 0x11
public final sayHello()Ljava/lang/String;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 8 L0
LDC "hello!"
ARETURN
L1
LOCALVARIABLE this Ltotest/Hello; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public <init>()V
L0
LINENUMBER 6 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this L/Hello; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
@Lkotlin/Metadata;(mv={1, 1, 1}, bv={1, 0, 2}, k=1,
d1={"u0000u0012nu0002u0018u0002nu0002u0010u0000nu0002
u0008u0002nu0002u0010u000enu0000u0018u00002u00020u00
01Bu0005u00a2u0006u0002u0010u0002Ju0006u0010u0003u001
au00020u0004u00a8u0006u0005", d2={"L/Hello;", "", "()V",
"sayHello", "", "production sources for module coroutine_main"})
// compiled from: Hello.kt
}
@Metadata(
mv = {1, 1, 7},
bv = {1, 0, 2},
k = 1,
d1 =
{"u0000u0012nu0002u0018u0002nu0002
u0005¢u0006u0002u0010u0002Ju0006u00
d2 = {"Ltotest/Hello;", "", "()V", "sayHello", "", "
)
public final class Hello {
@NotNull
public final String sayHello() {
return "hello!";
}
}
Compile Decompile
Tools > Kotlin > Show Kotlin Bytecode > Decompile
24. Fun fun functions
fun hello(name: String, city: String = "Salerno") = println("Hello $name from $city")
hello("Davide", "Salerno")
hello(name = "Davide")
hello(city = "Salerno", name = "Valentina")
fun Int.multilpy(x: Int): Int = this * x // 5.multilpy(10)
infix fun Int.multilpy(x: Int): Int = this * x // 5 multilpy 10
Also helps with refactoring!
25. E gli operatori?
data class Point(val x: Int, val y: Int) {
operator fun plus(a: Point) = Point(x + a.x, y + a.y)
}
operator fun Point.unaryMinus() = Point(-x, -y)
val point = Point(10, 20)
println(point + point + point) //Point(x=30, y=60)
println(-point) // Point(x=-10, y=-20)
26. Tailrec
● Recursion, in some language, can cause: StackOverflow!
● Reduce stack execution.
● Tailrec will resolve this issue only if the recursive call is the last one.
● It transforms your code in imperative code. Less readable, but more fast.
● Performance improvement
fun factorial(n: Long): Long = if (n <= 1L) n else n * factorial(n - 1)
tailrec fun factorial(n: Long, accumulator: Long = 1): Long = if (n <= 1L) accumulator
else factorial(n - 1, accumulator * n)
Tailrec cannot
applicable
“If” will return the verified condition value
27. Tailrec decompiled
public static final long factorial(long n, long accumulator) {
while(n > 1L) {
long var10000 = n - 1L;
accumulator *= n;
n = var10000;
}
return accumulator;
}
public static long factorial$default(long var0, long var2, int var4, Object
var5) {
if((var4 & 2) != 0) {
var2 = 1L;
}
return factorial(var0, var2);
}
}
public static final long factorial(long n, long accumulator) {
return n <= 1L?accumulator:factorial(n - 1L, accumulator * n);
}
public static long factorial$default(long var0, long var2, int var4, Object
var5) {
if((var4 & 2) != 0) {
var2 = 1L;
}
return factorial(var0, var2);
}
With
Tailrec
Without
Tailrec
StackOverflow
Tools > Kotlin > Show Kotlin Bytecode > Decompile
28. “If” is an expression, not a construct,
i.e. it returns a value.
29. Class
open class Person(val name: String) {
init { println("init…") }
open fun speak() { println("Hi $name!") }
infix fun and(o: Person) = "Hi ${o.name} & ${this.name}"
}
fun main(args: Array<String>) {
Person("Davide") and Person("Valentina")
val p = Person("Jack")
p.speak()
}
30. Class
class Customer(name: String) : Person(name) {
override fun speak() {
println("Welcome $name!")
}
}
class CustomerDavide : Person("Davide") {
override fun speak() {
println("Welcome $name!")
}
}
31. Equals, hashCode, toString e copy, nevermore!
data class User(val name: String, val age: Int)
val davide = User("Davide", 35)
val davideJunior = davide.copy(age=0)
fun main(args: Array<String>) {
val (name, age) = davide
println("$name $age years old")
}
deconstructing
32. Nothing is equal as appear
data class Point(val x: Int, val y: Int)
val a = Point(1, 2)
val b = Point(1, 2)
val c = a
println(a === b) // false
println(a == b) // true
println(a === c) // true
println(a == c) // true
Check the reference
.equals(...)
33. Lambda
fun main(args: Array<String>) {
arrayOf("Valentina", "Davide").forEach { println("Hello $it!") }
val ints =(1..3)
val logger = { msg: Any -> println("log $msg") }
ints.map { value -> value * 2 }.map { v -> logger(v) }
ints.map { it * 2 }.map { logger(it) }
}
34. Lambda
class Customer(val name: String) {
fun forEach(action: (char: Char) -> Unit) = name.forEach(action)
fun upperCaseAndConcat(callback: () -> String) = "${callback()} $name".toUpperCase()
}
fun main(args: Array<String>) {
val customer = Customer("Davide")
customer.forEach { println(it.toInt()) } //68 97 118 105 100 101
println(customer.upperCaseAndConcat { "Cerbo" }) //CERBO DAVIDE
}
The type with only one value: the Unit
object. This type corresponds to the void
type in Java.
39. Lambda & Function Label
fun forEachTest() {
val numbers = 1..100
numbers.forEach {
if (it == 25) {
return
}
println("index $it")
}
println("Hello")
}
fun forEachTest() {
val numbers = 1..100
numbers.forEach {
if (it == 25) {
return@forEach
}
println("index $it")
}
println("Hello")
}
Vs.
40. Tell me when, when?
fun describe(obj: Any): String = when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
describe(Person("davide"))
41. Null is safe!
var testA:String = "ciao"
var testB:String? = "ciao"
testB = null
println("a0 ${testA.length}")
println("b0 ${testB.length}")
// ^ Not safe! Compile time error! ^
println("b1 ${testB?.length}")
println("b2 ${testB!!.length}")
// ^ KotlinNullPointerException ^
// NPE Lovers
42. Null is safe!
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull() // [1, 2, 4]
val aInt: Int? = b as? Int
// If b is null, normally we will have a NullPointerException, while if the type is
different we will have a ClassCastExeption. Using “as?” we haven’t an exception,
we will have a null value assigned to the aInt value.
43. Null is safe!
data class Person(val name: String, val age: Int?)
val person:Person? = Person("Jack", 1)
if (person?.age != null) {
println("The person is aged ${person?.age}")
}
//oppure
person?.age?.let {
println("The person is aged $it")
}
44. ?:
Elvis operatorIt is the replacement to “Optional.getOrElse” in Java
val davide = Person(testA)
val elvis = Person(testB ?: "Elvis")
println(jack.name)
46. Companion object
● No static members.
● Has access to private level method and properties of the main object.
● Companion objects can implements interfaces.
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
@JvmStatic //converts to real static method
override fun create(): MyClass = MyClass()
}
}
47. Visibility Modifiers
private - means visible inside this class only (including
all its members);
protected — same as private + visible in subclasses too;
Override protected method are still protected.
internal — any client inside this module who sees the
declaring class sees its internal members. A module is a set
of Kotlin files compiled together;
public — any client who sees the declaring class sees its
public members. (Default)
48. Exception
Kotlin does not have checked exceptions
throw is an expression
val s = person.name ?: throw
IllegalArgumentException("Name required")
49. Property: Get & Set
class Strange(var value: Long) {
var strangeValue: Long
get() = value * 2
set(value){
if(value > 5) this.value = value
}
}
fun main(args: Array<String>) {
val customer = Strange(10)
println(customer.strangeValue) //20
customer.strangeValue = 3
println(customer.strangeValue) //20
customer.strangeValue = 6
println(customer.strangeValue) //12
}
50. Delegated properties: why me?
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name} in $thisRef.'")
}
}
class Example {
var p: String by Delegate()
}
51. Delegated properties: the lazy & the observable
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue)
println(lazyValue)
}
class User {
var n: String by Delegates.observable("empty") {
prop, old, new -> println("$old -> $new")
}
}
fun main(args: Array<String>) {
val user = User()
user.n = "first"
user.n = "second"
}
52. Coroutine
Coroutine ⋍ light-weight thread
● They are like threads, they run in parallel, wait for each other and they communicate.
● They are cheap, we can create many of those without having performance issues.
● They are executed in a thread pool.
● A thread can handle more than one coroutine.
● Thread became free until a coroutine is in waiting state. When the coroutine will return active,
it doesn’t get the old thread, but it will use a free thread in the pool.
https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md
https://proandroiddev.com/approaching-kotlin-coroutines-an-extensive-feature-concurrent-programming-in-kotlin-eaaa19b003d2
53. Coroutine is humble
fun main(args: Array<String>) = runBlocking {
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.forEach { it.join() }
}
fun main(args: Array<String>) {
val jobs = List(100_000) {
thread(start = true) {
Thread.sleep(1000L)
print(".")
}
}
jobs.forEach { it.join() }
}
OUT OF MEMORY!!!
54. Coroutine: suspend, async / await
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")
}
suspend fun doSomethingUsefulOne(): Int {
delay(1000L)
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L)
return 29
}
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
suspend fun doSomethingUsefulOne(): Int {
delay(1000L)
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L)
return 29
}
~2 sec.
~1 sec.
55. Coroutine is the basement
Coroutine
Actors
Communication
Sequence
Process
?
56. Now is the time to remember that Kotlin is good
because the trade-off between synthesis and
readable code is really good.
58. Webapp: Gradle
buildscript {
...
dependencies {
...
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlinVersion"
}
}
...
apply plugin: "kotlin-jpa"
group = 'it.devday'
version = '0.0.1-SNAPSHOT'
...
}
JPA need an empty constructor in some entities,
and Kotlin objects don’t have. To solve this issue
we can use this plugin that will create an empty
constructor in any object, without any change to
our codebase.
59. Webapp: Domain & Repository
import it.devday.kotlincodemotion.domain.Contact
import org.springframework.data....JpaRepository
interface ItemRepository: JpaRepository<Item, Long>
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id
@Entity
data class Item(
@Id @GeneratedValue
val id: Long,
val description: String,
val vat: Double,
val price: Double,
val quantity: Double)
60. Webapp: Domain & Repository
@Embeddable
data class Price(val amount: Double) {
operator fun plus(a: Price) = a.amount + this.amount
operator fun rem(a: ValueAddedTax) =
this.amount * (100 + a.percentage) / 100
}
@Embeddable
data class Quantity(val value: Double)
@Embeddable
data class ValueAddedTax(val percentage: Double)
@Entity data class Item(
@Id @GeneratedValue
val id: Long, val description: String,
@Embedded val vat: ValueAddedTax,
@Embedded val price: Price,
@Embedded val quantity: Quantity) {
val grossPrice: Double
get() = this.price % this.vat
val total: Double
get() = this.grossPrice * quantity.value
}
61. Webapp: Resource 1/2
@RestController
@RequestMapping("/items")
class ItemResource(val repository: ItemRepository) {
@GetMapping //curl -XGET http://localhost:8080/items
fun getAll() = repository.findAll()
@GetMapping("/{id}") //curl -XGET http://localhost:8080/items/1
fun getAll(@PathVariable id: Long) = repository.findById(id)
62. Webapp: Resource 2/2
@PostMapping //curl -XPOST http://localhost:8080/items -H 'Content-Type: application/json' -d
'{"description":"Kotlin in action", "vat":{"percentage":10}, "price":{"amount":1.5}, "quantity":{"value":"10"}}'
fun insert(@RequestBody item: Item) = repository.save(item)
@DeleteMapping("/{id}") //curl -XDELETE http://localhost:8080/items/1
fun delete(@PathVariable id: Long) {
val item = repository.findById(id).unwrap()
item?.let { repository.delete(item) }
}
}
wait...unwrap() doesn’t exist on Optional class
63. Webapp: Application
@SpringBootApplication
class KotlinApplication
fun <T> Optional<T>.unwrap(): T? = orElse(null)
fun main(args: Array<String>) {
runApplication<KotlinApplication>(*args)
}
unwrap()!
array is passed element by element, it is used with vararg arguments
public inline fun <reified T : kotlin.Any> runApplication(vararg args: kotlin.String)
The generic type will be available in the method. Wow!
64. Webapp: Where is my Repository?
@RestController
@RequestMapping("/items")
class ItemResource() {
@GetMapping //curl -XGET http://localhost:8080/items
fun getAll() = Item.findAll()
@GetMapping("/{id}") //curl -XGET http://localhost:8080/items/1
fun getAll(@PathVariable id: Long) = Item.findById(id)
…...
65. Webapp: Domain & Repository Dao
@Entity data class Item(….) {
….
companion object : Dao<Item, Long>
}
Why this approach? http://mavi.logdown.com/posts/5771422
66. interface Dao<T, K>
inline fun <reified T : Any, K> Dao<T, K>.findById(id: K):
T = Db.exec { em -> em.find(T::class.java, id) } as T
inline fun <reified T : Any, K> Dao<T, K>.save(item: T):
Unit = Db.exec { em -> tx(em) { em.persist(item) } } as
Unit
...
….
fun tx(em: EntityManager, c: () -> Any): Any {
try {
em.transaction.begin()
val result = c()
em.transaction.commit()
return result
} finally {
em.close()
}
}
Webapp: Domain & Repository Dao
67. @Component
object Db : ApplicationContextAware {
var ac: ApplicationContext? = null
override fun setApplicationContext(applicationContext: ApplicationContext) {
ac = applicationContext!!
}
fun exec(callback: (em: EntityManager) -> Any): Any {
return callback(ac!!.getBean(EntityManagerFactory::class.java).createEntityManager())
} }
Webapp: Domain & Repository Dao