Idiomatic Kotlin
Dmitry Jemerov <yole@jetbrains.com>
Expressions
Use ‘when’ as expression body
fun parseNum(number: String): Int? {
when (number) {
"one" -> return 1
"two" -> return 2
else -> return null
}
}
Use ‘when’ as expression body
fun parseNum(number: String): Int? {
when (number) {
"one" -> return 1
"two" -> return 2
else -> return null
}
}
fun parseNum(number: String) =
when (number) {
"one" -> 1
"two" -> 2
else -> null
}
Use ‘try’ as expression body
fun tryParse(number: String): Int? {
try {
return Integer.parseInt(number)
}
catch (e: NumberFormatException) {
return null
}
}
Use ‘try’ as expression body
fun tryParse(number: String): Int? {
try {
return Integer.parseInt(number)
}
catch (e: NumberFormatException) {
return null
}
}
fun tryParse(number: String) =
try {
Integer.parseInt(number)
}
catch (e: NumberFormatException) {
null
}
Use ‘try’ as expression
fun tryParse(number: String): Int? {
try {
return Integer.parseInt(number)
}
catch (e: NumberFormatException) {
return null
}
}
fun tryParse(number: String): Int? {
val n = try {
Integer.parseInt(number)
} catch (e: NumberFormatException) {
null
}
println(n)
return n
}
Use elvis with ‘return’ and ‘throw’
fun processPerson(person: Person) {
val name = person.name
if (name == null)
throw IllegalArgumentException(

"Named required")
val age = person.age
if (age == null) return
println("$name: $age")
}
class Person(val name: String?,
val age: Int?)
Use elvis with ‘return’ and ‘throw’
fun processPerson(person: Person) {
val name = person.name
if (name == null)
throw IllegalArgumentException(

"Named required")
val age = person.age
if (age == null) return
println("$name: $age")
}
fun processPerson(person: Person) {
val name = person.name ?:
throw IllegalArgumentException(
"Name required")
val age = person.age ?: return
println("$name: $age")
}
class Person(val name: String?,
val age: Int?)
Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =
c >= 'A' && c <= 'Z'
Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =
c >= 'A' && c <= 'Z'
fun isLatinUppercase(c: Char) =
c in 'A'..'Z'
Classes and Functions
Don’t create classes just to put functions in
class StringUtils {
companion object {
fun isPhoneNumber(s: String) =
s.length == 7 &&
s.all { it.isDigit() }
}
}
Don’t create classes just to put functions in
class StringUtils {
companion object {
fun isPhoneNumber(s: String) =
s.length == 7 &&
s.all { it.isDigit() }
}
}
object StringUtils {
fun isPhoneNumber(s: String) =
s.length == 7 &&
s.all { it.isDigit() }
}
Don’t create classes just to put functions in
class StringUtils {
companion object {
fun isPhoneNumber(s: String) =
s.length == 7 &&
s.all { it.isDigit() }
}
} fun isPhoneNumber(s: String) =
s.length == 7 &&
s.all { it.isDigit() }
object StringUtils {
fun isPhoneNumber(s: String) =
s.length == 7 &&
s.all { it.isDigit() }
}
Use extension functions copiously
fun isPhoneNumber(s: String) =
s.length == 7 &&
s.all { it.isDigit() }
Use extension functions copiously
fun isPhoneNumber(s: String) =
s.length == 7 &&
s.all { it.isDigit() }
fun String.isPhoneNumber() =
length == 7 &&
all { it.isDigit() }
Avoid using member extension functions 

(unless required for DSLs)
class PhoneBook {
fun String.isPhoneNumber() =
length == 7 &&
all { it.isDigit() }
}
Avoid using member extension functions 

(unless required for DSLs)
class PhoneBook {
fun String.isPhoneNumber() =
length == 7 &&
all { it.isDigit() }
}
class PhoneBook {
}
private fun
String.isPhoneNumber() =
length == 7 &&
all { it.isDigit() }
Don't use member extensions with 

containing class as the receiver
class PhoneBook {
fun PhoneBook.find(name: String)=
"1234567"
}
Don't use member extensions with 

containing class as the receiver
class PhoneBook {
fun PhoneBook.find(name: String)=
"1234567"
}
class PhoneBook {
fun find(name: String) =
"1234567"
}
Consider extracting non-essential API

of classes into extensions
class Person(val firstName: String,
val lastName: String) {
val fullName: String
get() = "$firstName $lastName"
}
Consider extracting non-essential API

of classes into extensions
class Person(val firstName: String,
val lastName: String) {
val fullName: String
get() = "$firstName $lastName"
}
class Person(val firstName: String,
val lastName: String)
val Person.fullName: String
get() = "$firstName $lastName"
Use default parameter values instead of overloads
wherever possible
class Phonebook {
fun print() {
print(",")
}
fun print(separator: String) {
}
}
fun main(args: Array<String>) {
Phonebook().print("|")
}
Use default parameter values instead of overloads
wherever possible
class Phonebook {
fun print() {
print(",")
}
fun print(separator: String) {
}
}
fun main(args: Array<String>) {
Phonebook().print("|")
}
class Phonebook {
fun print(

separator: String = ",") {
}
}
fun main(args: Array<String>) {
Phonebook().print(
separator = "|")
}
Use ‘lateinit’ for properties that can’t be initialised
in a constructor
class MyTest {
class State(val data: String)
var state: State? = null
@Before fun setup() {
state = State("abc")
}
@Test fun foo() {
Assert.assertEquals(

"abc", state!!.data)
}
}
Use ‘lateinit’ for properties that can’t be initialised
in a constructor
class MyTest {
class State(val data: String)
var state: State? = null
@Before fun setup() {
state = State("abc")
}
@Test fun foo() {
Assert.assertEquals(

"abc", state!!.data)
}
}
class MyTest {
class State(val data: String)
lateinit var state: State
@Before fun setup() {
state = State("abc")
}
@Test fun foo() {
Assert.assertEquals(

"abc", state.data)
}
}
Use type aliases for functional types and
collections
class EventDispatcher {
fun addClickHandler(

handler: (Event) -> Unit) {
}
fun removeClickHandler(
handler: (Event) -> Unit) {
}
}
Use type aliases for functional types and
collections
class EventDispatcher {
fun addClickHandler(

handler: (Event) -> Unit) {
}
fun removeClickHandler(
handler: (Event) -> Unit) {
}
}
typealias ClickHandler =
(Event) -> Unit
class EventDispatcher {
fun addClickHandler(
handler: ClickHandler) {
}
fun removeClickHandler(
handler: ClickHandler) {
}
}
Use type aliases for functional types and
collections
class EventDispatcher {
fun addClickHandler(

handler: (Event) -> Unit) {
}
fun removeClickHandler(
handler: (Event) -> Unit) {
}
}
typealias ClickHandler =
(Event) -> Unit
typealias HandlerMap =
Map<EventType, List<Event>>
Use data classes to return multiple values
fun namedNum(): Pair<Int, String> =
1 to "one"
fun main(args: Array<String>) {
val pair = namedNum()
val number = pair.first
val name = pair.second
}
Use data classes to return multiple values
fun namedNum(): Pair<Int, String> =
1 to "one"
fun main(args: Array<String>) {
val pair = namedNum()
val number = pair.first
val name = pair.second
}
data class NamedNumber(
val number: Int,
val name: String)
fun namedNum() =
NamedNumber(1, "one")
fun main(args: Array<String>) {
val (number, name) =

namedNum()
}
Use destructuring in loops
fun printMap(m: Map<String, String>) {
for (e in m.entries) {
println("${e.key} -> ${e.value}")
}
}
Use destructuring in loops
fun printMap(m: Map<String, String>) {
for (e in m.entries) {
println("${e.key} -> ${e.value}")
}
}
fun printMap(m: Map<String, String>) {
for ((key, value) in m) {
println("$key -> $value")
}
}
Use destructuring with lists
fun splitNameExt(fn: String): NameExt {
if ('.' in fn) {
val parts = fn.split('.', limit = 2)
return NameExt(parts[0], parts[1])
}
return NameExt(fn, null)
}
data class NameExt(val name: String,
val ext: String?)
Use destructuring with lists
fun splitNameExt(fn: String): NameExt {
if ('.' in fn) {
val parts = fn.split('.', limit = 2)
return NameExt(parts[0], parts[1])
}
return NameExt(fn, null)
}
fun splitNameExt(fn: String): NameExt {
if ('.' in fn) {
val (name, ext) =
fn.split('.', limit = 2)
return NameExt(name, ext)
}
return NameExt(fn, null)
}
data class NameExt(val name: String,
val ext: String?)
Use ‘copy’ method for data classes
class Person(val name: String,
var age: Int)
fun happyBirthday(person: Person) {
person.age++
}
Use ‘copy’ method for data classes
class Person(val name: String,
var age: Int)
fun happyBirthday(person: Person) {
person.age++
}
data class Person(val name: String,
val age: Int)
fun happyBirthday(person: Person) =
person.copy(
age = person.age + 1)
The Standard Library
Use ‘coerceIn’ to ensure a number is in range
fun updateProgress(value: Int) {
val newValue = when {
value < 0 -> 0
value > 100 -> 100
else -> value
}
}
Use ‘coerceIn’ to ensure a number is in range
fun updateProgress(value: Int) {
val newValue = when {
value < 0 -> 0
value > 100 -> 100
else -> value
}
}
fun updateProgress(value: Int) {
val newValue = 

value.coerceIn(0, 100)
}
Use ‘apply’ for object initialisation
fun createLabel(): JLabel {
val label = JLabel("Foo")
label.foreground = Color.RED
label.background = Color.BLUE
return label
}
Use ‘apply’ for object initialisation
fun createLabel(): JLabel {
val label = JLabel("Foo")
label.foreground = Color.RED
label.background = Color.BLUE
return label
}
fun createLabel() =
JLabel("Foo").apply {
foreground = Color.RED
background = Color.BLUE
}
Use ‘filterIsInstance’ to filter a list by object type
fun findStrings(objs: List<Any>) =
objs.filter { it is String }
Use ‘filterIsInstance’ to filter a list by object type
fun findStrings(objs: List<Any>) =
objs.filter { it is String }
fun findStrings(objs: List<Any>) =
obs.filterIsInstance<String>()
Use ‘mapNotNull’ to apply a function and select
items for which it returns a non-null value
fun listErrors(

results: List<Result>) =
results
.map { it.error }
.filterNotNull()
data class Result(val data: Any?,
val error: String?)
Use ‘mapNotNull’ to apply a function and select
items for which it returns a non-null value
fun listErrors(

results: List<Result>) =
results
.map { it.error }
.filterNotNull()
fun listErrors(
results: List<Result>) =
results.mapNotNull { 

it.error
}
data class Result(val data: Any?,
val error: String?)
Use ‘compareBy’ for multi-step comparisons
fun sortPersons(persons: List<Person>) =
persons.sortedWith(
Comparator<Person> { p1, p2 ->
val rc =
p1.name.compareTo(p2.name)
if (rc != 0)
rc
else
p1.age - p2.age
})
class Person(val name: String,
val age: Int)
Use ‘compareBy’ for multi-step comparisons
fun sortPersons(persons: List<Person>) =
persons.sortedWith(
Comparator<Person> { p1, p2 ->
val rc =
p1.name.compareTo(p2.name)
if (rc != 0)
rc
else
p1.age - p2.age
})
fun sortPersons(persons: List<Person>) =
persons.sortedWith(
compareBy(Person::name,
Person::age))
class Person(val name: String,
val age: Int)
Use ‘groupBy’ to group items in a collection
fun analyzeLog(log: List<Request>) {
val map = mutableMapOf<String,
MutableList<Request>>()
for (request in log) {
map.getOrPut(request.url)
{ mutableListOf() }
.add(request)
}
}
class Request(val url: String,
val remoteIP: String,
val timestamp: Long)
Use ‘groupBy’ to group items in a collection
fun analyzeLog(log: List<Request>) {
val map = mutableMapOf<String,
MutableList<Request>>()
for (request in log) {
map.getOrPut(request.url)
{ mutableListOf() }
.add(request)
}
}
fun analyzeLog(log: List<Request>) {
val map = log.groupBy(Request::url)
}
class Request(val url: String,
val remoteIP: String,
val timestamp: Long)
Use String methods for string parsing
val pattern = Regex("(.+)/([^/]*)")
fun splitPath(path: String): PathParts {
val match = pattern.matchEntire(path)
?: return PathParts("", path)
return PathParts(match.groupValues[1],
match.groupValues[2])
}
data class PathParts(val dir: String,
val name: String)
Use String methods for string parsing
val pattern = Regex("(.+)/([^/]*)")
fun splitPath(path: String): PathParts {
val match = pattern.matchEntire(path)
?: return PathParts("", path)
return PathParts(match.groupValues[1],
match.groupValues[2])
}
fun splitPath(path: String) =
PathParts(
path.substringBeforeLast('/', ""),
path.substringAfterLast(‘/'))
data class PathParts(val dir: String,
val name: String)
Q&A
yole@jetbrains.com
@intelliyole

Idiomatic Kotlin

  • 1.
    Idiomatic Kotlin Dmitry Jemerov<yole@jetbrains.com>
  • 2.
  • 3.
    Use ‘when’ asexpression body fun parseNum(number: String): Int? { when (number) { "one" -> return 1 "two" -> return 2 else -> return null } }
  • 4.
    Use ‘when’ asexpression body fun parseNum(number: String): Int? { when (number) { "one" -> return 1 "two" -> return 2 else -> return null } } fun parseNum(number: String) = when (number) { "one" -> 1 "two" -> 2 else -> null }
  • 5.
    Use ‘try’ asexpression body fun tryParse(number: String): Int? { try { return Integer.parseInt(number) } catch (e: NumberFormatException) { return null } }
  • 6.
    Use ‘try’ asexpression body fun tryParse(number: String): Int? { try { return Integer.parseInt(number) } catch (e: NumberFormatException) { return null } } fun tryParse(number: String) = try { Integer.parseInt(number) } catch (e: NumberFormatException) { null }
  • 7.
    Use ‘try’ asexpression fun tryParse(number: String): Int? { try { return Integer.parseInt(number) } catch (e: NumberFormatException) { return null } } fun tryParse(number: String): Int? { val n = try { Integer.parseInt(number) } catch (e: NumberFormatException) { null } println(n) return n }
  • 8.
    Use elvis with‘return’ and ‘throw’ fun processPerson(person: Person) { val name = person.name if (name == null) throw IllegalArgumentException(
 "Named required") val age = person.age if (age == null) return println("$name: $age") } class Person(val name: String?, val age: Int?)
  • 9.
    Use elvis with‘return’ and ‘throw’ fun processPerson(person: Person) { val name = person.name if (name == null) throw IllegalArgumentException(
 "Named required") val age = person.age if (age == null) return println("$name: $age") } fun processPerson(person: Person) { val name = person.name ?: throw IllegalArgumentException( "Name required") val age = person.age ?: return println("$name: $age") } class Person(val name: String?, val age: Int?)
  • 10.
    Use range checksinstead of comparison pairs fun isLatinUppercase(c: Char) = c >= 'A' && c <= 'Z'
  • 11.
    Use range checksinstead of comparison pairs fun isLatinUppercase(c: Char) = c >= 'A' && c <= 'Z' fun isLatinUppercase(c: Char) = c in 'A'..'Z'
  • 12.
  • 13.
    Don’t create classesjust to put functions in class StringUtils { companion object { fun isPhoneNumber(s: String) = s.length == 7 && s.all { it.isDigit() } } }
  • 14.
    Don’t create classesjust to put functions in class StringUtils { companion object { fun isPhoneNumber(s: String) = s.length == 7 && s.all { it.isDigit() } } } object StringUtils { fun isPhoneNumber(s: String) = s.length == 7 && s.all { it.isDigit() } }
  • 15.
    Don’t create classesjust to put functions in class StringUtils { companion object { fun isPhoneNumber(s: String) = s.length == 7 && s.all { it.isDigit() } } } fun isPhoneNumber(s: String) = s.length == 7 && s.all { it.isDigit() } object StringUtils { fun isPhoneNumber(s: String) = s.length == 7 && s.all { it.isDigit() } }
  • 16.
    Use extension functionscopiously fun isPhoneNumber(s: String) = s.length == 7 && s.all { it.isDigit() }
  • 17.
    Use extension functionscopiously fun isPhoneNumber(s: String) = s.length == 7 && s.all { it.isDigit() } fun String.isPhoneNumber() = length == 7 && all { it.isDigit() }
  • 18.
    Avoid using memberextension functions 
 (unless required for DSLs) class PhoneBook { fun String.isPhoneNumber() = length == 7 && all { it.isDigit() } }
  • 19.
    Avoid using memberextension functions 
 (unless required for DSLs) class PhoneBook { fun String.isPhoneNumber() = length == 7 && all { it.isDigit() } } class PhoneBook { } private fun String.isPhoneNumber() = length == 7 && all { it.isDigit() }
  • 20.
    Don't use memberextensions with 
 containing class as the receiver class PhoneBook { fun PhoneBook.find(name: String)= "1234567" }
  • 21.
    Don't use memberextensions with 
 containing class as the receiver class PhoneBook { fun PhoneBook.find(name: String)= "1234567" } class PhoneBook { fun find(name: String) = "1234567" }
  • 22.
    Consider extracting non-essentialAPI
 of classes into extensions class Person(val firstName: String, val lastName: String) { val fullName: String get() = "$firstName $lastName" }
  • 23.
    Consider extracting non-essentialAPI
 of classes into extensions class Person(val firstName: String, val lastName: String) { val fullName: String get() = "$firstName $lastName" } class Person(val firstName: String, val lastName: String) val Person.fullName: String get() = "$firstName $lastName"
  • 24.
    Use default parametervalues instead of overloads wherever possible class Phonebook { fun print() { print(",") } fun print(separator: String) { } } fun main(args: Array<String>) { Phonebook().print("|") }
  • 25.
    Use default parametervalues instead of overloads wherever possible class Phonebook { fun print() { print(",") } fun print(separator: String) { } } fun main(args: Array<String>) { Phonebook().print("|") } class Phonebook { fun print(
 separator: String = ",") { } } fun main(args: Array<String>) { Phonebook().print( separator = "|") }
  • 26.
    Use ‘lateinit’ forproperties that can’t be initialised in a constructor class MyTest { class State(val data: String) var state: State? = null @Before fun setup() { state = State("abc") } @Test fun foo() { Assert.assertEquals(
 "abc", state!!.data) } }
  • 27.
    Use ‘lateinit’ forproperties that can’t be initialised in a constructor class MyTest { class State(val data: String) var state: State? = null @Before fun setup() { state = State("abc") } @Test fun foo() { Assert.assertEquals(
 "abc", state!!.data) } } class MyTest { class State(val data: String) lateinit var state: State @Before fun setup() { state = State("abc") } @Test fun foo() { Assert.assertEquals(
 "abc", state.data) } }
  • 28.
    Use type aliasesfor functional types and collections class EventDispatcher { fun addClickHandler(
 handler: (Event) -> Unit) { } fun removeClickHandler( handler: (Event) -> Unit) { } }
  • 29.
    Use type aliasesfor functional types and collections class EventDispatcher { fun addClickHandler(
 handler: (Event) -> Unit) { } fun removeClickHandler( handler: (Event) -> Unit) { } } typealias ClickHandler = (Event) -> Unit class EventDispatcher { fun addClickHandler( handler: ClickHandler) { } fun removeClickHandler( handler: ClickHandler) { } }
  • 30.
    Use type aliasesfor functional types and collections class EventDispatcher { fun addClickHandler(
 handler: (Event) -> Unit) { } fun removeClickHandler( handler: (Event) -> Unit) { } } typealias ClickHandler = (Event) -> Unit typealias HandlerMap = Map<EventType, List<Event>>
  • 31.
    Use data classesto return multiple values fun namedNum(): Pair<Int, String> = 1 to "one" fun main(args: Array<String>) { val pair = namedNum() val number = pair.first val name = pair.second }
  • 32.
    Use data classesto return multiple values fun namedNum(): Pair<Int, String> = 1 to "one" fun main(args: Array<String>) { val pair = namedNum() val number = pair.first val name = pair.second } data class NamedNumber( val number: Int, val name: String) fun namedNum() = NamedNumber(1, "one") fun main(args: Array<String>) { val (number, name) =
 namedNum() }
  • 33.
    Use destructuring inloops fun printMap(m: Map<String, String>) { for (e in m.entries) { println("${e.key} -> ${e.value}") } }
  • 34.
    Use destructuring inloops fun printMap(m: Map<String, String>) { for (e in m.entries) { println("${e.key} -> ${e.value}") } } fun printMap(m: Map<String, String>) { for ((key, value) in m) { println("$key -> $value") } }
  • 35.
    Use destructuring withlists fun splitNameExt(fn: String): NameExt { if ('.' in fn) { val parts = fn.split('.', limit = 2) return NameExt(parts[0], parts[1]) } return NameExt(fn, null) } data class NameExt(val name: String, val ext: String?)
  • 36.
    Use destructuring withlists fun splitNameExt(fn: String): NameExt { if ('.' in fn) { val parts = fn.split('.', limit = 2) return NameExt(parts[0], parts[1]) } return NameExt(fn, null) } fun splitNameExt(fn: String): NameExt { if ('.' in fn) { val (name, ext) = fn.split('.', limit = 2) return NameExt(name, ext) } return NameExt(fn, null) } data class NameExt(val name: String, val ext: String?)
  • 37.
    Use ‘copy’ methodfor data classes class Person(val name: String, var age: Int) fun happyBirthday(person: Person) { person.age++ }
  • 38.
    Use ‘copy’ methodfor data classes class Person(val name: String, var age: Int) fun happyBirthday(person: Person) { person.age++ } data class Person(val name: String, val age: Int) fun happyBirthday(person: Person) = person.copy( age = person.age + 1)
  • 39.
  • 40.
    Use ‘coerceIn’ toensure a number is in range fun updateProgress(value: Int) { val newValue = when { value < 0 -> 0 value > 100 -> 100 else -> value } }
  • 41.
    Use ‘coerceIn’ toensure a number is in range fun updateProgress(value: Int) { val newValue = when { value < 0 -> 0 value > 100 -> 100 else -> value } } fun updateProgress(value: Int) { val newValue = 
 value.coerceIn(0, 100) }
  • 42.
    Use ‘apply’ forobject initialisation fun createLabel(): JLabel { val label = JLabel("Foo") label.foreground = Color.RED label.background = Color.BLUE return label }
  • 43.
    Use ‘apply’ forobject initialisation fun createLabel(): JLabel { val label = JLabel("Foo") label.foreground = Color.RED label.background = Color.BLUE return label } fun createLabel() = JLabel("Foo").apply { foreground = Color.RED background = Color.BLUE }
  • 44.
    Use ‘filterIsInstance’ tofilter a list by object type fun findStrings(objs: List<Any>) = objs.filter { it is String }
  • 45.
    Use ‘filterIsInstance’ tofilter a list by object type fun findStrings(objs: List<Any>) = objs.filter { it is String } fun findStrings(objs: List<Any>) = obs.filterIsInstance<String>()
  • 46.
    Use ‘mapNotNull’ toapply a function and select items for which it returns a non-null value fun listErrors(
 results: List<Result>) = results .map { it.error } .filterNotNull() data class Result(val data: Any?, val error: String?)
  • 47.
    Use ‘mapNotNull’ toapply a function and select items for which it returns a non-null value fun listErrors(
 results: List<Result>) = results .map { it.error } .filterNotNull() fun listErrors( results: List<Result>) = results.mapNotNull { 
 it.error } data class Result(val data: Any?, val error: String?)
  • 48.
    Use ‘compareBy’ formulti-step comparisons fun sortPersons(persons: List<Person>) = persons.sortedWith( Comparator<Person> { p1, p2 -> val rc = p1.name.compareTo(p2.name) if (rc != 0) rc else p1.age - p2.age }) class Person(val name: String, val age: Int)
  • 49.
    Use ‘compareBy’ formulti-step comparisons fun sortPersons(persons: List<Person>) = persons.sortedWith( Comparator<Person> { p1, p2 -> val rc = p1.name.compareTo(p2.name) if (rc != 0) rc else p1.age - p2.age }) fun sortPersons(persons: List<Person>) = persons.sortedWith( compareBy(Person::name, Person::age)) class Person(val name: String, val age: Int)
  • 50.
    Use ‘groupBy’ togroup items in a collection fun analyzeLog(log: List<Request>) { val map = mutableMapOf<String, MutableList<Request>>() for (request in log) { map.getOrPut(request.url) { mutableListOf() } .add(request) } } class Request(val url: String, val remoteIP: String, val timestamp: Long)
  • 51.
    Use ‘groupBy’ togroup items in a collection fun analyzeLog(log: List<Request>) { val map = mutableMapOf<String, MutableList<Request>>() for (request in log) { map.getOrPut(request.url) { mutableListOf() } .add(request) } } fun analyzeLog(log: List<Request>) { val map = log.groupBy(Request::url) } class Request(val url: String, val remoteIP: String, val timestamp: Long)
  • 52.
    Use String methodsfor string parsing val pattern = Regex("(.+)/([^/]*)") fun splitPath(path: String): PathParts { val match = pattern.matchEntire(path) ?: return PathParts("", path) return PathParts(match.groupValues[1], match.groupValues[2]) } data class PathParts(val dir: String, val name: String)
  • 53.
    Use String methodsfor string parsing val pattern = Regex("(.+)/([^/]*)") fun splitPath(path: String): PathParts { val match = pattern.matchEntire(path) ?: return PathParts("", path) return PathParts(match.groupValues[1], match.groupValues[2]) } fun splitPath(path: String) = PathParts( path.substringBeforeLast('/', ""), path.substringAfterLast(‘/')) data class PathParts(val dir: String, val name: String)
  • 56.