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

Readme

介绍

该系列为Coursera网课 Kotlin for Java Developers笔记

Week2

Week 2

From Java to Kotlin

Basics

1
2
3
const val a = 3
val b = 4
var c = 5
1
2
3
private static final int a = 3;
private final int b =4;
private int c =5

Control Structures

Extensions

1. Extension Functions

Accessing private members

  • In Java you can’t call a private member from a static function of another class
  • Kotlin extension functions are regular static functions defined in a separate auxiliary class.
  • You can’t call private members from extensions

2. Examples from the Standard Library

Kotlin standard library = Java standard library + extensions

infix form

1
2
3
4
5
6
7
8
9
10
public infix fun Int.until(to: Int): IntRange {
if (to <= Int.MIN_VALUE) return IntRange.EMPTY
return this .. (to - 1).toInt()
}
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

1.until(9)
1 until(9)
// infix form
1 until 9

3. Call Extensions

No override in extension

1
2
3
4
5
6
7
8
9
10
open class Parent
class Child:Parent()

fun Parent.foo() = "parent"
fun Child.foo()="child"

fun main(){
val parent: Parent = Child()
println(parent.foo())
}

因为extensive function在底层是static实现的,所以不存在重载

member vs extension

member > extension

member always wins!!

1
2
3
4
5
6
7
8
9
class A {
fun foo()=1
}
fun A.foo() =2
fun main(args: Array<String>) {
val a = A()
println(a.foo())
// "1" will be printed
}

4. Importance of extensions

在类的定义中只保留核心本质,将扩展API放入extension中

eg. String is a CharSequence with length n. Other APIs like replece, split are all its extensions.

Week4

Week4

一. Properties

1. Properties

1) Read-only & mutable properties

Kotlin Java
property Field + accessor(s)
val Filed + getter
var Filed + getter+setter

Sample :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Java
public final class Person {
@NotNull
private final String name;
private int age;

public Person(@NotNull String name, int age) {
this.name = name;
this.age = age;
}

@NotNull
public String getName() {
return name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}
1
2
// Kotlin
class Person(val name: String, var age: Int)

2) Backing field might be absent

Kotlin Java
property accessor(s)
val getter
var getter+setter

Sample :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Java
public final class Rectangle {
private int height;
private int width;

public int getHeight() {
return height;
}

public int getWidth() {
return width;
}

public boolean isSquare() {
return height == width;
}
}
1
2
3
4
5
6
7
8
9
10
// Kotlin
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() = height == width
}

fun main() {
val rectangle = Rectangle(2, 3)
println(rectangle.isSquare) // false
}

3) Stored value or not?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// the value is stored
val foo1 = run {
println("1. Calculating the answer...")
42
}

// the value is calculated on each process
val foo2: Int
get() {
println("2. Calculating the answer...")
return 42
}

fun main(){
println("foo1:")
println("$foo1 $foo1")
println("foo2:")
println("$foo2 $foo2")
}

4) Field

  • You can access field only inside accessors

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class StateLogger {
    var state = false
    set(value) {
    println(
    "state has changed: " +
    "$field -> $value"
    )
    field = value
    }
    }
  • No backing field will be generated if you define accessors (custom getter and setter) and don’t use a field keyword

    1
    2
    3
    4
    5
    6
    7
    8
    9
    enum class State { ON, OFF }
    class StateLogger {
    private var boolState = false
    var state: State
    get() = if (boolState) ON else OFF
    set(value: State) {
    boolState = value == ON
    }
    }

5) Default accessors

1
2
3
4
5
6
7
class A {
var trivialProperty: String = "abc"
// get() = field
// set(value) {
// field = value
// }
}

You can always use property instead of getter or setter (Under the hood, getter and setter will be called)

And inside the class, optimization performed by the compiler are possible (using the filed directly, but only with the trial/default case)

6) Change visibility of a setter

  • You want a var mutable property to be accessible only as a read-only property outside of the class

    1
    2
    3
    4
    5
    6
    7
    8
    class LengthCounter {
    var counter: Int = 0
    private set

    fun addWord(word: String) {
    counter += word.length
    }
    }

2. More about properties

1) override property

2) Smart cast

3. Lazy and late initialization

1) by lazy

1
2
3
4
5
6
7
8
9
10
11
12
13
val value:String = run {
println("1. computed")
"1. Hello"
}

val lazyValue:String by lazy {
println("2. computed")
"2. Hello"
}

fun main(){
// 1. computed
}

2) lateinit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// lateinit is still unsafe and a runtime exception may be thrown, but with a detailed message
lateinit var myData: String
// lateinit's constraints
// 1. it can't be val
// 2. it can't have a primitive type

// check whether `lateinit var` was initialized
class MyClass{
lateinit var lateinitVar:String
fun initializationLogic(){
println(this::lateinitVar.isInitialized) // false
lateinitVar = ""
println(this::lateinitVar.isInitialized) // true
}
}
fun main(){
var c = MyClass()
c.initializationLogic()
}

二. Object-oriented Programming

1. OOP in Kotlin

1) modifier intro.

Modifier Description
final (default) cann’t be overridden
open can be overridden
abstract must be overridden
override (mandatory) Overrides a member in a superclass or interface
Visibility Modifier Class member Top-level declaration
public (default) visible everywhere visible everywhere
internal visible in the mudule visible in the module
protected visible in the class and the subclasses (in Java, it includes the same package)
private visible in the class visible in the file
  • Visibility modifiers and Java

    Kotlin modifier JVM level
    public public
    internal public & name mangling
    protected protected
    private private / package private

2) Package structure

1
2
3
4
5
6
7
8
9
10
11
12
13
// Java
package:org.kyle.store
City.java
Customer.java
Order.java

// Kotlin
package:store
StoreModel.kt
(File Structure)
Class City
Class Customer
Class Order
  • one kotlin file may contain several classes and top-level functions

2. Constructors, Inheritance syntax

1) Constructor

  • Primer Constructor

  • var/val on a parameter creates a property

    1
    2
    3
    class Person(val name: String) {

    }

    the same as

    1
    2
    3
    4
    5
    6
    class Person(name: String){
    val name:String
    init {
    this.name = name
    }
    }
  • Changing visibility of a constructor

    1
    2
    class InternalComponent
    internal constructor(name:St)
  • Each Secondary constructor must using this to call another constructor

2) Syntax for inheritance

  • Same syntax for extends and implements

    1
    2
    3
    4
    5
    interface Base
    class BaseImpl : Base

    open class Parent
    class Child: Parent()
    1
    2
    open class Parent(val name: String)
    class Child(name: String) : Parent(name)
    1
    2
    3
    4
    open class Parent(val name: String)
    class Child : Parent {
    constructor(name: String, param: Int) : super(name)
    }
  • init order

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    open class Parent(){
    init {
    println("Parent")
    }
    }

    class Child:Parent(){
    init {
    println("Child")
    }
    }
    val child = Child()
    // Parent
    // Child
  • Overriding a property

    Actually you are overriding a getter, not a field

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    open class Parent() {
    open val foo = 1

    init {
    println(foo)
    }
    }

    class Child : Parent() {
    override val foo = 2
    }

    val child = Child()
    // 0

3. Class modifiers

1) enum

  • enum class Color(val r: Int, val g: Int, val b: Int) {
        BLUE(0, 0, 255), ORANGE(255, 165, 0), RED(255, 0, 0);
        fun rgb() = (r * 256 + g) * 256 + b
    }
    
    fun main(){
        println(BLUE.r) // 0
        println(BLUE.rgb()) // 255
    }
    <!--code20-->
    
1
2
3
4
5
6
7
8
sealed class Expr
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()

fun eval(e: Expr): Int = when (e) {
is Num -> e.value
is Sum -> eval(e.left) + eval(e.right)
}

4) nested and inner class

  • Inner class stores the reference to an outer class (Memory leak)
In Java In Kotlin Class declared within another class
static class A class A (by default) nested class
class A (by default) inner class A inner class
1
2
3
4
5
6
7
8
9
10
class A {
class B {
inner class C() {
fun main() {
println(this)
println(this@B)
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
class A {
inner class B {
inner class C() {
fun main() {
println(this)
println(this@B)
println(this@A)
}
}
}
}

5) Class delegation

To implement the interface by delegation to this instance using keyword by

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Repository {
fun getById(id: Int): String
fun getAll(): List<String>
}

interface Logger {
fun logAll()
}

class Controller(
repository: Repository,
logger: Logger
) : Repository by repository, Logger by logger {
fun use(controller:Controller){
controller.logAll()
}
}

4. ?? Objects, object expressions & companion objects

5. Constants

  • const (for primitive types and String)

    compile-time constants, the value is inlined

    1
    const val answer = 42
  • @JvmField (eliminates accessors)

    exposes a Kotlin property as a field in Java

  • @JvmStatic @JvmField const

    1
    2
    3
    4
    5
    6
    7
    8
    9
    object SuperComputer{
    @JvmStatic
    val answer = 42
    }

    fun main(){
    println(SuperComputer.answer)
    // System.out.println(SuperComputer.getAnswer())
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    object SuperComputer{
    @JvmField
    val answer = 42
    }

    fun main(){
    println(SuperComputer.answer)
    // System.out.println(SuperComputer.answer)
    }
    1
    2
    3
    4
    5
    6
    7
    8
    object SuperComputer{
    const val answer = 42
    }

    fun main(){
    println(SuperComputer.answer)
    // System.out.println(42)
    }

6. Generics

  • T can be nullable or not nullable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fun <T> List<T>.filter(predicate:(T) -> Boolean):List<T> {
    return this
    }

    fun use1(ints:List<Int>){
    ints.filter { it > 0 }
    }
    fun use2(strings:List<String?>){
    strings.filter { !it.isNullOrEmpty() }
    }
  • Nullable generic argument

    1
    2
    3
    4
    5
    6
    7
    8
    fun <T> List<T>.firstOrNull(): T? {
    ...
    }

    val ints = listOf(1, 2, 3)
    val i = ints.firstOrNull() // 1
    val j = listOf<Int>().firstOrNull() // null
    val k = listOf(null, 1).firstOrNull() // null
  • Non-nullable upper bound (Any)

    1
    2
    3
    4
    5
    6
    7
    fun <T : Any> foo(list: List<T>) {
    for (element in list) {

    }
    }

    //val a = foo(listOf(1,null)) // compile error

    Nullable upper bound (Any?)

  • Comparable upper bound

  • Multiple constraints for a type parameter (using where)

    1
    2
    3
    4
    5
    6
    fun <T> ensureTrailingPeriod(seq: T)
    where T : CharSequence, T : Appendable{
    if (!seq.endsWith('.')){
    seq.append('.')
    }
    }
  • same JVM signature problem

    1
    2
    // fun List<Int>.average(): Double {}
    // fun List<Double>.average(): Double {}

    Solution

    1
    2
    3
    fun List<Int>.average(): Double {}
    @JvmName("averageOfDouble")
    fun List<Double>.average(): Double {}

三. Conventions

1. Operator Overloading

1) Arithmetic operations

expression function name
a + b plus
a - b minus
a * b times
a / b div
a % b mod
1
2
3
4
5
6
7
8
9
operator fun Point.plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}

fun main() {
val p1 = Point(3, 4)
val p2 = Point(5, 6)
println(p1 + p2) // (8,10)
}

2) No restrictions on parameter type

1
2
3
4
5
6
7
8
operator fun Point.times(scale: Int): Point {
return Point(x * scale, y * scale)
}

fun main() {
val p3 = Point(1, 2)
println(p3 * 3) // (3,6)
}

3) Unary operation

1
operator fun Point.unaryMinus() = Point(-x,-y)
Expression function name
+a unaryPlus
-a unaryMinus
!a not
++a,a++ inc
–a,a– dec

4) Assignment operations

  • a += b

    it you define any one of the 2, it will call the defined one,

    else if you define 2, you will call a.plusAssign if a is val,

    else you will get a compile error

    • a = a.plus(b)
    • a.plusAssign(b)

5) Conventions for lists

  • Prefer val to var
1
2
3
4
5
6
7
8
9
10
11
    var l1 = listOf(1, 2)                        
l1 += 2 // `l1 = l1.plus(2)` is called

val l2 = listOf(1, 2)
// l2 += 2 // compile error, val can't be resi

var l3 = mutableListOf(1, 2)
// l3 += 2 // compile error, ambiguous `+=`

val l4 = mutableListOf(1, 2)
l4 += 2 // `l4.plusAssign(2) is called`

2. Conventions

1) Comparisons

symbol translated to
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

2) Equality check

  • s1 == s2 calls equals under the hood s1.equals(s2)

  • it can correctly handles nullable values

    1
    2
    3
    s1 == s2 // s1.equals(s2)
    null == "abc" // false
    null == null // true

3) Index

  • Accessing elements by index: []

    x[a,b] // x.get(a,b)
    y[a,b] = c // x.set(a,b,c)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Board() {
    val cheet = Array(3) { CharArray(2) }
    operator fun get(x: Int, y: Int): Char {
    return cheet[x][y]
    }

    operator fun set(x: Int, y: Int, value: Char) {
    cheet[x][y] = value
    }
    }

    fun main() {
    val board = Board()
    board[1, 2] = 'x'
    board[1, 2] // 'x'
    }

4) Other convention

symbol translated to
a in c c.contains(a)
start..end start.rangeTo(end)
for(a in c){} (in a for loop) c.iterator()
  • Destructuring declarations

    val (a,b) = p

    • val a = p.component1()
    • val b = p.component2()
  • Destructuring in lambdas

    Description Syntax
    One parameter { a -> …}
    Two parameters { a, b -> …}
    A destructured pair { (a, b) -> …}
    A destructured pair and another parameter { (a, b), c -> …}
  • Iterating over list with index

    1
    2
    3
    for ((index, element) in list.withIndex()) {
    println("$index $element")
    }
    1
    2
    3
    4
    5
    for (indexedValue in list.withIndex()) {
    val index = indexedValue.component1()
    val element = indexedValue.component2()
    println("$index $element")
    }
  • Destructuring declarations & Data classes

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    data class Contact(
    val name: String,
    val email: String,
    val phoneNumber: String
    )

    fun main() {
    val contact = Contact("Kyle", "[email protected]", "185")
    contact.component1()
    contact.component2()
    contact.component3()

    val (name,_,phoneNumber) = contact
    }

Week3

Week 3

一. Nullability

1. Nullable types

1) Null Pointer Exception

Modern approach: to make NPE compile-time error, not run-time error

2) Null types

1
2
3
4
5
6
7
8
9
val s1:String = "always not null"
// s2 and s3 is nullable String
val s2:String? = "can be null or non-null"
val s3:String? = null

// l1 is nullable int - `Int?`
val l1 = s3?.length
// l2 is int - `Int`
val l2 = s3?.length ?: 0

3) 3 ways to call methods on a nullable type

  • if-checks
  • ?.
  • !!
1
2
3
4
5
6
7
8
s1.length

//s2.length // compile error
if (s2 != null) {
s2.length
}
s2?.length
s2!!.length

4) operators

  • Nullability operator ?.

    foo?.bar() means

    if foo != null return foo.bar()
    if foo == null return null

    1
    2
    val length:Int? = if(s != null ) s.length else null
    val length:Int? = s?.length
  • Elvis operator ?:

    foo ?: bar means

    if foo != null return foo

    if foo == null return bar

    1
    2
    val length:Int = if(s != null) s.length else 0
    val lenght:Int = s?.length ?: 0

    Both ?. and ?: come from Groovy

  • Not-null assertion operator !!

    foo!! means

    if foo != null return foo

    if foo == null return NPE

Remember Prefer ?. ?: if-checks to !!

2. Nullable types under the hood

1) Under the hood

  • Nullable types under the hood are using @Nullable @NotNull annotations

    There is no performance ahead

2) Nullable types VS Optional

$Nullable Types \neq Optional$

  • NullableType uses only one object
  • Optional type uses 2 objects (one is for the wrapper, the other is the actual type)

3) List<Int?> VS List<Int>?

1
2
3
4
5
6
7
fun foo(list1:List<Int?>,list2:List<Int>?){
list1.size
val i: Int? = list1.get(0)

list2?.size
val j: Int? = list2?.get(0)
}

3. Safe casts

  • safe cast as?

    foo as? Type means

    if foo is Type -> foo as Type

    if foo !is Type -> null

    1
    2
    val length:Int? = if(s != null ) s.length else null
    val length:Int? = s?.length

4. Importance of nullability

二. Functional Programming

1. Lambdas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
val list = listOf(23, 34)
val map = mapOf(1 to 11,2 to 22)
list.any({ i: Int -> i > 0 }) // full syntax
list.any() { i: Int -> i > 0 } // when lambda is the last argument
list.any { i: Int -> i > 0 } // empty parentheses can be omitted
list.any { i -> i > 0 } // type can be omitted if it's clear from the context
list.any { it > 0 } // "it" denoted the argument if it's only one
list.any {
println("processing $it")
it > 0 // the last expression is the result
}
map.mapValues { entry -> "${entry.key} -> ${entry.value}" }
map.mapValues { (key,value) -> "$key -> $value" }
map.mapValues { (_,value) -> "$value" }

2. Common Operations on collections

  • filter : return a new list satisfying the predict

  • map : return a new list with the operation

  • any/all/none: return true if any/all/none satisfy the predict

  • find/firstOrNull return the first result, if none, return null

    first : if no element throw an exception

  • count : counts the number of the given predict

  • partition : return a pair of list list

  • groupBy

  • associateBy : duplicates removed

  • associate: create a pair T to V (V = f(T))

  • zip : length = min(list1, list2)

    zipWithNext

  • flatten : call on a List<List<>>

  • flatMap ?

3. Operation Quiz

1) Simplifying Code

  • don’t use it if it has different types in neighboring lines
  • prefer explicit parameter names if it might be confusing otherwise
  • learn the library and try to reuse the library functions as much as possible

4. Function types

1) lambda has a type (xx,xx) -> xx

1
2
val sum = { x: Int, y: Int -> x + y }
val sum2: (Int, Int) -> Int = { x, y -> x + y }

2) it can be called like a function

1
2
val isEven = { i: Int -> i % 2 == 0 }
val result: Boolean = isEven(42) // true

3) Passing a variable of function type as an argument

1
2
3
val list = listOf(1, 2, 3, 4)
list.any(isEven) // true
list.filter(isEven) // [2,4]

4) Calling lambda directly

1
2
;{ println("hey!") }() // seems strange
run { println("hey") } // use run instead

5) SAM interfaces in Java

1
2
3
4
// use lambda as parameter of SAM like Runnable ..
// In Java:
void postponeComputation(int delay, Runnable computation)
postponeComputation(1000) { println(42) }

6) Function types and nullability

1
2
3
4
//    val f1: () -> Int? = null
val f2: () -> Int? = { null }
val f3: (() -> Int)? = null
// val f4: (() -> Int)? = { null }

7) Working with a nullable function type

1
2
3
4
5
6
7
8
9
10
    val f: (() -> Int)? = null

// f() // compile error

// method 1
if (f != null){
f()
}
// method 2
f?.invoke()

5. Member references

1) Intro

1
2
3
val people = listOf<Person>(Person("Kyle", 19));
people.maxBy { it.age }
people.maxBy(Person::age) // class::member

2) Function VS lambdas

  • You can store lambda in a variable

  • but you can’t store a function in a varible

    1
    2
    val isEven = { i: Int -> i % 2 == 0 }
    val predicate = isEven
    1
    2
    3
    fun isEven(i: Int): Boolean = i % 2 == 2
    // val predicate = isEven //compile error
    val predicate = isEven()
  • use function reference instead

    1
    2
    3
    4
    fun isEven(i: Int): Boolean = i % 2 == 2
    val predicate = ::isEven
    // under the hood, it is the under line.
    // val predicate = {i: Int -> isEven(i)}

    another example

    1
    2
    3
    4
    5
    6
    fun sendEmail(person: Person, message: String) {}

    // val action = { person: Person, message: String ->
    // sendEmail(person, message)
    // }
    val action = ::sendEmail

3) Passing function reference as an argument (lambda)

1
2
3
4
fun isEven(i: Int): Boolean = i % 2 == 0
val list = listOf(1,2,3,4)
list.any(::isEven)
list.filter(::isEven)

4) bound VS unbound reference

  • regular non-bound reference

    1
    2
    3
    val agePredicate: (Person, Int) -> Boolean = Person::isOlder
    val alice = Person("Alice", 29)
    agePredicate(alice, 21)
  • bound reference

    1
    2
    3
    val alice = Person("Alice", 29)
    val agePredicate: (Int) -> Boolean = alice::isOlder
    agePredicate(21)
  • Bound to this reference

    1
    2
    3
    4
    class Person(val name: String, val age: Int) {
    fun isOlder(ageLimit: Int) = age > ageLimit
    fun getAgePredicate() = ::isOlder
    }

    Note: In the following example, ::isEven is not a bound reference

    1
    2
    3
    4
    5
    fun isEven(i: Int): Boolean = i % 2 == 0

    val list = listOf(1, 2, 3, 4)
    list.any(::isEven)
    list.filter(::isEven)

6. Returning from lambda

1) return return from function marked with fun

  • fun duplicateNonZero(list:List<Int>):List<Int>{
        return list.flatMap {
            if (it == 0) return listOf()
            listOf(it,it)
        }
    }
    println(duplicateNonZero(listOf(3,0,5))) // [],wrong
    <!--code24-->
  • Solution using label

    1
    2
    3
    4
    5
    6
    7
    fun duplicateNonZero(list:List<Int>):List<Int>{
    return list.flatMap {
    if (it == 0) return@flatMap listOf<Int>()
    listOf(it,it)
    }
    }
    println(duplicateNonZero(listOf(3,0,5))) // [3,3,5,5],solution 1
  • Solution using local function

    1
    2
    3
    4
    5
    6
    7
    8
    fun duplicateNonZeroLocalFunction(list:List<Int>):List<Int>{
    fun duplicateNonZeroElement(e:Int):List<Int>{
    if (e == 0)return listOf()
    return listOf(e,e)
    }
    return list.flatMap(::duplicateNonZeroElement)
    }
    println(duplicateNonZeroLocalFunction(listOf(3,0,5))) // [3,3,5,5]
  • Solution using anonymous function

    1
    2
    3
    4
    5
    6
    7
    fun duplicateNonZero(list: List<Int>): List<Int> {
    return list.flatMap(fun(e): List<Int> {
    if (e == 0) return listOf()
    return listOf(e, e)
    })
    }
    println(duplicateNonZero(listOf(3, 0, 5)))
  • Solution no return

    1
    2
    3
    4
    5
    6
    7
    8
    9
    fun duplicateNonZero(list: List<Int>): List<Int> {
    return list.flatMap{
    if (it == 0)
    listOf()
    else
    listOf(it,it)
    }
    }
    println(duplicateNonZero(listOf(3, 0, 5)))

3) labeled return inside forEach correspond to continue for loop

  • val list = listOf(3, 0, 5)
    list.forEach {
        if (it == 0) return@forEach
        println(it)
    }
    <!--code29-->