Week5
Week5
一. Inline functions
1. Libraray functions looking like build-in constructs
1) Useful library functions
run
return the block of code (lambda) and returns the last expression as the resultlet
allows to check the argument for being non-null, not only the receiver1
2
3
4
5
6
7
8fun main() {
val email: Email? = getEmail()
if (email != null) sendEmail(email)
email?.let { sendEmail(it) }
getEmail()?.let { sendEmail(it) }
}Kotlin is not a pure functional language
1
2
3
4
5
6
7
8
9fun analyzeUserSession(session: Session){
val user = session.user
if (user is FaceBookUser){
println(user.accountId)
}
(session.user as? FaceBookUser)?.let {
println(it.accountId)
}
}takeIf
returns the receiver object if it satisfies the given predicate, otherwise returns null1
2
3issue.takeIf { it.status == FIXED}
person.patronymicName.takeIf(String::isNotEmpty)1
2
3
4val number = 42
println(number.takeIf { it > 10 }) // 42
val other = 2
println(other.takeIf { it > 10 }) // nullUsing
takeIf
in chained calls1
2
3issues.filter { it.status == OPEN }
.takeIf(List<Issue>::isNotEmpty)
?.let { println("There're some open issues") }takeUnless
returns the receiver object if it does not satisfy the given predicate, otherwise returns nullrepeat
1
2
3
4repeat(10)
{
println("Welcome!")
}
All these functions are declared as inline functions (There is no performance overhead)
2. The power of inline
1) inline function
compiler substitutes a body of the function instead of calling it
1 | fun myRun(f: () -> Unit) = f() |
inline constraints : You can’t postpone the call.
2) withLock
function
3) Resource management: use
function
No performance overhead when you use
run
,let
,takeIf
,takeUnless
,repeat
,withLock
,use
No anonymous class or extra objects are created for lambda under the hood
Note: If you call a inline function in Java, you won’t get it inlined.
Because it is credit to the Kotlin compile
4) @InlineOnly
Specifies that this function should not be called directly without inling
- Not in your
jar
- Can’t be called in Java
Fin.
However, use inline
in your code with care, because actually hotspot can do this job for you.
二. Sequences
1. Collections vs Sequences
1) Collections
example
1
2
3
4
5
6val list = listOf(1, 2, 3)
val maxOddSquare = list
.map { it * it }
.filter { it % 2 == 1 }
.max()
// create 3 collections in totalOperations on collections
- lambdas are inlined (no performance overhead)
- But : intermediate collections are created for chained calls
Collections vs Sequences
is likeEager vs lazy evaluation
2) Sequences
example
1
2
3
4
5
6
7val list = listOf(1, 2, 3)
val maxOddSquare = list
.asSequence()
.map { it * it }
.filter { it % 2 == 1 }
.max()
// create 1 collections in total
2. More about Sequences
1) Test 1
fun test1() { listOf(1, 2, 3, 4) .map { it * it } .find { it > 3 } // 4 // we don't do anything unless it is need listOf(1, 2, 3, 4) .asSequence() .map { it * it } .find { it > 3 } // 4 } <!--code9-->   - intermediate operations return you another sequence - terminal operations return you everything else
3) Test 3 Order of operations is important
fun test3() { fun m(i: Int): Int { print("m$i ") return i } fun f(i: Int): Boolean { print("f$i ") return i % 2 == 0 } val list = listOf(1, 2, 3, 4) list.asSequence().map(::m).filter(::f).toList() // m1 f1 m2 f2 m3 f3 m4 f4 list.asSequence().filter(::f).map(::m).toList() // f1 f2 m2 f3 f4 m4 } <!--code10-->

2) Generating a sequence
generateSequence{xx} : until xx return null
1
2
3
4
5
6fun test1() {
val seq = generateSequence {
Random.nextInt(5).takeIf { it > 0 }
}
println(seq.toList())
}
3) Generating an infinite sequence
1 | fun test2() { |
To prevent integer overflow
1 | fun test3() { |
4) Yield (lazy)
fun test5(){ val numbers = sequence { var x =0 while (true){ yield(x++) } } numbers.take(5).toList() // [0,1,2,3,4] } <!--code14-->
4. Library Functions
1 | class Person(val age: Int, val name: String) { |
三. Lambda with Receiver
1. Lambda with Receiver
1) with
function
1 | val sb = StringBuilder() |
1 | val sb = StringBuilder() |
with(receiver: T, block: T.() -> R): R
is a function
2) Lambda vs lambda with receiver
regular funtion | regular lambda |
---|---|
Extension function | lambda with receiver |
1 | fun test2() { |
improvment:
1
2
3
4
5
6
7
8fun test3(){
val s = buildString {
appendln("Alphabet: ")
for (c in 'a'..'z') {
append(c)
}
}
}Html example
2. More useful library functions
with(receiver: T, block: T.() -> R): R
1
2
3
4
5
6
7
8fun withTest(){
var window = Window()
with(window){
width = 300
height = 200
isVisible = true
}
}T.run(block: T.() -> R): R
{ this.block(); return this }
1
2
3
4
5
6
7
8fun runTest(){
val windowOrNull = windowById["main"]
windowOrNull?.run {
width = 300
height = 200
isVisible = true
}
}T.let(block: (T) -> R): R
{ block(this); return this }
T.apply(block: T.() -> Unit): T
{ return this.block() }
1
2
3
4
5
6
7
8
9
fun applyTest() {
val mainWindow =
windowById["main"]?.apply {
width = 300
height = 200
isVisible = true
} ?: return
}T.also(block: (T) -> Unit): T
{ return block(this) }
1
2
3
4
5
6
7
8
9
10fun alsoTest() {
val mainWindow =
windowById["main"]?.apply {
width = 300
height = 200
isVisible = true
}?.also {
showWindow(it)
}
}
{ .. this .. } | { .. it .. } | |
---|---|---|
return result of lambda | with / run | let |
return receiver | apply | also |
四. Types
1. Basic types
1) Bytecode of Kotlin
Kotlin code (types.kt)
1
2fun foo(): Int = 1
fun bar(): Int? = 1Decompiled Java code (TypesKt.java)
1
2
3
4
5
6
7
8
9
10public final class TypesKt {
public static final int foo() {
return 1;
}
public static final Integer bar() {
return 1;
}
}
2) Kotlin Bytecode
Primitive & wrapper types
Kotlin Java Int int Int? Java.lang.Integer
The same for double
and boolean
Generic arguments
Kotlin Java List<Int> List<Integer> Arrays of primitive types
Kotlin Java Array<Int> Integer[] IntArray int[] String
Kotlin java kotlin.String Java.lang.String Kotlin.String modifies some of Java String’s API
such as String.replaceAll
1
2
3// Java
"one.two.".replace(".", "*"); // one*two*
"one.two.".replaceAll(".", "*"); // ********1
2
3// Kotlin
"one.two.".replace(".", "*") // one*two*
"one.two.".replace(".".toRegex(), "*") // ********
Any
Kotlin Java Any Java.lang.Object Any in Kotlin is a super class type for all non-nullable types (Both for reference type and for primitive type)
Boxing under the hood
1
2
3
4
5
6
7
8
9
10
11log(2017)
// 2017 will be boxing into Integer
fun log(any: Any) {
println("Value: $any")
}
// 2017 will not be boxing
fun log(i: Int) {
println("Value: $i")
}
Function types
Kotlin Java () -> Boolean Function0<Boolean> (Order) -> Int Function1<Order, Int> (Int, Int) -> Int Function2<Int, Int, Int>
3) Prefer Lists to Arrays
- If you write pure Kotlin, use list
- If you write Kotlin with Java, use array
2. Kotlin type hierarchy

1) Unit
vs Nothing
vs void
Unit
instead ofvoid
Kotlin Java Unit void Nothing
is defferent toUnit/void
it means “this function never returns”
1
2
3fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}
Expressions that have
Nothing
type :throw IllegalArgumentException
return
TODO("Needs to be done")
2) Nothing


Kotlin | Java |
---|---|
Nothing | void |
.png)
- the simplest expression of Nothing? type:
null
3) Type of null
1 | var user = null // user is inferred as Nothing? |
3. Nullable types
Java | Kotlin |
---|---|
@Nullable Type | Type? |
@NotNull Type | Type |
Type | Type! (Notation, not syntax) |
Type that came from Jave -> type of “unknown” nullability in Kotlin
1) Type! in Kotlin
1
2
3
4
5
6// Java
public class Session {
public String getDescription() {
return null;
}
}IllegalStateException
1
2
3
4
5fun test1(){
val session = Session()
val description:String = session.description // exception
println(description.length)
}NullPointerException
1
2
3
4
5fun test2(){
val session = Session()
val description = session.description // description is inferred as `String!`
println(description.length) // exception
}(Correct) use ?. to safe access
1
2
3
4
5fun test3() {
val session = Session()
val description = session.description // description is inferred as `String!`
println(description?.length)
}(Correct) to prevent test2, if you use
.length
, you’ll get compile error1
2
3
4
5fun test4() {
val session = Session()
val description: String? = session.description // description is inferred as `String!`
println(description?.length)
}
2) How to still prevent NPEs?
Annotate your Java types
Different annotations are supported
@Nullable @NotNull JetBrains @Nullable @NotNull Android @Nullable @CheckForNull JSR-305 @Nullable @CheckForNull FindBugs @NonNull Lombok … … … 1
2
3
4
5
6
7// Java
public class Session {
public String getDescription() {
return null;
}
}1
2
3val session = Session()
val description = session.description // description is inferred as `String?`
println(description?.length)Tip: make one annotation as default, only specify the other as needed
Specify types explicitly
1
2
3
4
5
6// Java
public class Session {
public String getDescription() {
return null;
}
}1
2
3val session = Session()
val description: String? = session.description // description is inferred as `String!`
println(description?.length)1
2
3val session = Session()
val description:String = session.description // get IllegalStateException exception immediately, better than NPE
println(description.length)
4. Collection types
1) Standard collections
1 | val set = hashSetOf(1,7,53) |
2) (Read-only)List & MutableList
2 interfaces declared in kotlin.collections package
MutableList extends List
Note: Read-only $\neq$ immutable
Read-only interface just lacks mutaing methods
The actual list can ve changed by another reference
See example below
1
2
3
4
5
6fun readOnlyList(){
val mutableList = mutableListOf(1, 2, 3)
val list: List<Int> = mutableList
mutableList.add(4)
println(list) // [1, 2, 3, 4]
}Under the hood, both
List
andMutableList
is the same java.util.List in bytecode

3) Read-only interfaces improve API
1 | class Customer |
If you want, you can forcely turn List
into MutableList
, but it avoids you from accidently misusing.
Summary: Good compromise between safety and convenience