【Try Kotlin】Kotlin Koans 代码笔记

Kotlin Koans 心印

Introduction

1.Hello, world!

Simple Functions

Take a look at function syntax and make the function start return the string "OK".

In the tasks the function TODO() is used that throws an exception. Your job during the koans will be to replace this function invocation with a meaningful code according to the problem.

TODO:

fun start(): String = TODO()

万事开头Hello world.

一道简单函数,意思是要构造一个start函数返回字符串OK.

CODE:

fun start(): String = "OK"

fun main(args :Array<String>)
{
start()
}

涉及的语法

  • 定义函数

    1.带有两个 Int 参数、返回 Int 的函数:

    fun sum(a: Int, b: Int): Int {
    return a + b
    }

    2.将表达式作为函数体、返回值类型自动推断的函数:

    fun sum(a: Int, b: Int) = a + b

    3.函数返回无意义的值:

    fun printSum(a: Int, b: Int): Unit {
    println("sum of $a and $b is ${a + b}")
    }

    4.Unit 返回类型可以省略:

    fun printSum(a: Int, b: Int) {
    println("sum of $a and $b is ${a + b}")
    }
  • 单表达式函数

    1.当函数返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可

    fun double(x: Int): Int = x * 2

    2.当返回值类型可由编译器推断时,显式声明返回类型是可选的

    fun double(x: Int) = x * 2

2.Java to Kotlin conversion

We have a handy tool for Java developers: Java to Kotlin converter. It works better in IntelliJ IDEA, but you can try it online as well. To become familiar with it, please convert the code below. Copy Java code, choose 'Convert from Java' above and copy the result function back.

public class JavaCode {
public String toJSON(Collection<Integer> collection) {
StringBuilder sb = new StringBuilder();
sb.append("[");
Iterator<Integer> iterator = collection.iterator();
while (iterator.hasNext()) {
Integer element = iterator.next();
sb.append(element);
if (iterator.hasNext()) {
sb.append(", ");
}
}
sb.append("]");
return sb.toString();
}
}

复制粘贴点转换,开心飞:D

CODE:

class JavaCode {
fun toJSON(collection:Collection<Int>):String {
val sb = StringBuilder()
sb.append("[")
val iterator = collection.iterator()
while (iterator.hasNext()) {
val element = iterator.next()
sb.append(element)
if (iterator.hasNext()) {
sb.append(", ")
}
}
sb.append("]")
return sb.toString()
}
}

Kotlin代码讲道理是真的比Java看起来心情愉悦 :)

3.Named arguments

Default and named arguments help to minimize the number of overloads and improve the readability of the function invocation. The library function joinToString is declared with default values for parameters:

fun joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = "",
/* ... */
): String

It can be called on a collection of Strings. Specifying only two arguments make the function joinOptions() return the list in a JSON format (e.g., "[a, b, c]")

TODO:

fun joinOptions(options: Collection<String>) = options.joinToString(TODO())

使用默认参数和命名参数可使重载数量最小化并且提高函数调用的可读性。库函数 joinToString 就在声明中对参数赋予了默认值,它可以被一个字符串集合调用。

指定两个参数值来使函数 joinOptions() 构成形如[a, b, c]的返回值。

分隔符还是逗号空格不变,那就只要把前缀改成[,后缀改成]就好了。

CODE:

fun joinOptions(options: Collection<String>) = options.joinToString(prefix = "[", postfix = "]")

涉及的语法

  • 默认参数

    1.函数参数可以有默认值,当省略相应的参数时使用默认值。与其他语言相比,这可以减少重载数量。

    fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size()) {
    ……
    }

默认值通过类型后面的 = 及给出的值来定义。

2.覆盖方法总是使用与基类型方法相同的默认参数值。 当覆盖一个带有默认参数值的方法时,必须从签名中省略默认参数值:
```
open class A {
open fun foo(i: Int = 10) { …… }
} class B : A() {
override fun foo(i: Int) { …… } // 不能有默认值
}
```
  • 命名参数

    可以在调用函数时使用命名的函数参数。当一个函数有大量的参数或默认参数时这会非常方便。

    给定以下函数

    fun reformat(str: String,
    normalizeCase: Boolean = true,
    upperCaseFirstLetter: Boolean = true,
    divideByCamelHumps: Boolean = false,
    wordSeparator: Char = ' ') {
    ……
    }

    我们可以使用默认参数来调用它

    reformat(str)

    然而,当使用非默认参数调用它时,该调用看起来就像

    reformat(str, true, true, false, '_')

    使用命名参数我们可以使代码更具有可读性

    reformat(str,
    normalizeCase = true,
    upperCaseFirstLetter = true,
    divideByCamelHumps = false,
    wordSeparator = '_'
    )

    并且如果我们不需要所有的参数

    reformat(str, wordSeparator = '_')

    请注意,在调用 Java 函数时不能使用命名参数语法,因为 Java 字节码并不总是保留函数参数的名称。

4.Default arguments

There are several overloads of 'foo()' in Java:

public String foo(String name, int number, boolean toUpperCase) {
return (toUpperCase ? name.toUpperCase() : name) + number;
}
public String foo(String name, int number) {
return foo(name, number, false);
}
public String foo(String name, boolean toUpperCase) {
return foo(name, 42, toUpperCase);
}
public String foo(String name) {
return foo(name, 42);
}

All these Java overloads can be replaced with one function in Kotlin. Change the declaration of the function foo in a way that makes the code using foo compile. Use default and named arguments.

TODO:

fun foo(name: String, number: Int, toUpperCase: Boolean) =
(if (toUpperCase) name.toUpperCase() else name) + number fun useFoo() = listOf(
foo("a"),
foo("b", number = 1),
foo("c", toUpperCase = true),
foo(name = "d", number = 2, toUpperCase = true)
)

以上的那几个 foo() 函数的重载在Kotlin中可以用一个函数来取代,请运用默认参数和命名参数改写一下 foo 函数的声明,使得在调用 foo 函数的时候更简洁。

观察可以发现最简单的一个调用也就单单只传了个字符串,那后面两个参数 number 和 toUpperCase 就需要在声明的时候赋一个默认值。再看那一串Java代码,显然两个默认值分别是 42 和 false 。

CODE:

fun foo(name: String, number: Int = 42, toUpperCase: Boolean = false) =
(if (toUpperCase) name.toUpperCase() else name) + number

5.Lambdas

Kotlin supports a functional style of programming. Read about higher-order functions and function literals (lambdas) in Kotlin.

Pass a lambda to any function to check if the collection contains an even number. The function any gets a predicate as an argument and returns true if there is at least one element satisfying the predicate.

TODO:

fun containsEven(collection: Collection<Int>): Boolean = collection.any { TODO() }

给any函数传一个lambda表达式来测试集合元素的个数是否是偶数。

any函数:对于传给any函数的布尔表达式只要一个元素满足就返回true。

CODE:

fun containsEven(collection: Collection<Int>): Boolean = collection.any { it % 2 == 0 }

涉及的语法

  • 高阶函数

    高阶函数是将函数用作参数或返回值的函数。

    一个很好的例子是 lock(),它接受一个锁对象和一个函数,获取锁,运行函数并释放锁:

    fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
    return body()
    }
    finally {
    lock.unlock()
    }
    }

    检查上面的代码: body 拥有函数类型:() -> T, 所以它应该是一个不带参数并且返回 T 类型值的函数。 它在 try-代码块内部调用、被 lock 保护,其结果由 lock() 函数返回。

    当要调用 lock() 函数时,我们可以把另一个函数传给它作为参数:

    fun toBeSynchronized() = sharedResource.operation()
    val result = lock(lock, ::toBeSynchronized)

    通常会更方便的另一种方式是传一个 lambda 表达式:

    val result = lock(lock, { sharedResource.operation() })

    Lambda 表达式在下文会有更详细的描述,但为了继续这一段,让我们看一个简短的概述:

    lambda 表达式总是被大括号括着,

    其参数(如果有的话)在 -> 之前声明(参数类型可以省略),

    函数体(如果存在的话)在 -> 后面。

    在 Kotlin 中有一个约定,如果函数的最后一个参数是一个函数,并且你传递一个 lambda 表达式作为相应的参数,你可以在圆括号之外指定它:

    lock (lock) {
    sharedResource.operation()
    }

    高阶函数的另一个例子是 map():

    fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
    val result = arrayListOf<R>()
    for (item in this)
    result.add(transform(item))
    return result
    }

    如果 lambda 是该调用的唯一参数,则调用中的圆括号可以省略。因此,该函数可以如下调用:

    val doubled = ints.map { value -> value * 2 }

    其中,如果函数字面值只有一个参数, 那么它的声明可以省略(连同 ->),其名称是 it。(it:单个参数的隐式名称)

    ints.map { it * 2 }
  • Lambda 表达式和匿名函数

    1.一个 lambda 表达式或匿名函数是一个“函数字面值”,即一个未声明的函数, 但立即做为表达式传递。考虑下面的例子:

    max(strings, { a, b -> a.length < b.length })

    函数 max 是一个高阶函数,换句话说它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,即函数字面值。写成函数的话,它相当于

    fun compare(a: String, b: String): Boolean = a.length < b.length

    2.对于接受另一个函数作为参数的函数,我们必须为该参数指定函数类型。 例如上述函数 max 定义如下:

    fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
    var max: T? = null
    for (it in collection)
    if (max == null || less(max, it))
    max = it
    return max
    }

    参数 less 的类型是 (T, T) -> Boolean,即一个接受两个类型T的参数并返回一个布尔值的函数: 如果第一个参数小于第二个那么该函数返回 true。

    在上面第 4 行代码中,less 作为一个函数使用:通过传入两个 T 类型的参数来调用。

    如上所写的是就函数类型,或者可以有命名参数,如果你想文档化每个参数的含义的话:

    val compare: (x: T, y: T) -> Int = ……

    3.Lambda 表达式语法

    Lambda 表达式的完整语法形式,即函数类型的字面值如下:

    val sum = { x: Int, y: Int -> x + y }

    lambda 表达式总是被大括号括着, 完整语法形式的参数声明放在括号内,并有可选的类型标注, 函数体跟在一个 -> 符号之后。如果推断出的该 lambda 的返回类型不是 Unit,那么该 lambda 主体中的最后一个(或可能是单个)表达式会视为返回值。

    如果我们把所有可选标注都留下,看起来如下:

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

    一个 lambda 表达式只有一个参数是很常见的。 如果 Kotlin 可以自己计算出签名,它允许我们不声明唯一的参数,并且将隐含地为我们声明其名称为 it:

    ints.filter { it > 0 } // 这个字面值是“(it: Int) -> Boolean”类型的

    我们可以使用限定的返回语法从 lambda 显式返回一个值。否则,将隐式返回最后一个表达式的值。因此,以下两个片段是等价的:

    ints.filter {
    val shouldFilter = it > 0
    shouldFilter
    }
    ints.filter {
    val shouldFilter = it > 0
    return@filter shouldFilter
    }

    请注意,如果一个函数接受另一个函数作为最后一个参数,lambda 表达式参数可以在圆括号参数列表之外传递。

    4.带接收者的函数字面值

    Kotlin 提供了使用指定的接收者对象调用函数字面值的功能。 在函数字面值的函数体中,可以调用该接收者对象上的方法而无需任何额外的限定符。 这类似于扩展函数,它允许你在函数体内访问接收者对象的成员。

    这样的函数字面值的类型是一个带有接收者的函数类型:

    sum : Int.(other: Int) -> Int

    该函数字面值可以这样调用,就像它是接收者对象上的一个方法一样:

    1.sum(2)

    匿名函数语法允许你直接指定函数字面值的接收者类型。 如果你需要使用带接收者的函数类型声明一个变量,并在之后使用它,这将非常有用。

    val sum = fun Int.(other: Int): Int = this + other

    当接收者类型可以从上下文推断时,lambda 表达式可以用作带接收者的函数字面值。

    class HTML {
    fun body() { …… }
    } fun html(init: HTML.() -> Unit): HTML {
    val html = HTML() // 创建接收者对象
    html.init() // 将该接收者对象传给该 lambda
    return html
    } html { // 带接收者的 lambda 由此开始
    body() // 调用该接收者对象的一个方法
    }

6.Strings

Read about different string literals and string templates in Kotlin.

Raw strings are useful for writing regex patterns, you don't need to escape a backslash by a backslash. Below there is a pattern that matches a date in format 13.06.1992 (two digits, a dot, two digits, a dot, four digits):

fun getPattern() = """\d{2}\.\d{2}\.\d{4}"""

Using month variable rewrite this pattern in such a way that it matches the date in format 13 JUN 1992 (two digits, a whitespace, a month abbreviation, a whitespace, four digits).

TODO:

val month = "(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)"

fun getPattern(): String = TODO()

原生字符串对于写正则表达式十分有用,不需要用反斜杠来转义反斜杠。如上给出了一个形如13.06.1992的正则表达式的写法。

使用变量month来改写这个表达式使得符合形如13 JUN 1992的日期样式。

CODE:

fun getPattern(): String = """\d{2} ${month} \d{4}"""

涉及的语法:

  • 字符串

    字符串用 String 类型表示。字符串是不可变的。 字符串的元素——字符可以使用索引运算符访问: s[i]。 可以用 for 循环迭代字符串:

    for (c in str) {
    println(c)
    }

    1.字符串字面值

    Kotlin 有两种类型的字符串字面值:转义字符串和原生字符串。 转义字符串可以有转义字符,原生字符串可以包含换行和任意文本。

    转义字符串采用传统的反斜杠方式:

    val s = "Hello, world!\n"

    原生字符串使用三个引号(""")分界符括起来,内部没有转义并且可以包含换行和任何其他字符:

    val text =
    """
    for (c in "foo")
    print(c)
    """

    可以通过 trimMargin() 函数去除前导空格:

    val text =
    """
    |Tell me and I forget.
    |Teach me and I remember.
    |Involve me and I learn.
    |(Benjamin Franklin)
    """.trimMargin()

    trimMargin 函数默认 | 用作边界前缀,但也可以选择其他字符并作为参数传入,比如 trimMargin(">")

    2.字符串模板

    字符串可以包含模板表达式 ,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符$开头,由一个简单的名字构成:

    val i = 10
    val s = "i = $i" // 求值结果为 "i = 10"

    或者用花括号扩起来的任意表达式:

    val s = "abc"
    val str = "$s.length is ${s.length}" // 求值结果为 "abc.length is 3"

    原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $ 字符(它不支持反斜杠转义),你可以用下列语法:

    val price = """${'$'}9.99"""
  • 正则表达式

    1. 元字符\d 匹配一个数字字符。等价于[0-9]

    2. 限定符{n} 匹配确定的 n 次。例如,o{2} 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。

7.Data classes

Rewrite the following Java code to Kotlin:

public class Person {
private final String name;
private final int age; public Person(String name, int age) {
this.name = name;
this.age = age;
} public String getName() {
return name;
} public int getAge() {
return age;
}
}

Then add a modifier data to the resulting class. This annotation means the compiler will generate a bunch of useful methods in this class: equals/hashCode, toString and some others. The getPeople function should start to compile.

Read about classes, properties and data classes.

TODO:

class Person

fun getPeople(): List<Person> {
return listOf(Person("Alice", 29), Person("Bob", 31))
}

要求就是写一个kotlin的数据类来实现上面的Java代码一样的功能。

CODE:

data class Person(val name:String, val age:Int)

fun getPeople(): List<Person> {
return listOf(Person("Alice", 29), Person("Bob", 31))
}

涉及的语法:

  • Kotlin中的类

    1.类

    Kotlin 中使用关键字 class 声明类

    class Invoice {
    }

    类声明由类名、类头(指定其类型参数、主构造函数等)和由大括号包围的类体构成。类头和类体都是可选的; 如果一个类没有类体,可以省略花括号:

    class Empty

    2.构造函数

    在 Kotlin 中的一个类可以有一个主构造函数和一个或多个次构造函数。

    • 主构造函数

      主构造函数是类头的一部分,它跟在类名(和可选的类型参数)后。

    class Person constructor(firstName: String) {
    }

    如果主构造函数没有任何 注解 或者 可见性修饰符 ,可以省略这个 constructor 关键字。

    class Person(firstName: String) {
    }
      主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键   字作为前缀的初始化块中:
    class Customer(name: String) {
    init {
    logger.info("Customer initialized with value ${name}")
    }
    }
      主构造的参数可以在初始化块中使用。它们也可以在类体内声明的属性初始化器中使用:
    class Customer(name: String) {
    val customerKey = name.toUpperCase()
    }
      事实上,声明属性以及从主构造函数初始化属性,Kotlin 有简洁的语法:
    class Person(val firstName: String, val lastName: String, var age: Int) {
    // ……
    }
      与普通属性一样,主构造函数中声明的属性可以是可变的(var)或只读的(val)。
    
      如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面:
    class Customer public @Inject constructor(name: String) { …… }
    • 次构造函数

      类也可以声明前缀有 constructor的次构造函数:

    class Person {
    constructor(parent: Person) {
    parent.children.add(this)
    }
    }
      如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可:
    class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
    parent.children.add(this)
    }
    }
      如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数。构造函数的可见性是 public。如果你不希望你的类有一个公有构造函数,你需要声明一个带有非默认可见性的空的主构造函数:
    class DontCreateMe private constructor () {
    }
      注意:在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值。
    class Customer(val customerName: String = "")

    3.创建类的实例

    要创建一个类的实例,我们就像普通函数一样调用构造函数:

    val invoice = Invoice()
    
    val customer = Customer("Joe Smith")
  • 数据类

    我们经常创建一些只保存数据的类。在这些类中,一些标准函数往往是从数据机械推导而来的。在 Kotlin 中,这叫做 数据类 并标记为 data

    data class User(val name: String, val age: Int)

    为了确保生成的代码的一致性和有意义的行为,数据类必须满足以下要求:

    主构造函数需要至少有一个参数;

    主构造函数的所有参数需要标记为 val 或 var;

    数据类不能是抽象、开放、密封或者内部的;

    (在1.1之前)数据类只能实现接口。

    在 JVM 中,如果生成的类需要含有一个无参的构造函数,则所有的属性必须指定默认值。

    data class User(val name: String = "", val age: Int = 0)

8.Nullable types

Read about null safety and safe calls in Kotlin and rewrite the following >Java code using only one if expression:

public void sendMessageToClient(
@Nullable Client client,
@Nullable String message,
@NotNull Mailer mailer
) {
if (client == null || message == null) return; PersonalInfo personalInfo = client.getPersonalInfo();
if (personalInfo == null) return; String email = personalInfo.getEmail();
if (email == null) return; mailer.sendMessage(email, message);
}

TODO:

fun sendMessageToClient(
client: Client?, message: String?, mailer: Mailer
){
TODO()
} class Client (val personalInfo: PersonalInfo?)
class PersonalInfo (val email: String?)
interface Mailer {
fun sendMessage(email: String, message: String)
}

只使用一个if来重写实现上面的java代码。

CODE:

fun sendMessageToClient(
client: Client?, message: String?, mailer: Mailer
){
val email = client?.personalInfo?.email
if(email!=null && message!=null){
mailer.sendMessage(email,message)
}
} class Client (val personalInfo: PersonalInfo?)
class PersonalInfo (val email: String?)
interface Mailer {
fun sendMessage(email: String, message: String)
}

涉及的语法:

  • 空安全

    在 Kotlin 中,类型系统区分一个引用可以容纳 null (可空引用)还是不能容纳(非空引用)。 例如,String 类型的常规变量不能容纳 null:

    var a: String = "abc"
    a = null // 编译错误

    如果要允许为空,我们可以声明一个变量为可空字符串,写作 String?:

    var b: String? = "abc"
    b = null // ok

    现在,如果你调用 a 的方法或者访问它的属性,它保证不会导致 NPE,这样你就可以放心地使用:

    val l = a.length

    但是如果你想访问 b 的同一个属性,那么这是不安全的,并且编译器会报告一个错误:

    val l = b.length // 错误:变量“b”可能为空

    那我们还是需要访问怎么办呢?有如下办法:

    1.在条件中检查 null

    首先,你可以显式检查 b 是否为 null,并分别处理两种可能:

    val l = if (b != null) b.length else -1

    编译器会跟踪所执行检查的信息,并允许你在 if 内部调用 length。 同时,也支持更复杂的条件等等:

    if (b != null && b.length > 0) {
    print("String of length ${b.length}")
    } else {
    print("Empty string")
    }

    请注意,这只适用于 b 是不可变的情况(即在检查和使用之间没有修改过的局部变量 ,或者不可覆盖并且有幕后字段的 val 成员),因为否则可能会发生在检查之后 b 又变为 null 的情况。

    2.安全的调用

    你的第二个选择是安全调用操作符,写作 ?.

    b?.length

    如果 b 非空,就返回 b.length,否则返回 null,这个表达式的类型是 Int?。

    安全调用在链式调用中很有用。例如,如果一个员工 Bob 可能会(或者不会)分配给一个部门, 并且可能有另外一个员工是该部门的负责人,那么获取 Bob 所在部门负责人(如果有的话)的名字,我们写作:

    bob?.department?.head?.name

    如果任意一个属性(环节)为空,这个链式调用就会返回 null。

    如果要只对非空值执行某个操作,安全调用操作符可以与 let 一起使用:

    val listWithNulls: List<String?> = listOf("A", null)
    for (item in listWithNulls) {
    item?.let { println(it) } // 输出 A 并忽略 null
    }

9.Smart casts

Rewrite the following Java code using smart casts and when expression:

public int eval(Expr expr) {
if (expr instanceof Num) {
return ((Num) expr).getValue();
}
if (expr instanceof Sum) {
Sum sum = (Sum) expr;
return eval(sum.getLeft()) + eval(sum.getRight());
}
throw new IllegalArgumentException("Unknown expression");
}

使用智能转换和when语句来改写上面的Java代码。

CODE:

fun eval(expr: Expr): Int =
when (expr) {
is Num -> expr.value
is Sum -> eval(expr.left) + eval(expr.right)
else -> throw IllegalArgumentException("Unknown expression")
} interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

涉及的语法:

  • 类型的检查与转换

    1.is 和 !is 操作符

    我们可以在运行时通过使用 is 操作符或其否定形式 !is 来检查对象是否符合给定类型:

    if (obj is String) {
    print(obj.length)
    } if (obj !is String) { // 与 !(obj is String) 相同
    print("Not a String")
    }
    else {
    print(obj.length)
    }

    2.智能转换

    在许多情况下,不需要在 Kotlin 中使用显式转换操作符,因为编译器跟踪不可变值的 is-检查,并在需要时自动插入(安全的)转换:

    fun demo(x: Any) {
    if (x is String) {
    print(x.length) // x 自动转换为字符串
    }
    }

    编译器足够聪明,能够知道如果反向检查导致返回那么该转换是安全的:

        if (x !is String) return
    print(x.length) // x 自动转换为字符串

    或者在 && 和 || 的右侧:

        // `||` 右侧的 x 自动转换为字符串
    if (x !is String || x.length == 0) return // `&&` 右侧的 x 自动转换为字符串
    if (x is String && x.length > 0) {
    print(x.length) // x 自动转换为字符串
    }

    这些 智能转换 用于 when-表达式 和 while-循环 也一样:

    when (x) {
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
    }

    请注意,当编译器不能保证变量在检查和使用之间不可改变时,智能转换不能用。 更具体地,智能转换能否适用根据以下规则:

    val 局部变量——总是可以;

    val 属性——如果属性是 private 或 internal,或者该检查在声明属性的同一模块中执行。智能转换不适用于 open 的属性或者具有自定义 getter 的属性;

    var 局部变量——如果变量在检查和使用之间没有修改、并且没有在会修改它的 lambda 中捕获;

    var 属性——决不可能(因为该变量可以随时被其他代码修改)。

  • When 表达式

    when 取代了类 C 语言的 switch 操作符。其最简单的形式如下:

    when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // 注意这个块
    print("x is neither 1 nor 2")
    }
    }

    when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。 when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式, 符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。(像 if 一样,每一个分支可以是一个代码块,它的值是块中最后的表达式的值。)

    如果其他分支都不满足条件将会求值 else 分支。 如果 when 作为一个表达式使用,则必须有 else 分支, 除非编译器能够检测出所有的可能情况都已经覆盖了。

    如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:

    when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
    }

    我们可以用任意表达式(而不只是常量)作为分支条件

    when (x) {
    parseInt(s) -> print("s encodes x")
    else -> print("s does not encode x")
    }

    我们也可以检测一个值在(in)或者不在(!in)一个区间或者集合中:

    when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
    }

    另一种可能性是检测一个值是(is)或者不是(!is)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法和属性而无需任何额外的检测。

    fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
    }

    when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:

    when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
    }

10.Extension functions

Read about extension functions. Then implement extension functions Int.r() and Pair.r() and make them convert Int and Pair to RationalNumber.

TODO:

fun Int.r(): RationalNumber = TODO()
fun Pair<Int, Int>.r(): RationalNumber = TODO() data class RationalNumber(val numerator: Int, val denominator: Int)

完善扩展函数Int.r()Pair.r()来使得他们能够把Int和Pair转换成RationalNumber(有理数)。

这个有理数类有两个参数,一个是分子,一个是分母。那只要传参返回这个实例就好了。

CODE:

fun Int.r(): RationalNumber = RationalNumber(this,1)

fun Pair<Int, Int>.r(): RationalNumber = RationalNumber(this.first, this.second)
//或可以去掉this,更简单的写为RationalNumber(first, second) data class RationalNumber(val numerator: Int, val denominator: Int)

涉及的语法:

  • 扩展函数

    声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。 下面代码为 MutableList<Int> 添加一个swap 函数:

    fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // “this”对应该列表
    this[index1] = this[index2]
    this[index2] = tmp
    }

    这个 this 关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象) 现在,我们对任意 MutableList<Int> 调用该函数了:

    val l = mutableListOf(1, 2, 3)
    l.swap(0, 2) // “swap()”内部的“this”得到“l”的值

    当然,这个函数对任何 MutableList<T> 起作用,我们可以泛化它:

    fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // “this”对应该列表
    this[index1] = this[index2]
    this[index2] = tmp
    }

11.Object expressions

Read about object expressions that play the same role in Kotlin as anonymous classes in Java.

Add an object expression that provides a comparator to sort a list in a descending order using java.util.Collections class. In Kotlin you use Kotlin library extensions instead of java.util.Collections, but this example is still a good demonstration of mixing Kotlin and Java code.

TODO:

import java.util.*

fun getList(): List<Int> {
val arrayList = arrayListOf(1, 5, 2)
Collections.sort(arrayList, object {})
return arrayList
}

对象表达式在kotlin中扮演着java的匿名类一样的角色。

使用java的集合类库来补充对象表达式,以构成一个能实现降序排序的比较器。

CODE:

import java.util.*

fun getList(): List<Int> {
val arrayList = arrayListOf(1, 5, 2)
Collections.sort(arrayList, object : Comparator<Int> {
override fun compare(x: Int, y: Int) = y-x;
})
return arrayList
}

涉及的语法:

  • 对象表达式

    任何时候,如果我们只需要“一个对象而已”,并不需要特殊超类型,那么我们可以简单地写:

    fun foo() {
    val adHoc = object {
    var x: Int = 0
    var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
    }

    请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any。在匿名对象中添加的成员将无法访问。

    class C {
    // 私有函数,所以其返回类型是匿名对象类型
    private fun foo() = object {
    val x: String = "x"
    } // 公有函数,所以其返回类型是 Any
    fun publicFoo() = object {
    val x: String = "x"
    } fun bar() {
    val x1 = foo().x // 没问题
    val x2 = publicFoo().x // 错误:未能解析的引用“x”
    }
    }

    就像 Java 匿名内部类一样,对象表达式中的代码可以访问来自包含它的作用域的变量。 (与 Java 不同的是,这不仅限于 final 变量。)

    fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0 window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
    clickCount++
    } override fun mouseEntered(e: MouseEvent) {
    enterCount++
    }
    })
    // ……
    }
  • 对象声明

    单例模式是一种非常有用的模式,而 Kotlin(继 Scala 之后)使单例声明变得很容易:

    object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
    // ……
    } val allDataProviders: Collection<DataProvider>
    get() = // ……
    }

    这称为对象声明。并且它总是在 object 关键字后跟一个名称。 就像变量声明一样,对象声明不是一个表达式,不能用在赋值语句的右边。

    要引用该对象,我们直接使用其名称即可:

    DataProviderManager.registerDataProvider(……)
  • Java中Comparator的用法

  • Java中关于comparator接口和它的方法compare()

    1.Comparator接口

    针对一些本身没有比较能力的对象(数组)为它们实现比较的功能,所以它叫做比较器,是一个外部的东西,通过它定义比较的方式,再传到Collection.sort()和Arrays.sort()中对目标排序,而且通过自身的方法compare()定义比较的内容和结果的升降序;

    2.compare()方法

    升序:
    public int compare(Object a,Object b){
    return a.val-b.val;
    } 降序:
    public int compare(Object a,Object b){
    return b.val-a.val;
    }

12.SAM conversions

When an object implements a SAM interface (one with a Single Abstract Method), you can pass a lambda instead. Read more about SAM-conversions.

In the previous example change an object expression to a lambda.

TODO:

import java.util.*

fun getList(): List<Int> {
val arrayList = arrayListOf(1, 5, 2)
Collections.sort(arrayList, { x, y -> TODO() })
return arrayList
}

当一个对象调用一个SAM接口的时候,可以用传一个lambda表达式来代替。

CODE:

import java.util.*

fun getList(): List<Int> {
val arrayList = arrayListOf(1, 5, 2)
Collections.sort(arrayList, { x, y -> y-x })
return arrayList
}

涉及的语法:

  • SAM 转换

    就像 Java 8 一样,Kotlin 支持 SAM 转换。这意味着 Kotlin 函数字面值可以被自动的转换成只有一个非默认方法的 Java 接口的实现,只要这个方法的参数类型能够与这个 Kotlin 函数的参数类型相匹配。

    你可以这样创建 SAM 接口的实例:

    val runnable = Runnable { println("This runs in a runnable") }

    以及在方法调用中:

    val executor = ThreadPoolExecutor()
    // Java 签名:void execute(Runnable command)
    executor.execute { println("This runs in a thread pool") }

    如果 Java 类有多个接受函数式接口的方法,那么可以通过使用将 lambda 表达式转换为特定的 SAM 类型的适配器函数来选择需要调用的方法。这些适配器函数也会按需由编译器生成。

    executor.execute(Runnable { println("This runs in a thread pool") })

    请注意,SAM 转换只适用于接口,而不适用于抽象类,即使这些抽象类也只有一个抽象方法。

    还要注意,此功能只适用于 Java 互操作;因为 Kotlin 具有合适的函数类型,所以不需要将函数自动转换为 Kotlin 接口的实现,因此不受支持。

13.Extension functions on collections

Kotlin code can be easily mixed with Java code. Thus in Kotlin we don't introduce our own collections, but use standard Java ones (slightly improved). Read about read-only and mutable views on Java collections.

In Kotlin standard library there are lots of extension functions that make the work with collections more convenient. Rewrite the previous example once more using an extension function sortedDescending.

TODO:

fun getList(): List<Int> {
return arrayListOf(1, 5, 2)//TODO("return the list sorted in descending order")
}

CODE:

fun getList(): List<Int> {
return arrayListOf(1, 5, 2).sortedDescending()
}

Conventions

1.Comparison

Read about operator overloading to learn how different conventions for operations like ==, <, + work in Kotlin. Add the function compareTo to the class MyDate to make it comparable. After that the code below date1 < date2 will start to compile.

In Kotlin when you override a member, the modifier override is mandatory.

TODO:

data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable<MyDate> {

}

fun compare(date1: MyDate, date2: MyDate) = date1 < date2

要求是在MyDate类中添加compareTo函数以使得类对象可比较。

在Kotlin中如要重载成员,必须加上override关键字。

关于compareTo(),在Java中的用法是:

public int compareTo(Object o){
return this.val-o.val; //假定比较的是val属性,得到升序排序
}

CODE:

data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable<MyDate> {
override fun compareTo(other: MyDate) = when {
year != other.year -> year - other.year
month != other.month -> month - other.month
else -> dayOfMonth - other.dayOfMonth }
}
上一篇:初识Kotlin之集合


下一篇:Kotlin 在kotlin内使用Java的一些注意(长篇)