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 result

  • let allows to check the argument for being non-null, not only the receiver

    1
    2
    3
    4
    5
    6
    7
    8
    fun 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
    9
    fun 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 null

    1
    2
    3
    issue.takeIf { it.status == FIXED}

    person.patronymicName.takeIf(String::isNotEmpty)
    1
    2
    3
    4
    val number = 42
    println(number.takeIf { it > 10 }) // 42
    val other = 2
    println(other.takeIf { it > 10 }) // null

    Using takeIf in chained calls

    1
    2
    3
    issues.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 null

  • repeat

    1
    2
    3
    4
    repeat(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
2
3
4
5
6
7
8
9
10
11
fun myRun(f: () -> Unit) = f()
inline fun run(f: () -> Unit) = f()
fun main() {
val name = "Kotlin"
// brings performance overhead (class InlineKt$main$1 is created)
myRun { println("Hi, $name") }
// No performance overhead
run { println("Hi, $name") }
// in comparison to
println("Hi, $name")
}

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
    6
    val list = listOf(1, 2, 3)
    val maxOddSquare = list
    .map { it * it }
    .filter { it % 2 == 1 }
    .max()
    // create 3 collections in total
  • Operations on collections

    • lambdas are inlined (no performance overhead)
    • But : intermediate collections are created for chained calls

Collections vs Sequences is like Eager vs lazy evaluation

2) Sequences

  • example

    1
    2
    3
    4
    5
    6
    7
    val 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-->
    
    ![截屏2020-01-2810.04.37](/images/Kotlin for Java Developers/Week5/2.png)
    
    ![截屏2020-01-2810.14.40](/images/Kotlin for Java Developers/Week5/3.png)
    
    -   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-->
    

![5](/images/Kotlin for Java Developers/Week5/5.png)

2) Generating a sequence

  • generateSequence{xx} : until xx return null

    1
    2
    3
    4
    5
    6
    fun test1() {
    val seq = generateSequence {
    Random.nextInt(5).takeIf { it > 0 }
    }
    println(seq.toList())
    }

3) Generating an infinite sequence

1
2
3
4
fun test2() {
val numbers = generateSequence(0) { it + 1 }
numbers.take(5).toList() // [0,1,2,3,4]
}

To prevent integer overflow

1
2
3
4
fun test3() {
val numbers = generateSequence(BigInteger.ZERO) { it + BigInteger.ONE }
numbers.take(5).toList() // [0,1,2,3,4]
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Person(val age: Int, val name: String) {
val isPublicProfile: Boolean = false
}

fun main() {
val people = listOf(Person(1, "Kate"))

people.filter { it.age < 21 }.size
people.count { it.age < 21 }

people.sortedBy { it.age }.reversed()
people.sortedByDescending { it.age }

people
.map { person ->
person.takeIf { it.isPublicProfile }?.name
}
.filterNotNull()
people.mapNotNull { person ->
person.takeIf { it.isPublicProfile }?.name
}

people.filterNotNull().map { it.name }
people.mapNotNull { it?.name }

val map = mutableMapOf<Int, MutableList<Person>>()
for (person in people) {
if (person.age !in map) {
map[person.age] = mutableListOf()
}
val group = map.getValue(person.age)
group += person
}
for (person in people) {
val group = map.getOrPut(person.age) { mutableListOf() }
group += person
}
val mapOneWay = people.groupBy(Person::age)

people
.asSequence()
.groupBy { it.age } // not lazy
.mapValues { (_, group) -> group.size }
people
.asSequence()
.groupingBy { it.age }
.eachCount()
}

三. Lambda with Receiver

1. Lambda with Receiver

1) with function

1
2
3
4
5
6
val sb = StringBuilder()
sb.appendln("Alphabet: ")
for (c in 'a'..'z'){
sb.append(c)
}
sb.toString()
1
2
3
4
5
6
7
8
val sb = StringBuilder()
with(sb){
appendln("Alphabet: ")
for (c in 'a'..'z'){
append(c)
}
toString()
}
  • 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
2
3
4
5
6
7
8
9
fun test2() {
// lambda
val isEven: (Int) -> Boolean = { it % 2 == 0 }
isEven(0)

// lambda with receiver
val isOdd: Int.() -> Boolean = { this % 2 == 1 }
1.isOdd()
}
  • improvment:

    1
    2
    3
    4
    5
    6
    7
    8
    fun 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
    8
    fun 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
    8
    fun 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
    10
    fun 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
    2
    fun foo(): Int = 1
    fun bar(): Int? = 1
  • Decompiled Java code (TypesKt.java)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public final class TypesKt {
    public static final int foo() {
    return 1;
    }

    @Nullable
    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
      11
      log(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

![6](/images/Kotlin for Java Developers/Week5/6.png)

1) Unit vs Nothing vs void

  • Unit instead of void

    Kotlin Java
    Unit void
  • Nothing is defferent to Unit/void

    it means “this function never returns”

    1
    2
    3
    fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
    }

    ![截屏2020-01-2815.23.18](/images/Kotlin for Java Developers/Week5/7.png)

  • Expressions that have Nothing type :

    throw IllegalArgumentException return TODO("Needs to be done")

2) Nothing

![截屏2020-01-2815.34.32](/images/Kotlin for Java Developers/Week5/nothing-1.png)

![截屏2020-01-2815.34.32](/images/Kotlin for Java Developers/Week5/nothing-2.png)

Kotlin Java
Nothing void

![截屏2020-01-2815.38.46 (/Volumes/MacWorkplace/Workplace/Kotlin/Kotlin for Java Developers/docs/Week5/nothing-3.png)](/Users/kyle/Desktop/截屏2020-01-2815.38.46 (2).png)

  • the simplest expression of Nothing? type: null

3) Type of null

1
2
var user = null // user is inferred as Nothing?
val users = mutableListOf(null) // users is inferred as MutableList<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
    5
    fun test1(){
    val session = Session()
    val description:String = session.description // exception
    println(description.length)
    }
  • NullPointerException

    1
    2
    3
    4
    5
    fun 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
    5
    fun 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 error

    1
    2
    3
    4
    5
    fun 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 {
    @Nullable
    public String getDescription() {
    return null;
    }
    }
    1
    2
    3
    val 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
    3
    val session = Session()
    val description: String? = session.description // description is inferred as `String!`
    println(description?.length)
    1
    2
    3
    val session = Session()
    val description:String = session.description // get IllegalStateException exception immediately, better than NPE
    println(description.length)

4. Collection types

1) Standard collections

1
2
3
4
5
6
val set = hashSetOf(1,7,53)
val list = arrayListOf(1,7,53)
val map = hashMapOf(1 to "one")
println(set.javaClass) //class java.util.HashSet
println(list.javaClass) // class java.util.ArrayList
println(map.javaClass) // class java.util.HashMap

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
    6
    fun 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 and MutableList is the same java.util.List in bytecode

![截屏2020-01-2817.26.21](/images/Kotlin for Java Developers/Week5/list.png)

3) Read-only interfaces improve API

1
2
3
4
5
6
7
8
9
10
11
class Customer

object Shop{
private val customers = mutableListOf<Customer>()
fun getCustomers():List<Customer> = customers
}

fun main(){
val customers = Shop.getCustomers()
// customers.add() // compile error
}

If you want, you can forcely turn List into MutableList, but it avoids you from accidently misusing.

Summary: Good compromise between safety and convenience

作者

Kyle-Ye

发布于

2020-02-27

更新于

2020-02-27

许可协议