Kotlin - ttulka/programming GitHub Wiki
-
Fundamentals
- Variables
- Functions
- Lambdas
- Operator overloading
- Imports
- Data Types
- Nulls
- Conditionals
- For Loop
- While Loop
- Objects
- Interfaces
- Extensions
- Data Classes
- Sealed Classes
- Generics
- Enum Classes
- Object Expressions
- Type aliases
- Exceptions
- Collections
- Coroutines
- I/O
- Compilation and Packaging
- References
fun main(args: Array<String>) {
println("Hello, World!")
}
// read-only
val a: Int = 1 // immediate assignment
val b = 2 // `Int` type is inferred
val c: Int // Type required when no initializer is provided
c = 3 // deferred assignment
// mutable
var x = 5 // `Int` type is inferred
x += 1
Lazy property:
val p: String by lazy {
// compute the string
}
The lazy
function return an object that has a method called getValue
with the proper signature, so you can use it together with the by
keyword to create a delegated property. The argument of lazy
is a lambda that is calls to initialize the value. The lazy
function is thread-safe by default.
Swapping two variables:
var a = 1
var b = 2
a = b.also { b = a }
Destructuring:
val (name, age) = person
for ((key, value) in map) {
}
Underscore for unused variables:
val (_, status) = getResult()
fun sum(a: Int, b: Int): Int {
return a + b
}
// or
fun sum(a: Int, b: Int) = a + b
fun printSum(a: Int, b: Int)/*: Unit*/ {
println("sum of $a and $b is ${a + b}")
}
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { /*...*/ }
fun foo(bar: Int = 0, baz: Int) { /*...*/ }
foo(baz = 1) // The default value bar = 0 is used
- Overriding methods always use the same default parameter values as the base method.
fun printAll(str: String, vararg nums: Int) {
for (n in nums) println(n)
}
printAll("abc", 1, 2, 3)
val arr = intArrayOf(1, 2, 3)
printAll("abc", *arr)
Omitting the dot and the parentheses for the call.
infix fun Int.shl(x: Int): Int { ... }
1 shl 2 // infix notation
// is the same as:
1.shl(2)
Lower precedence than the arithmetic operators, type casts, and the rangeTo operator:
-
1 shl 2 + 3
is equivalent to1 shl (2 + 3)
Kotlin supports a style of functional programming known as tail recursion.
val eps = 1E-10 // "good enough", could be 10^-15
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))
val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
val twoParameters: (String, Int) -> String = repeatFun
fun runTransformation(f: (String, Int) -> String): String {
return f("hello", 3)
}
val result = runTransformation(repeatFun) // hellohellohello
max(strings, { a, b -> a.length < b.length })
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter(fun(item) = item > 0)
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
Overhead can be eliminated by inlining the lambda expressions. Inlining means the body is copied into the bytecode instead of making a call.
- Inlining may cause the generated code to grow.
inline fun <T> lock(lock: Lock, body: () -> T): T { ... }
To execute a block of code within the context of an object.
-
let
,run
,with
,apply
, andalso
. -
let
andrun
useit
as available context object argument. -
apply
andalso
return the context object. -
let
,run
, andwith
return the lambda result.
val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run {
add("four")
add("five")
count { it.endsWith("e") }
}
println("There are $countEndsWithE elements that end with e.")
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
val firstItem = first()
val lastItem = last()
println("First item: $firstItem, last item: $lastItem")
}
- can be used to invoke one or more functions on results of call chains.
- is often used for executing a code block only with non-null values.
val str: String? = "Hello"
//processNonNullString(str) // compilation error: str can be null
val length = str?.let {
println("let() called on $it")
processNonNullString(it) // OK: 'it' is not null inside '?.let { }'
it.length
}
- recommended for calling functions on the context object without providing the lambda result.
- or as a helper object whose properties or functions will be used for calculating a value.
- can be read as “with this object, do the following.”
val numbers = mutableListOf("one", "two", "three")
val firstAndLast = with(numbers) {
"The first element is ${first()}," +
" the last element is ${last()}"
}
println(firstAndLast)
- useful when your lambda contains both the object initialization and the computation of the return value.
- lets you execute a block of several statements where an expression is required.
val hexNumberRegex = run {
val digits = "0-9"
val hexDigits = "A-Fa-f"
val sign = "+-"
Regex("[$sign]?[$digits$hexDigits]+")
}
for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) {
println(match.value)
}
// +1234
// -FFFF
// -a
// be
- for code blocks that don't return a value and mainly operate on the members of the receiver object.
- common case is the object configuration.
- can be read as “apply the following assignments to the object.”
val adam = Person("Adam").apply {
age = 32
city = "London"
}
- for performing some actions that take the context object as an argument.
- for actions that need a reference rather to the object than to its properties and functions, or when you don't want to shadow this reference from an outer scope.
- can read it as “and also do the following with the object.”
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
These functions let you embed checks of the object state in call chains.
-
takeIf
returns this object if it matches the predicate. -
takeUnless
returns the object if it doesn't match the predicate and null if it does.
val number = Random.nextInt(100)
val evenOrNull = number.takeIf { it % 2 == 0 }
val oddOrNull = number.takeUnless { it % 2 == 0 }
One can move lambdas out of the parenthesis if the lambda is the last parameter:
run { println("I'm in a lambda") }
// is a shortcut for: run({ println("I'm in a lambda") })
// or { println("I'm in a lambda") }()
If a lambda has only one parameter, it
can be used instead:
val lambda: (Int) -> String = { "Value: $it" }
// is a shortcut for: val lambda: (Int) -> String = { v -> "Value: $v" }
typealias IntToString = (Int) -> String
val lambda1: IntToString = { i -> "$i" }
val lambda2: IntToString = { "$it" }
val lambda3: IntToString = Int::toString // callable reference to an existing declaration
val lambda4: IntToString = fun (i) = "$i"
interface IntToString: (Int) -> String
val lambda1: IntToString = object: IntToString {
override operator fun invoke(i: Int) = "$i"
}
val lambda2: (Int) -> String = object: IntToString {
override operator fun invoke(i: Int) = "$i"
}
class IntToString: (Int) -> String {
override operator fun invoke(i: Int): String = "$i"
}
val lambda: (Int) -> String = IntToString()
fun countTo100(): String {
val sb = StringBuilder()
return with(sb) {
for (i in 1..100) {
append(i)
append(", ")
}
toString() // last expression is returned
}
}
// or even:
fun countTo100() =
with(StringBuilder()) {
for (i in 1..100) {
append(i)
append(", ")
}
toString()
}
// or:
fun countTo100() =
StringBuilder().apply {
for (i in 1..100) {
append(i)
append(", ")
}
}.toString()
fun findByName(people: List<Person>, name: String) {
people.forEach myReturnLabel@ {
if (it.name == name) {
println("Person $name found.")
return@myReturnLabel // otherwise the whole function would return
}
}
println("Finding a person finished.")
}
"Some string".apply somestr@ {
"Other string".apply {
println(this.toUpperCase()) // OTHER STRING
println(this@somestr.toUpperCase()) // SOME STRING
}
}
https://kotlinlang.org/docs/reference/operator-overloading.html
Expression | Translated to |
---|---|
+a |
a.unaryPlus() |
-a |
a.unaryMinus() |
!a |
a.not() |
`a++ | a.inc() |
a-- |
a.dec() |
a + b |
a.plus(b) |
a - b |
a.minus(b) |
data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus() = Point(-x, -y)
val point = Point(10, 20)
fun main() {
println(-point) // prints "Point(x=-10, y=-20)"
}
import org.example.Message // Message is now accessible without qualification
// or
import org.example.* // everything in 'org.example' becomes accessible
// or
import org.test.Message as testMessage // testMessage stands for 'org.test.Message'
kotlin.*
kotlin.annotation.*
kotlin.collections.*
kotlin.comparisons.*
kotlin.io.*
kotlin.ranges.*
kotlin.sequences.*
kotlin.text.*
-
java.lang.*
(JVM) -
kotlin.jvm.*
(JVM) -
kotlin.js.*
(JS)
Everything is a class (no primitive types).
var myInt = 2
var myLong = 2L // val myLong: Long = 2
//myLong = myInt // Error
myLong = myInt.toLong()
val myByte: Byte = 2
val myShort: Short = 2
val myDouble = 2.0
val myFloat = 2.0f
//myDouble = myFloat // Error
myDouble = myFloat.toDouble()
val myChar = 'a'
val myChar2 = 97.toChar() // 'a'
val myBoolean = true
-
Any
is basis fortoString()
,hashCode()
andequals()
(==
) -
Unit
is a singleton object as void for functions returning nothing. -
Nothing
is a subclass of any class. For infinite loops, etc.
Terms type and class are not equivalent. For example, a class can be used to declare a nullable type: var x: Int?
, that means each Kotlin class can be used to construct at least two types.
If an immutable local variable or property is checked for a specific type, there's no need to cast it explicitly:
if (obj is String) {
// `obj` is automatically cast to `String` in this branch
return obj.length
}
// or even
// `obj` is automatically cast to `String` on the right-hand side of `&&`
if (obj is String && obj.length > 0) {
return obj.length
}
A reference must be explicitly marked as nullable when null
value is possible:
fun parseInt(str: String): Int? {
return null
}
-
==
operator does null check automatically. -
[]
array operator does null check automatically.
Nullable values are automatically cast to non-nullable after null check:
val x = parseInt(arg1)
val y = parseInt(arg2)
// println(x * y) // Compilation Error
if (x != null && y != null) {
println(x * y) // ok here
}
If not null shorthand:
val files = File("Test").listFiles()
println(files?.size)
If not null and else shorthand:
val files = File("Test").listFiles()
println(files?.size ?: "empty")
Executing a statement if null:
val values = ...
val email = values["email"] ?: throw Exception("Email is missing!")
Execute if not null:
val value = ...
value?.let {
// execute this block if not null
}
Map nullable value if not null:
val value = ...
val mapped = value?.let { transformValue(it) } ?: defaultValue
// defaultValue is returned if the value or the transform result is null.
Consuming a nullable Boolean:
val b: Boolean? = ...
if (b == true) {
...
} else {
// `b` is false or null
}
If we do want NullPointerException
to be thrown:
val str: String? = null
str!!.toUpperCase() // assertion !! thows when null
var a = 1
// simple name in template:
val s1 = "a is $a"
a = 2
// arbitrary expression in template:
val s2 = "${s1.replace("is", "was")}, but now is $a"
val filePath = """c:\dir\file"""
val multiLine = """This is printed
|on multiple
|lines.""".trimMargin()
val strings: Array<String> = arrayOf("a","b","c")
val longs = arrayOf<Long>(1,2,3)
longs[0] // 1L
val integers: Array<Int>
integers = arrayOf(1,2,3)
val mixed = arrayOf(1, "Abc", true)
mixed is Array<Any> // true
val evenNumbers = Array(16) { i -> i * 2 }
val allZeros = Array(16) { 0 }
val nulls = arrayOfNulls<Int?>(5)
val integers1 = arrayOf(1,2,3)
val integers2 = arrayOf(4,5)
val allIntegers = arrayOf(*integers1, *integers2, 6)
// [1, 2, 3, 4, 5, 6]
- better performance
- compatible with Java's arrays (eg.
int[]
)
val myIntArray = intArrayOf(1,2,3)
val myIntArray2 = arrayOf(1,2,3).toIntArray()
myIntArray2 is IntArray // true
val myIntArray3 = myIntArray2.toTypedArray()
myIntArray3 is Array<Int> // true
- constructor doesn't need the second init value:
val myIntArray = IntArray(16)
fun maxOf(a: Int, b: Int): Int {
if (a > b) {
return a
} else {
return b
}
}
// or
fun maxOf(a: Int, b: Int) = if (a > b) a else b
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
2, 3 -> "Two or Three"
in 10..99 -> "Between 10 and 99"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
Checking if a collection contains an object using in operator:
when {
"orange" in items -> println("juicy")
"apple" in items -> println("apple is fine too")
}
val r1 = 1..5
val r2 = 'a'..'z'
val r3 = "ABC".."XYZ"
3 in r1 // true
'c' in r2 // true
"DFC" in r3 // true
Check if a number is within a range using in operator:
val x = 10
val y = 9
if (x in 1..y+1) {
println("fits in range")
}
val items = listOf("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}
// or
val items = listOf("apple", "banana", "kiwifruit")
for (index in items.indices) {
println("item at $index is ${items[index]}")
}
for ((k, v) in map) {
println("$k -> $v")
}
for (x in 1..5) {
print(x)
}
for (x in 1..10 step 2) {
print(x)
}
println()
for (x in 9 downTo 0 step 3) {
print(x)
}
val items = listOf("apple", "banana", "kiwifruit")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index]}")
index++
}
-
return
by default returns from the nearest enclosing function or anonymous function. -
break
terminates the nearest enclosing loop. -
continue
proceeds to the next step of the nearest enclosing loop.
loop@ for (i in 1..100) {
for (j in 1..100) {
if (...) break@loop
}
}
class Invoice { /*...*/ }
// or
class Invoice
Classes can contain class members:
- Constructors and initializer blocks
- Functions
- Properties
- Nested and Inner Classes
- Object Declarations
A class in Kotlin can have a primary constructor and one or more secondary constructors:
- The primary constructor cannot contain any code.
class Person constructor(val name: String) { /*...*/ }
// or
class Person constructor(name: String) { /*...*/ }
// or
class Person(name: String) { /*...*/ }
class Person(val name: String) {
var children: MutableList<Person> = mutableListOf<>()
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
Initialization code can be placed in initializer blocks:
- Initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers.
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println) // 1
init {
println("First initializer block that prints ${name}") // 2
}
val secondProperty = "Second property: ${name.length}".also(::println) // 3
init {
println("Second initializer block that prints ${name.length}") // 4
}
}
class Constructors {
constructor(i: Int) {
println("Constructor") // 2
}
init {
println("Init block") // 1
}
}
If the constructor has annotations or visibility modifiers, the constructor keyword is required, and the modifiers go before it:
class Person public @Inject constructor(name: String) { /*...*/ }
class DontCreateMe private constructor () { /*...*/ }
Kotlin does not have a new
keyword:
val invoice = Invoice()
val person = Person("Joe Smith")
open class Base(p: Int)
class Derived(p: Int) : Base(p)
- All classes in Kotlin have a common superclass
Any
. - By default, Kotlin classes are final: they can’t be inherited.
- To make a class inheritable, mark it with the
open
keyword:
open class Base // open for inheritance
- Each secondary constructor has to initialize the base type using the
super
keyword:
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
Kotlin requires explicit modifiers for overridable members:
open class Shape {
open fun draw() { /*...*/ }
fun fill() { /*...*/ }
}
class Circle() : Shape() {
override fun draw() { /*...*/ }
}
Code in a derived class can call its superclass functions and property accessors implementations using the super
keyword:
open class Rectangle {
open fun draw() { println("Drawing a rectangle") }
val borderColor: String get() = "black"
}
class FilledRectangle : Rectangle() {
override fun draw() {
super.draw()
println("Filling the rectangle")
}
val fillColor: String get() = super.borderColor
}
Inside an inner class, accessing the superclass of the outer class is done with the super
keyword qualified with the outer class name: super@Outer
:
class FilledRectangle: Rectangle() {
fun draw() { /* ... */ }
val borderColor: String get() = "black"
inner class Filler {
fun fill() { /* ... */ }
fun drawAndFill() {
super@FilledRectangle.draw() // Calls Rectangle's implementation of draw()
fill()
println("Drawn with color ${super@FilledRectangle.borderColor}")
}
}
}
A class and some of its members may be declared abstract
:
open class Polygon {
open fun draw() {}
}
abstract class Rectangle : Polygon() {
abstract override fun draw()
}
class Person(name: String) {
var name = name
get() { return field }
set(value) { field = value }
}
// is equivalent to
class Person(var name: String)
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}
val isEmpty get() = this.size == 0
var setterVisibility: String = "abc"
private set
var setterWithAnnotation: Any? = null
@Inject set
When you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
-
public
(default) - visible everywhere, -
private
- only be visible inside the file containing the declaration, -
protected
- not available for top-level declarations, -
internal
- visible everywhere in the same module.
// file name: example.kt
package foo
private fun foo() { ... } // visible inside example.kt
public var bar: Int = 5 // property is visible everywhere
private set // setter is visible only in example.kt
internal val baz = 6 // visible inside the same module
-
public
(default) - any client who sees the declaring class sees its public members, -
private
- means visible inside this class only (including all its members), -
protected
- same as private + visible in subclasses too, -
internal
- any client inside this module who sees the declaring class sees its internal members.
open class Outer {
private val a = 1
protected open val b = 2
internal val c = 3
val d = 4 // public by default
protected class Nested {
public val e: Int = 5
}
}
class Subclass : Outer() {
// a is not visible
// b, c and d are visible
// Nested and e are visible
override val b = 5 // 'b' is protected
}
class Unrelated(o: Outer) {
// o.a, o.b are not visible
// o.c and o.d are visible (same module)
// Outer.Nested is not visible, and Nested::e is not visible either
}
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print()
}
val employee1 = Employee("Mary", 33)
val employee2 = Employee("Mary", 33)
employee1 == employee2 // true, Structural Equality
employee1 === employee2 // false, Referencial Equality
val something: Any = ...
if (something is Employee) {
var employee = something // as Employee // not necessary
println(emloyee.name)
}
class MyClass private constructor() {
companion object Factory { /* name is optional */
fun create(): MyClass = MyClass()
}
}
//val instance = MyClass() // Error, private constructor
val instance = MyClass.create()
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
val f: Factory<MyClass> = MyClass
val instance = f.create()
interface MyInterface {
val prop: Int // abstract
val propertyWithImplementation: String
get() = "foo"
fun bar()
fun foo() {
// optional body
}
}
class Child : MyInterface {
override val prop: Int = 29
override fun bar() {
// body
}
}
Kotlin provides the ability to extend a class with new functionality without having to inherit from the class.
- Extensions are resolved statically
// The following adds a swap function to MutableList<Int>:
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
val list = mutableListOf(1, 2, 3)
list.swap(0, 2)
Classes whose main purpose is to hold data.
- The primary constructor needs to have at least one parameter.
- All primary constructor parameters need to be marked as
val
orvar
. - Data classes cannot be abstract, open, sealed or inner.
- Data classes may only implement interfaces.
data class Person(val name: String, val age: Int)
Following members from all properties declared in the primary constructor:
-
equals()
/hashCode()
pair; -
toString()
of the form "User(name=John, age=42)"; -
componentN()
functions corresponding to the properties in their order of declaration; -
copy()
function
val jack = Person(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"
Sealed classes are used for representing restricted class hierarchies, when a value can have one of the types from a limited set, but cannot have any other type.
- They are, in a sense, an extension of enum classes.
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}
Marking a superclass with the sealed
modifier restricts creating subclasses. All the direct subclasses must be nested in the superclass.
If you handle all subclasses of a sealed
class in a when
expression, you don't need to provide the default branch.
class Box<T>(t: T) {
var value = t
}
val box: Box<Int> = Box<Int>(1)
// or
val box = Box(1)
val list: ArrayList<Int> = arrayListOf(1, 2, 3)
// List is covariant: List<out E>
val li2: List<Int> = list
val li3: List<Number> = list
val li4: List<Any> = list
val li5: List<*> = list
// ArrayList is invariant, not covariant
//val li6: ArrayList<Number> = list // Error
fun <T> processList(list: List<T>) { }
// equivalent to
fun <T: Any?> processList(list: List<T>) { }
// only non-nullable values:
fun <T: Any> processList(list: List<T>) { }
fun <T: String> upperList(list: List<T>) =
list.map { it.toUpperCase() }
fun <T> appendList(list: List<T>)
where T: CharSequence, T: Appendable =
list.map { it.append(",") }
- As JVM removes generics in the runtime, one cannot check generic type
T
. - In Kotlin this is possible with
inline
function andreified
parameters:
inline fun <reified T> getElementOfType(list: List<Any>): List<T> =
list.filter { it is T } as List<T>
val integers: List<Int> = getElementOfType(listOf("abc", 1, true, 5)) // [1, 5]
A generic class, eg. MutableList
, is called invariant on the type parameter if, for any two different types A
and B
, MutableList<A>
isn't a subtype of MutableList<B>
. In Java, all classes are invariant.
A covariant class is a generic class (eg. Producer<T>
) for which holds: Producer<A>
is a subtype of Producer<B>
if A
is a subtype of B
, subtyping is preserved. For example, Producer<Cat>
is a subtype of Producer<Animal>
.
interface Source<out T> { // covariance, T can be only used as return type
fun nextT(): T // we can only read, not write
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // This is OK, since T is an out-parameter
// ...
}
A class that is contravariant on the type parameter is a generic class (eg. Consumer<T>
) for which holds: Consumer<A>
is a subtype of Consumer<B>
if B
is a subtype of A
, subtyping is reversed. For example, Consumer<Animal>
is a subtype of Consumer<Cat>
.
interface Comparable<in T> { // contravariance, T can be only used as parameter type
operator fun compareTo(other: T): Int // we can only write, not read
}
fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
// Thus, we can assign x to a variable of type Comparable<Double>
val y: Comparable<Double> = x // OK!
}
Use-site variance declarations in Kotlin correspond directly to Java bounded wildcards. MutableList<out T>
means the same as MutableList<? extends T>
and MutableList<in T>
corresponds to MutableList<? super T>
.
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
When we need to create an object of a slight modification of some class, without explicitly declaring a new subclass for it.
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { /*...*/ }
override fun mouseEntered(e: MouseEvent) { /*...*/ }
})
open class A(x: Int) {
public open val y: Int = x
}
interface B { /*...*/ }
val ab: A = object : A(1), B {
override val y = 15
}
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
object Resource {
val name = "Name"
fun printName() = println("Resource $name")
}
Resource.name
Resource.printName()
interface MyInterface
fun myfun(o: MyInterface) {}
myfun(object: MyInterface {}) // anonymous instance of MyInterface
}
typealias FileTable<K> = MutableMap<K, MutableList<File>>
typealias Predicate<T> = (T) -> Boolean
fun foo(p: Predicate<Int>) = p(42)
fun main() {
val f: (Int) -> Boolean = { it > 0 }
println(foo(f)) // prints "true"
val p: Predicate<Int> = { it > 0 }
println(listOf(1, -2).filter(p)) // prints "[1]"
}
Only unchecked exceptions exist in Kotlin.
fun getNumber(str: String): Int {
return try {
Integer.parseInt(str)
} catch (e: NumberFormatException) {
-1
} finally {
println("We're done.")
}
}
val stream = Files.newInputStream(Paths.get("/some/file.txt"))
stream.buffered().reader().use { reader ->
println(reader.readText())
}
val bob = Person("Bob", 31)
val people = listOf<Person>(Person("Adam", 20), bob, bob)
val people2 = listOf<Person>(Person("Adam", 20), Person("Bob", 31), bob)
println(people == people2)
bob.age = 32
println(people == people2)
val numbers = mutableListOf(1, 2, 3, 4)
numbers.add(5)
numbers.removeAt(1)
numbers[0] = 0
numbers.shuffle()
println(numbers)
val numbers = setOf(1, 2, 3, 4)
println("Number of elements: ${numbers.size}")
if (numbers.contains(1)) println("1 is in the set")
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
println("All keys: ${numbersMap.keys}")
println("All values: ${numbersMap.values}")
if ("key2" in numbersMap) println("Value by key \"key2\": ${numbersMap["key2"]}")
if (1 in numbersMap.values) println("The value 1 is in the map")
if (numbersMap.containsValue(1)) println("The value 1 is in the map") // same as previous
Sequences offer the same functions as Iterable
but implement another approach to multi-step collection processing.
- Multi-step processing of sequences is executed lazily when possible.
- Performs all the processing steps one-by-one for every single element.
- Iterable completes each step for the whole collection and then proceeds to the next step.
- Similar to Stream in Java.
- Itermediate and terminal operations.
val numbersSequence = sequenceOf("four", "three", "two", "one")
val numbers = listOf("one", "two", "three", "four")
val numbersSequence = numbers.asSequence()
val oddNumbers = generateSequence(1) { it + 2 } // `it` is the previous element
println(oddNumbers.take(5).toList()) // [1, 3, 5, 7, 9]
To create a finite sequence, provide a function that returns null after the last element you need:
val oddNumbersLessThan10 = generateSequence(1) { if (it < 10) it + 2 else null }
println(oddNumbersLessThan10.count()) // 6
val oddNumbers = sequence {
yield(1)
yieldAll(listOf(3, 5))
yieldAll(generateSequence(7) { it + 2 })
}
println(oddNumbers.take(5).toList()) // [1, 3, 5, 7, 9]
val empty = emptyList<String>()
val doubled = List(3, { it * 2 })
val linkedList = LinkedList<String>(listOf("one", "two", "three"))
val sourceList = mutableListOf(1, 2, 3)
val copyList = sourceList.toMutableList()
val readOnlyCopyList = sourceList.toList()
sourceList.add(4)
println("Copy size: ${copyList.size}")
//readOnlyCopyList.add(4) // compilation error
println("Read-only copy size: ${readOnlyCopyList.size}")
val numbers = listOf(1, 2, 3)
val plusList = numbers + 4 // [1, 2, 3, 4]
val minusList1 = numbers - listOf(1, 3) // [2]
val minusList2 = numbers - 3 // [1, 2]
val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
numbersMap + Pair("four", 4) // {one=1, two=2, three=3, four=4}
numbersMap - "one" // {two=2, three=3}
numbersMap - listOf("two", "four") // {one=1, three=3}
val numbers = listOf("one", "two", "three")
numbers.elementAt(2) // three
numbers.elementAtOrNull(9) // null
numbers.elementAtOrElse(9) { // value for 9 undefined
index -> "value for $index undefined"}
numbers.first() // one
numbers.last() // three
numbers.first { it.startsWith("t") } // two
numbers.last { it.startsWith("t") } // three
numbers.firstOrNull { it.length > 9 } // null
numbers.find { it.startsWith("t") } // two
numbers.findLast { it.startsWith("t") } // three
numbers.random() // one or two or three
numbers.contains("four") // false
"zero" in numbers // false
numbers.containsAll(listOf("four", "two")) // false
numbers.containsAll(listOf("one", "zero")) // false
numbers.isEmpty() // false
numbers.isNotEmpty() // true
println(numbers.contains("four"))
println("zero" in numbers)
println(numbers.containsAll(listOf("four", "two")))
println(numbers.containsAll(listOf("one", "zero")))
println(numbers.isEmpty())
println(numbers.isNotEmpty())
class Version(val version: Int) : Comparable<Version> {
override fun compareTo(other: Version): Int =
when {
other.version > this.version -> -1
other.version < this.version -> 1
else -> 0
}
}
val v1 = Version(1)
val v2 = Version(2)
println(v1 > v2) // false
println(v1 <= v2) // true
val list1 = listOf(v1, v2)
val list2 = listOf(v2, v1)
println(list1 == list2) // false
println(list1 == list2.sorted()) // true
val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 }) // [3, 6, 9]
println(numbers.mapIndexed { idx, value -> value * idx }) // [0, 2, 6]
Zipping transformation is building pairs from elements with the same positions in both collections.
val colors = listOf("red", "brown", "grey")
val animals = listOf("fox", "bear")
println(colors.zip(animals))
// [(red, fox), (brown, bear)]
Association transformations allow building maps from the collection elements and certain values associated with them.
val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length })
// {one=3, two=3, three=5, four=4}
flatten
:
val numberSets = listOf(setOf(1, 2, 3), setOf(4, 5))
println(numberSets.flatten()) // [1, 2, 3, 4, 5]
flatMap
:
val containers = listOf(
StringContainer(listOf("one", "two", "three")),
StringContainer(listOf("four", "five"))
)
println(containers.flatMap { it.values })
// [one, two, three, four, five]
val numbers = listOf("one", "two", "three", "four")
val longerThan3 = numbers.filter { it.length > 3 }
println(longerThan3) // [three, four]
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key11" to 11)
val filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") }
println(filteredMap) // {key1=1, key11=11}
val numbers = listOf("one", "two", "three", "four")
val filteredIdx = numbers.filterIndexed {
index, s -> index != 0 && s.length < 5 }
// [two, four]
val filteredNot = numbers.filterNot { it.length <= 3 }
// [three, four]
val numbers = listOf(null, "one", "two", null)
numbers.filterNotNull().forEach {
println(it.length) // length is unavailable for nullable Strings
}
val numbers = listOf("one", "two", "three", "four")
val (match, rest) = numbers.partition { it.length > 3 }
println(match) // [three, four]
println(rest) // [one, two]
val numbers = listOf("one", "two", "three", "four")
numbers.any { it.endsWith("e") } // true
numbers.none { it.endsWith("a") } // true
numbers.all { it.endsWith("e") } // false
emptyList<Int>().all { it > 5 } // true, vacuous truth
val numbers = listOf("one", "two", "three", "four", "five")
numbers.groupBy { it.first().toUpperCase() }
// {O=[one], T=[two, three], F=[four, five]}
numbers.groupBy(keySelector = { it.first() },
valueTransform = { it.toUpperCase() })
// {o=[ONE], t=[TWO, THREE], f=[FOUR, FIVE]}
val numbers = listOf(1, 2, 3, 4, 5, 6)
numbers.slice(1..3) // [2, 3, 4]
numbers.slice(0..4 step 2) // [1, 3, 5]
numbers.slice(setOf(3, 5, 0)) // [4, 6, 1]
val numbers = listOf(1, 2, 3, 4, 5, 6)
numbers.take(3) // [1, 2, 3]
numbers.takeLast(3) // [4, 5, 6]
numbers.drop(1) // [2, 3, 4, 5, 6]
numbers.dropLast(5) // [1]
- Chunked - to break a collection onto parts of a given size.
- Windowed - to retrieve all possible ranges of the collection elements of a given size.
val numbers = (1..5).toList()
numbers.chunked(2) // [[1, 2], [3, 4], [5]]
numbers.windowed(2) // [[1, 2], [2, 3], [3, 4], [4, 5]]
-
min
,max
,average
,sum
,count
. -
minBy
,maxBy
take a selector function. -
minWith
,maxWith
take a Comparator. -
sumBy
,sumByDouble
apply functions that return Int / Double.
val numbers = listOf(1, 2, 3)
println(numbers.sumBy { it * 2 }) // 12
println(numbers.sumByDouble { it.toDouble() / 2 }) // 3
-
reduce
,fold
(takes an initial value)
val numbers = listOf(1, 2, 3)
numbers.reduce { sum, element -> sum + element } // 6
numbers.fold(1) { sum, element -> sum + element } // 7
Coroutines are light-weight threads.
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // launch a new coroutine in background and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello,") // main thread continues while coroutine is delayed
Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}
val job = GlobalScope.launch { // launch a new coroutine
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // wait until child coroutine completes
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancel() // cancels the job
job.join() // waits for job's completion
// or job.cancelAndJoin()
println("main: Now I can quit.")
withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
suspend fun doSomethingOne(): Int {
delay(1000L)
return 13
}
suspend fun doSomethingTwo(): Int {
delay(1000L)
return 29
}
val time = measureTimeMillis {
val one = doSomethingOne()
val two = doSomethingTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")
// The answer is 42
// Completed in 2013 ms
val time = measureTimeMillis {
val one = async { doSomethingOne() }
val two = async { doSomethingTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
// The answer is 42
// Completed in 1017 ms
Async functions are not suspending functions.
- They can be used from anywhere.
- Their use always implies asynchronous (here meaning concurrent) execution.
// The result type is Deferred<Int>
fun somethingOneAsync() = GlobalScope.async {
doSomethingOne()
}
// The result type is Deferred<Int>
fun somethingTwoAsync() = GlobalScope.async {
doSomethingTwo()
}
// note that we don't have `runBlocking` to the right of `main` in this example
fun main() {
val time = measureTimeMillis {
// we can initiate async actions outside of a coroutine
val one = somethingOneAsync()
val two = somethingTwoAsync()
// but waiting for a result must involve either suspending or blocking.
// here we use `runBlocking { ... }` to block the main thread while waiting for the result
runBlocking {
println("The answer is ${one.await() + two.await()}")
}
}
println("Completed in $time ms")
}
// The answer is 42
// Completed in 1120 ms
Dispatcher determines what thread or threads the corresponding coroutine uses for its execution.
launch { // context of the parent, main runBlocking coroutine
println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher
println("Default : I'm working in thread ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}
// Unconfined : I'm working in thread main @coroutine#3
// Default : I'm working in thread DefaultDispatcher-worker-1 @coroutine#4
// newSingleThreadContext: I'm working in thread MyOwnThread @coroutine#5
// main runBlocking : I'm working in thread main @coroutine#2
When launch { ... }
is used without parameters, it inherits the context (and thus dispatcher) from the CoroutineScope it is being launched from.
To represent the stream of values that are being asynchronously computed.
fun foo(): Flow<Int> = flow { // flow builder
for (i in 1..3) {
delay(100) // pretend we are doing something useful here
emit(i) // emit next value
}
}
fun main() = runBlocking<Unit> {
// Collect the flow
foo().collect { value -> println(value) }
}
Flows are cold streams similar to sequences — the code inside a flow builder does not run until the flow is collected.
Channels provide a way to transfer a stream of values.
val channel = Channel<Int>()
launch {
// this might be heavy CPU-consuming computation or async logic, we'll just send five squares
for (x in 1..3) channel.send(x * x)
}
// here we print five received integers:
repeat(3) { println(channel.receive()) }
println("Done!")
// 1
// 4
// 9
// Done!
val channel = Channel<Int>()
launch {
for (x in 1..3) channel.send(x * x)
channel.close() // we're done sending
}
// here we print received values using `for` loop (until the channel is closed)
for (y in channel) println(y)
println("Done!")
// 1
// 4
// 9
// Done!
A pipeline is a pattern where one coroutine is producing, possibly infinite, stream of values, and another coroutine or coroutines are consuming that stream, doing some processing, and producing some other results.
fun CoroutineScope.produceNumbers() = produce<Int> {
var x = 1
while (true) send(x++) // infinite stream of integers starting from 1
}
fun CoroutineScope.square(numbers: ReceiveChannel<Int>): ReceiveChannel<Int> = produce {
for (x in numbers) send(x * x)
}
val numbers = produceNumbers() // produces integers from 1 and on
val squares = square(numbers) // squares integers
repeat(3) {
println(squares.receive()) // print first five
}
println("Done!") // we are done
coroutineContext.cancelChildren() // cancel children coroutines
// 1
// 4
// 9
// Done!
An actor is an entity made up of a combination of a coroutine, the state that is confined and encapsulated into this coroutine, and a channel to communicate with other coroutines.
// Message types for counterActor
sealed class CounterMsg
object IncCounter : CounterMsg() // one-way message to increment counter
class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply
// This function launches a new counter actor
fun CoroutineScope.counterActor() = actor<CounterMsg> {
var counter = 0 // actor state
for (msg in channel) { // iterate over incoming messages
when (msg) {
is IncCounter -> counter++
is GetCounter -> msg.response.complete(counter)
}
}
}
fun main() = runBlocking<Unit> {
val counter = counterActor() // create the actor
withContext(Dispatchers.Default) {
massiveRun {
counter.send(IncCounter)
}
}
// send a message to get a counter value from an actor
val response = CompletableDeferred<Int>()
counter.send(GetCounter(response))
println("Counter = ${response.await()}")
counter.close() // shutdown the actor
}
// Completed 100000 actions in 973 ms
// Counter = 100000
Select expression makes it possible to await multiple suspending functions simultaneously and select the first one that becomes available.
suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) {
select<Unit> { // <Unit> means that this select expression does not produce any result
fizz.onReceive { value -> // this is the first select clause
println("fizz -> '$value'")
}
buzz.onReceive { value -> // this is the second select clause
println("buzz -> '$value'")
}
}
}
val fizz = fizz()
val buzz = buzz()
repeat(7) {
selectFizzBuzz(fizz, buzz)
}
coroutineContext.cancelChildren() // cancel fizz & buzz coroutines
use
close the closable automatically:
Files.newBufferedReader(path).use {
it.read()
}
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar
# library (no Kotlin runtime included)
kotlinc hello.kt -d hello.jar
kotlin -classpath hello.jar HelloKt
<properties>
<kotlin.version>1.3.72</kotlin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals><goal>compile</goal></goals>
</execution>
<execution>
<id>test-compile</id>
<goals><goal>test-compile</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>