My study note of Kotlin. From basic to data structure and algorithm, might cover some usage in Android.
Kotlin koans
fun task2(collection: Collection<Int>): String {
return collection.joinToString(prefix = "{", postfix = "}")
}
assertEquals("{1, 2, 3, 42, 555}", task2(listOf(1, 2, 3, 42, 555)))
field
refers to the property itself. And in setter funtion, we can manipulate other properties.
class PropertyExample() {
var counter = 0
var propertyWithCounter: Int? = null
set(value) {
field = value
counter++
}
}
And also can go with just statement.
class PropertyExample() {
var counter = 0
var propertyWithCounter: Int? = null
get() = 42
}
Without delegation
:
class LazyProperty(val initializer: () -> Int) {
var _lazy: Int? = null
val lazy: Int
get() {
if (_lazy == null) {_lazy = initializer()}
return _lazy!!
}
}
With delegation
:
class LazyProperty(val initializer: () -> Int) {
val lazy: Int by lazy {
initializer()
}
}
By default, the evaluation of lazy properties is synchronized: the value is computed only in one thread, and all threads will see the same value.
Returns true if at least one element matches the given predicate.
fun containsEven(collection: Collection<Int>): Boolean = collection.any { it % 2 == 0}
Example:
val isEven: (Int) -> Boolean = { it % 2 == 0 }
val zeroToTen = 0..10
println("zeroToTen.any { isEven(it) } is ${zeroToTen.any { isEven(it) }}") // true
println("zeroToTen.any(isEven) is ${zeroToTen.any(isEven)}") // true
val odds = zeroToTen.map { it * 2 + 1 }
println("odds.any { isEven(it) } is ${odds.any { isEven(it) }}") // false
val emptyList = emptyList<Int>()
println("emptyList.any { true } is ${emptyList.any { true }}") // false
Kotlin has two types of String
-
one is escaped strings, which is similar to Java String, which have escaped characters
val s = "Hello, world!\n"
; -
another one is raw string with
"""
, e.g.val text = """ for (c in "foo") print(c) """
which has newlines or other characters in.
And here is another good example to make pattern:
val month = "(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)"
fun getPattern(): String = """\d{2} ${month} \d{4}"""
I know data class for a while but I dont know this untill today: the compiler automatically does for data class User(val name: String, val age: Int)
- generate
equals()
andhashCode()
toString()
of the form"User(name=John, age=42)"
;
[2019-2-5 update] Compiler generates equals() and hashCode() for data class, but if we use array in it, we need to override it, since:
In Kotlin there are two types of equality:
- Structural equality == (a check for equals(), only check content)
- Referential equality === (two references point to the same object);
Array equals is not structually equals by default. Why?! JVM!
Root Cause
It’s a long-standing well-known issue on the JVM: equals() works differently for arrays and collections. Collections are compared structurally, while arrays are not
, equals() for them simply resorts to referential equality: this === other.
Currently, Kotlin data classes are ill-behaved with respect to this issue:
- if you declare a component to be an array, it will be compared structurally,
- but if it is a multidimensional array (array of arrays), the subarrays will be compared referentially (through equals() on arrays), and if the declared type of a component is Any or T, but at runtime it happens to be an array, equals() will be called too.
This behavior is inconsistent. Read and equality
Example
DataEquals print out false when the data class includes a array in it.
Solution
Make it a List other than Array.
Everything in Kotlin is an object. To user, all the numbers, strings, booleans are classes. But, there are some type have internal representation, e.g. numbers, characters, booleans can be represented as primitive type at runtime.
list
in kotlin is immutable, if we want to update it, should use mutableList.
array
in kotlin has .plus operation, it is easy to copy or combine two array.
Returns a list of values built from the elements of this
collection and the [other] collection with the same index
- using the provided [transform] function applied to each pair of elements.
- The returned list has length of the shortest collection.
Let have an example
:
Kotlin Source code
public inline fun <T, R, V> Iterable<T>.zip(other: Iterable<R>, transform: (a: T, b: R) -> V): List<V> {
val first = iterator()
val second = other.iterator()
val list = ArrayList<V>(minOf(collectionSizeOrDefault(10), other.collectionSizeOrDefault(10)))
while (first.hasNext() && second.hasNext()) {
list.add(transform(first.next(), second.next()))
}
return list
}
Time Complexity
If list one is size N (short one), list two is size M (large one), big-O is
- zip
- while is
O(N)
- list.add is O(1) if add at the end and list not resize, but it grows up if resize the list
- while is
- foreach print O(N)
O(N) + O(N) is still O(N)
Space Complexity
Since it creates another list in zip function, and starts with the list size 10, we could expect more space cost
So we can say zip function is not that good in terms of time and space efficiency. But it makes good looking code.
Open your Kotlin file (I was at Java file, nothing happen...)
Step 1: Tools -> Kotlin -> Show Kotlin Bytecode
Step 2: Decompile
to Java
learnt from here
have a look at this one https://proandroiddev.com/kotlin-android-extensions-using-view-binding-the-right-way-707cd0c9e648
Flat a map is easy to understand, but one day I have a linked hash map: LinkedHashMap<Int, Any>, value can be an object or a list of objects. I wanted flat function can flat all the objects into one level list no matter what.
val map = LinkedHashMap<String, Any>()
map["1"] = listOf("one", "two", "three")
val result = map.values.flatMap{listOf(it)}
The result size is one, which means it doesn't flat the map as I wish.
While it simply works if I do val map = LinkedHashMap<String, List<Any>>()
High-order function
is beautiful and elegant in code.
my high-order funtion code example
But it has drawbacks, since each function is an object, memory allocations (both for function objects and classes) and virtual calls introduce runtime overhead.
But we could use inline function to avoid that runtime overhead. The inline
modifier affects both the function itself and the lambdas passed to it: all of those will be inlined into the call site.
Note that some inline functions may call the lambdas passed to them as parameters, in such cases, non-local control flow is also not allowed
in the inline lambdas. To indicate that, the lambda parameter needs to be marked with the crossinline
modifier.
Break
and continue are not yet available in inlined lambdas
, but we are planning to support them too.
until
keyword is nothing but ..n-1, let's have a look at the source code:
public infix fun Int.until(to: Int): IntRange {
if (to <= Int.MIN_VALUE) return IntRange.EMPTY
return this .. (to - 1).toInt()
}
There was a performance issue before Kotlin 1.1.4, but fixed already.
And we can define our own infix function as well:
infix fun Int.add(some:Int): Int {
return this + some
}
fun main(args: Array<String>) {
println(3 add 5)
}
Understand plus
and add
.
Check example SetThings.kt
.
We use Kotlin extensions a lot, which is one of the most useful functions in Kotlin. But in my working project, sometime we still have to maintain Java code.
Java calls to Kotlin Extension
ExtensionExampleKt.printMeOutWithTail("Hello", "world!");
Kotlin Extension
fun String.printMeOutWithTail(withTail: String) {
println("$this $withTail")
}
BTW, When I was writing the example, named the package as "kotlin", then I got Error:(1, 1) Kotlin: Only the Kotlin standard library is allowed to use the 'kotlin' package
. The error is from kotlinc-jvm, which means compiler does want you to use kotlin.*
as package name.
Interesting, a function inside of another function:
fun sayHi() {
fun say() {
System.out.println("hi")
}
say()
}
val mySet = mutableSetOf(1, 2, 3, 4)
mySet.add(5)
println(mySet.asSequence().take(3).toList())
println(mySet.toList().takeLast(3))
take(3) means take the first 3 items, takeLast(3) means take the last 3 items. Easy enough to understand. BTW, mutableSetOf()
is LinkedHashSet
type.
In Java, IDE compiles when class property order
is messed up. But in Kotlin, it doesn't complain, but throw error in runtime. Here is an example:
constructor is always called after
properties are loaded in class.
::
is used as function operator
fun isOdd(x: Int) = x % 2 != 0
fun main(args: Array<String>) {
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // this line
println(numbers.filter { isOdd(it) }) // and this line do the same thing
}
Kotlin 1.1 introduced coroutines, a new way of writing asynchronous, non-blocking code.
The two main purposes I started using RxJava are:
- We can do streaming programming by using RxJava, thinking data as streams, and everything can be an Observable, and we subscribe on it and wait for it on main thread, and then do the rest of work
- We can manage threadings easier, compare to using Async... and other threading API in Android
Then why do I want to try Coroutines?
- It is Kotlin own feature, if I can replace Rx with it, less dependencies to my project
- Simpler code, easy to learn
- Less threads costing (thread is resource)
- Kotlin coroutines can run in one thread or different threads, or in a shared thread pool
- Coroutines runs cooperatively multitasking; Thread runs preemptively multitasking
Have some readings:
A method which need to run at coroutines.
launch {
...
}
Just by doing this, we started a new coroutines. By default, it runs in a shared threads pool. One thread can run many coroutines.
val deferred = (1..1_000_000).map { n ->
GlobalScope.async {
n
}
}
Async returns a value from coroutines.
delay()
only suspend itself - the current coroutines, does not block any thread. delay()
cannot be called from main thread, since it can only be called from coroutines or other suspend function.
withContext(IO) {
...
}
withContext(Default) {
...
}
IO
: that is designed for offloading blocking IO tasks to a shared pool of threads. This dispatchershares threads with a Dispatchers.Default
dispatcher.withContext(Dispatchers.IO) { ... }
does not lead to an actual switching to another threadDefault
: The default dispatcher, that is used when coroutines are launched inGlobalScope
, is represented byDispatchers.Default
and uses shared background pool of threads, solaunch(Dispatchers.Default) { ... }
uses the same dispatcher asGlobalScope.launch { ... }
.Global scope
is used to launch top-level coroutines which are operating on thewhole application lifetime
and are not cancelled prematurely.
Deferred
values provide a convenient way to transfer a single value between coroutines. Channels
provide a way to transfer a stream of values.
val channel = Channel<Int>()
launch {
for (x in 1..5) 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!")
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1
requires Kotlin version 1.3 and above.
Sealed class is abstract class can't be initialied directly.
The benefit of sealed class:
- when a value can have one of the types from a limited set, make value more restriced.
- less code compare to use normal class
- easily use with
when()
,else
is redundant, since sealed class is exhaustive.
The inconvenient is:
- sealed class and its children need to be in the same file or nested, since its constructors is private
Example:
/* my example */
sealed class Response<MODEL>
class ResponseSuccess<MODEL>(val data: MODEL) : Response<MODEL>()
class ResponseLoading<MODEL> : Response<MODEL>()
class ResponseError<MODEL>(val error: Throwable?) : Response<MODEL>()
Use with when()
when (response) {
is ResponseSuccess -> {
...
}
is ResponseLoading -> {
...
}
is ResponseError -> {
...
}
}