2. Jetbrains
• Develops tools dedicated for
software developers.
• Large code base written in
Java (IDE and server side
tools).
• E.g. IntelliJ-Community
edition, 3M+ lines of Java
code.
@nklmish
3. Back in 2010, Jetbrains were
looking for a language…
Java Interoperability
Can use existing Java code base & eco system
Reduce boilerplate code
Concise
Expressive
Easy tooling
Should be easy to prepare tooling for the language
Pragmatic
Solve real world problem
@nklmish
4. Why not X language ?
• “Most languages did not have the features we were
looking for, with the exception of Scala” - Dmitry Jemerov
• So why not Scala?
• Slow compilation time
• Tooling
@nklmish
5. Kotlin
• Open source programming language.
• Targets : Server side, client side web, mobile and
native.
• First commit - 8 Nov 2010, First release - 15 Feb
2016
• Gaining momentum - 20K+ repos and millions of lines
of code
• Google officially support it for android development.
@nklmish
6. Why Kotlin ?
Java Interoperability
We can use existing Java code & libraries as well
As call Java code from Kotlin and vice-versa
Null safety baked into type system
Safe
Statically Typed Easy tooling Pragmatic
Reduce boilerplate code
Concise
Expressive Small Learning Curve
Java developers can leverage there existing knowledge
Support Both
27. Infix function == function
prefixed with “infix”
Must be a member/
extension function
@nklmish
Must take only one parameter
class Stack {
infix fun push(num : Int) {
//
}
}
fun main(args: Array<String>) {
val stack = Stack()
stack push 1
}
class Stack {
fun push(num : Int) {
//
}
}
fun main(args: Array<String>) {
val stack = Stack()
stack.push(1)
}
28. Infix function - real life
@nklmish
"should assign a default state for a newly launched game" {
game.states.count() shouldBe 1
}
val map = mapOf("Bob" to "123 North Avenue")
kotlintest
Kotlin
29. Extension functions ==
replace utility classes
@nklmish
fun LocalDateTime.toDate(): Date = Date.from(this.toInstant(OffsetDateTime.now().offset))
30. Higher order function
@nklmish
fun calculateTotal(amount : Double, exchange: (Double) -> Double) : Double {
val convertedAmount = exchange(amount)
return convertedAmount + (convertedAmount * 0.23)
}
36. Inline function - time
measurement
@nklmish
val elapsedTime = measureTimeMillis { //execute the given code & returns elapsed time in ms.
calculateTotal(100.00) {
amount -> amount * 4.5
}
}
val elapsedTime = measureNanoTime { //execute the given block & returns elapsed time in ns.
...
}
Inline functions
37. Inline + reified == access
generic type info
@nklmish
@Suppress("UNCHECKED_CAST")
fun <T> Node.parent(clazz: Class<T>): T? {
var tmp = parent
while (tmp != null && !clazz.isInstance(tmp)) tmp = tmp.parent
return tmp as T?
}
//invocation
node.parent(Node::class.java)
Without reified
inline fun <reified T> Node.parent(): T? {
var tmp = parent
while (p != null && p !is T) tmp = tmp.parent
return p as T?
}
//invocation
node.parent<Node>()
Reified
38. Function literal with
receiver == recipe for DSL
@nklmish
class Delivery {
fun person(function: Person.() -> Unit) : Person {
...
}
fun address(function: Address.() -> Unit) : Address {
...
}
}
fun delivery(function: Delivery.() -> Unit): Delivery {
...
}
Extension fun on Delivery
39. Function literal with
receiver == recipe for DSL
@nklmish
class Delivery {
fun person(function: Person.() -> Unit) : Person {
...
}
fun address(function: Address.() -> Unit) : Address {
...
}
}
fun delivery(function: Delivery.() -> Unit): Delivery {
...
}
Extension fun on Delivery => we can access
40. Function literal with
receiver == recipe for DSL
@nklmish
delivery({ })
Syntactic sugar (if lambda is last argument)
delivery() { }== delivery { }==
Calling function “delivery” passing lambda as argument
42. Function literal with
receiver == recipe for DSL
@nklmish
class Delivery {
fun person(function: Person.() -> Unit) : Person {
...
}
fun address(function: Address.() -> Unit) : Address {
...
}
}
fun delivery(function: Delivery.() -> Unit): Delivery {
...
}
fun onTransit(function: () -> Unit) : TransitInfo {
…
}
fun sendSMS(message: String) {
...
}
Extension fun on Delivery
Extension fun on Address
Extension fun on Person
Higher Order Function
43. Function literal with
receiver == recipe for DSL
@nklmish
delivery {
person {
name = "Bob"
surname = "Smith"
}
address {
city = “paris"
street = "abc street"
postalCode = "12345"
onTransit {
sendSMS("...")
}
}
}
Final Result
44. Real life DSL - Anko
verticalLayout {
val name = editText()
button("Say Hello") {
onClick { toast("Hello, ${name.text}!") }
}
}
https://github.com/Kotlin/anko @nklmish
45. Real life DSL - GradleKotlinDSL
https://github.com/gradle/kotlin-dsl @nklmish
51. Class - Java
public class Schedule {
private final LocalDateTime start;
private final LocalDateTime end;
private String notes;
public Schedule(LocalDateTime start, LocalDateTime end) {
this.start = start;
this.end = end;
}
public LocalDateTime getStart() {
return start;
}
public LocalDateTime getEnd() {
return end;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
}
@nklmish
52. Class - Kotlin
class Schedule(val start: LocalDateTime, val end: LocalDateTime, var note : String)
@nklmish
53. Class - Kotlin
class Schedule(val start: LocalDateTime, val end: LocalDateTime, var note : String)
Default: public and final
@nklmish
54. Class - Kotlin
class Schedule(val start: LocalDateTime, val end: LocalDateTime, var note : String)
Properties
@nklmish
55. Class - Kotlin
class Schedule(val start: LocalDateTime, val end: LocalDateTime, var note : String)
Immutable Immutable
@nklmish
56. Class - Kotlin
class Schedule(val start: LocalDateTime, val end: LocalDateTime, var note : String)
Mutable
@nklmish
57. Data class == data holders
data class Schedule(val start : LocalDateTime, val end : LocalDateTime)
@nklmish
58. Data class
data class Schedule(val start : LocalDateTime, val end : LocalDateTime)
Autogenerate: meaningful toString(), copy(), equals(), hashcode()
@nklmish
59. Data class - copy()
val schedule = Schedule(LocalDateTime.now(), LocalDateTime.now().plusSeconds(1))
val copy = schedule.copy()
val copyWithModifiedEndDate = schedule.copy(end = schedule.end.plusSeconds(1)) // we
can change specific fields during copy, comes handy during unit testing
@nklmish
60. Data class - validation
data class Schedule(val start : LocalDateTime, val end : LocalDateTime) {
init {
if (end.isBefore(start)) throw IllegalArgumentException("end $end should be after start $start")
}
}
@nklmish
String interpolation
Note: String interpolation comes handy when dealing with JSON strings. e.g.
private val expectedServerContent = """
{"name":"$canonicalName","json":"{"id":"$expectedGameProviderId"}","data":{"@foo":".bar","number":"123"},"policy":"","password":""}
""".trimIndent()
61. Sealed class ~ extension of
enum classes
sealed class Bonus {
data class Yearly(val amount : BigDecimal) : Bonus()
data class Monthly(val amount : BigDecimal, val factor : BigDecimal) : Bonus()
}
@nklmish
Each enum constant exists only as
a single instance
Enum
Subclass of a sealed class can have
multiple instances which can contain
state
Sealed class
62. Sealed class ~ extension of
enum classes
sealed class Bonus {
data class Yearly(val amount : BigDecimal) : Bonus()
data class Monthly(val amount : BigDecimal, val factor : BigDecimal) : Bonus()
}
fun evaluate(bonus: Bonus) = when(bonus) {
is Yearly -> bonus.amount
is Monthly -> bonus.amount.multiply(bonus.factor)
}
Abstract class
@nklmish
66. Lazy
• Create a new instance only when an object get accessed
for the very first time.
@nklmish
67. Lazy
class GameRound(val player : String) {
val bets by lazy { fetchBets(this) }
private fun fetchBets(gameRound: GameRound): Int {
...
}
}
@nklmish
68. Lazy - thread safety ?
class GameRound(val player : String) {
val bets by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { fetchBets(this) }
private fun fetchBets(gameRound: GameRound): Int {
...
}
}
Only a single thread can initialise the Lazy instance.
@nklmish
69. Lazy - thread safety ?
class GameRound(val player : String) {
val bets by lazy(mode = LazyThreadSafetyMode.PUBLICATION) { fetchBets(this) }
private fun fetchBets(gameRound: GameRound): Int {
...
}
}
Concurrent access permissible but only first returned value will be used.
@nklmish
70. Lazy - thread safety ?
class GameRound(val player : String) {
val bets by lazy(mode = LazyThreadSafetyMode.NONE) { fetchBets(this) }
private fun fetchBets(gameRound: GameRound): Int {
...
}
}
multiple threads == undefined behaviour
@nklmish
There are more builtin delegated properties like notNull, observable, etc.
We can also create our own.
73. players.filter({p -> p.score > 70})
players.filter() {p -> p.score > 70}
players.filter {p -> p.score > 70}
players.filter {it.score > 70}
syntactic sugar
Implicit name for a single parameter
@nklmish
74. • Allows to invoke methods of different object (w/o any
qualifiers) inside there body.
@nklmish@nklmish
with receiver
@nklmish
75. @nklmish@nklmish@nklmish
public String toCSVFormat(List<Integer> list) {
StringBuilder sb = new StringBuilder("[");
for (Integer integer : list) {
sb.append(integer).append(“;");
}
sb.deleteCharAt(sb.length() - 1);
sb.append("]");
return sb.toString();
}
Java
Kotlin : with (returns result of last call)
fun toCSVFormat(list: List<Int>) =
with(StringBuilder()) {
append("[")
for (i in list) {
append(i).append(";")
}
deleteCharAt(length - 1)
append("]")
toString()
}
with receiver - with
76. @nklmish@nklmish@nklmish
public String toCSVFormat(List<Integer> list) {
StringBuilder sb = new StringBuilder("[");
for (Integer integer : list) {
sb.append(integer).append(“;");
}
sb.deleteCharAt(sb.length() - 1);
sb.append("]");
return sb.toString();
}
Java Kotlin : apply (returns the receiver)
fun toCSVFormat(list: List<Int>) =
StringBuilder().apply {
append("[")
for (i in list) {
append(i).append(";")
}
deleteCharAt(length - 1)
append("]")
}.toString()
with receiver - apply
78. Null safety - baked into type
system
@nklmish
var message : String = "hello"
message = null // compile time error
var message : String? = "hello"
message = null // ok
println(message?.length) //safe operator, if not null then invoke length
Java
message?.let {
process(it)
}
Kotlin
if (message != null) {
process(message);
}
79. Null safety - using Java API
from Kotlin?
• Annotate Java code with nullability annotations.
• Kotlin can understand annotations from:
• Jetbrains (org.jetbrains.annotations)
• JSR-305 (javax.annotation)
• Android (android.support.annotation)
@nklmish
91. Casting - Kotlin (smartcast)
val obj:Any = "Any"
if (obj is String) {
obj.toUpperCase() // compiler smartness, auto cast to String
// Works only, if obj is immutable or
// haven’t changed after check
}
@nklmish
92. Deprecation
@Deprecated(level = DeprecationLevel.WARNING,
message = "we are going to replace with StringUtils",
replaceWith = @ReplaceWith(
expression = "StringUtils.isEmpty(input)",
imports = {"org.apache.commons.lang3.StringUtils"})
)
public static boolean isEmpty(String input) {
...
}
@nklmish
93. Deprecation
@Deprecated(level = DeprecationLevel.WARNING,
message = "we are going to replace with StringUtils",
replaceWith = @ReplaceWith(
expression = "StringUtils.isEmpty(input)",
imports = {"org.apache.commons.lang3.StringUtils"})
)
public static boolean isEmpty(String input) {
...
}
level: Warning, Error, Hidden
@nklmish
94. Deprecation
@Deprecated(level = DeprecationLevel.WARNING,
message = "we are going to replace with StringUtils",
replaceWith = @ReplaceWith(
expression = "StringUtils.isEmpty(input)",
imports = {"org.apache.commons.lang3.StringUtils"})
)
public static boolean isEmpty(String input) {
...
}
Method to replace with
@nklmish
95. Deprecation
@Deprecated(level = DeprecationLevel.WARNING,
message = "we are going to replace with StringUtils",
replaceWith = @ReplaceWith(
expression = "StringUtils.isEmpty(input)",
imports = {"org.apache.commons.lang3.StringUtils"})
)
public static boolean isEmpty(String input) {
...
}
Which class to import
@nklmish
97. when == powerful switch
fun playerPrivileges(rank : Int) = when(rank) {
in 1..3 -> "VIP"
in 4..50 -> "Elevated"
in 51..1000 -> "Classic"
else -> "Regular"
}
@nklmish
98. when == powerful switch
fun playerPrivileges(rank : Int) = when(rank) {
in 1..3 -> "VIP"
in 4..50 -> "Elevated"
in 51..1000 -> "Classic"
else -> "Regular"
}
Can be any type, no limitations
@nklmish
99. when == powerful switch
fun playerPrivileges(rank : Int) = when(rank) {
in 1..3 -> "VIP"
in 4..50 -> "Elevated"
in 51..1000 -> "Classic"
else -> "Regular"
}
No need to type break
@nklmish
100. Operator overloading -
Java?public class Wallet {
public Wallet(Integer amount) {
this.amount = amount;
}
private final Integer amount;
public Wallet plus(Wallet w) {
return new Wallet(this.amount + w.amount);
}
public Wallet minus(Wallet w) {
return new Wallet(this.amount - w.amount);
}
public Wallet multiply(Wallet w) {
return new Wallet(this.amount * w.amount);
}
}
Wallet walletA = new Wallet(100);
Wallet walletB = new Wallet(200);
walletA.plus(walletB);
walletA.subtract(walletB);
walletA.multiply(walletB);
@nklmish
101. Operator overloading -
Kotlin
data class Wallet(val amount : Int) {
operator fun plus(w : Wallet) = Wallet(amount.plus(w.amount))
operator fun minus(w : Wallet) = Wallet(amount.minus(w.amount))
operator fun times(w : Wallet) = Wallet(amount.minus(w.amount))
}
val walletA = Wallet(100)
val walletB = Wallet(200)
walletA + walletB
walletA - walletB
walletA * walletB
@nklmish
102. Operator overloading -
Kotlin
Naming Convention Syntax
plus walletA + walletB
unaryPlus +walletA
minus walletA - walletB
unaryMinus -walletA
inc ++walletA
dec — walletA
times walletA * walletB
div walletA / walletB
mod walletA % walletB
not !walletA
@nklmish
103. Co-routines == asyn
programming made simple
@nklmish
val jobs = List(1_000_000) {
async(CommonPool) {
delay(10L)
1.0
}
}
runBlocking {
println(jobs.sumByDouble { it.await() })
}
104. Co-routines == asyn
programming made simple
@nklmish
val jobs = List(1_000_000) {
async(CommonPool) {
delay(10L)
1.0
}
}
runBlocking {
println(jobs.sumByDouble { it.await() })
}
Lightweight
109. Kotlin + Spring -
build.gradle
@nklmish
dependencies {
classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion")
}
Before plugin
After Plugin
@SpringBootApplication
open class PhoneBookApplication {
@Bean
open fun passwordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}
}
@SpringBootApplication
class PhoneBookApplication {
@Bean
fun passwordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}
}
110. Kotlin + Spring -
build.gradle
@nklmish
dependencies {
classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion")
}
@Document
data class Contact(@Id val id: String = UUID.randomUUID().toString(),
val firstName: String,
var lastName: String,
val phones: List<String> = emptyList()
)