Kotlin puzzlers

话说已经好久没写blog了呢,又是失踪人口回归,我一直觉得不能强迫自己为了写博客而写博客,总是要为了记录点什么才行,这次我就刚好见到kotlin academy发的邮件,瞥了一眼觉得很多易错的题目蛮好(鬼畜)的,就忍不住想写了blog记录一下啦。 原文地址:Kotlin Academy

以下题目的答案和解析都下一题揭晓啦(学自夜雀 逃。

Order of nullable operators (猫王操作符的优先级 重要提示

Level:Beginner

1
2
3
4
5
6
7
fun main(args: Array<String>) {
val x: Int? = 2
val y: Int = 3
val sum = x?:0 + y

println(sum)
}

What will it print ? Some possibilities: //这个不用翻译了吧…

a. 3

b. 5

c. 2

d. 0

Author: Thomas Nield


Contravariance (逆变)

Level:Beginner

1
2
3
4
5
class Wrapper<in T>

val instanceVariableOne: Wrapper<Nothing> = Wrapper<Any>() //Line A

val instanceVariableTwo: Wrapper<Any> = Wrapper<Nothing>() //Line B

What does it display? Some possibilities:

a. Both lines A and B compile.

b. Lines A and B do not compile.

c. Line A compiles;Line B does not.

d. Line B compiles;Line A does not.

Author:Allan Caine

上题答案:

c. 2

解析:

Elvis operator(猫王操作符,我脑补),比+具有更低优先级的操作符,所以 +会先被计算,

即 sum = x ?: ( 0 + y) = x ?: 3 = 2, 使用小括号可以正确计算。


Interface delegation and data classes

Level:Expert

1
2
3
4
5
6
7
8
9
10
data class Container(
val name: String,
private val items: List<Int>
) : List<Int> by items

fun main(args: Array<String>) {
val (name, items) = Container("Kotlin", listOf(1, 2, 3))
println("Hello $name, $items")
}

What will it print? Some possibilities:

a. Hello Kotlin,[1, 2, 3]

b. Hello Kotlin, 1

c. Hello 1, 2

d. Hello Kotlin, 2

Author: Nikolas Havrikov

上题答案:

c. Line A compiles;Line B does not.

解析:

Wrapper<in T> 中的T是逆变的。 Wrapper 的类型应该与T的子类型相反,即 super T

因为NothingAny的子类型(注:Nothing 是其他所有类型的子类型),所以Wrapper<Any>Wrapper<Nothing>的子类型。

Line A compiles。他将一个子类型赋值给超类。

Line B does not compile。 他将一个超类赋值给子类。


WTF with labels

Level:Advanced

1
2
3
4
5
6
fun main(args: Array<String>) {
val j = wtf@{ n: Int ->
wtf@(wtf@n + wtf@2)
}(10)
print(j)
}

What does it display? Some possibilities:

a. It won’t compile

b. 10

c. 2

d. 12

Author: Dmitry Kandaloy

上题答案:

d. Hello Kotlin, 2

解析:

private val items使Container.component2() 私有化。

public List<T>.component2()定义在kotlin-stdlib中的扩展函数。

Container用by delegation的方式实现了接口List<Int>,因此调用了上面的扩展函数。

所以,(name, items)解构后第二个参数就是伪代码就是listOf(1, 2, 3).component2()

你可以在JB的issue tracker中找到以下讨论:

https://youtrack.jetbrains.com/issue/KT-24308


Return in Function literal

Level:Beginner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun f1() {
(1..4).forEach{
if (it == 2) return
println(it)
}
}
fun f2() {
(1..4).forEach(fun(it) {
if (it == 2) return
println(it)
})
}
fun main(args: Array<String>) {
f1()
f2()
}

What does it display? Some possibilities:

a. 134134

b. 1134

c. 1341

d. Doesn’t compile.

Author: Marcin Moskala

上题答案:

d. 12

解析:

哈哈哈,这就是一个很普通的lambda表达式加上一堆的label啦。别想多


Int plus-plus

Level:Beginner

1
2
3
4
5
6
7
8
9
fun main(args: Array<String>) {
var i = 0
println(i.inc())
println(i.inc())

var j = 0
println(j++)
println(++j)
}

What does it display? Some possibilities:

a. 0, 1, 0, 1

b. 0, 1, 0, 2

c. 1, 1, 0, 2

d. 1, 2, 0, 2

Author: Dmitry Kandalov

上题答案:

b. 1134

解析:

当我们想要在lambda表达式中使用return即从闭包返回的时候,我们需要使用label标签,譬如return@forEach.

因为for-each是内联函数同时允许非局部返回,所以return就会结束f1方法。


Function names

Level:Expert

1
2
3
4
fun ``() {}
fun ` `() {}
fun `everything works.`() {}
fun `1/1/ is ok; 1/0 is an error`(){}

Which functions have acceptable names? Some possibilities:

a. ok; ok; ok; ok

b. error; ok; ok; error;

c. error; ok; error; error;

d. error; error; error; error;

Author: Dmitry Kandalov

上题答案:

c. 1, 1, 0, 2

解析:

前缀++(++j)增加数值并且返回增加后新的值,但是后缀++(i++)虽然同样增加数值但它返回的是未增加老的值

本题带有迷惑的地方是kotlin函数inc()的前缀和后缀.详细请看

递增和递减


Receivers wars

Level:Advanced

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
fun foo() {
println("Top-level rule")
}
class Foo {
fun foo() {
println("Extension receiver rule")
}
}
class Test {
fun foo() {
println("Dispatch receiver rule")
}
fun Foo.foo() {
println("Member extension function rule")
}
fun Foo.test() {
foo()
}
fun testFoo() {
Foo().test()
}
}

fun main(args: Array<String>) {
Test().testFoo()
}

Which does it display? Some possibilities:

a. Top-level rule

b. Extension receiver rule

c. Dispatch receiver rule

d. Member extension function rule

Author: Marcin Moskalatma

上题答案:

c. error; ok; error; error;

解析:

  1. :声明必须要有名字
  2. :Ok
  3. 和 4. : 命名包含非法字符,”,” “;” 和 “/“

Negative numbers

Level:Advanced

1
2
3
4
5
fun main(args: Array<String>) {
println(-1.inc())
println(", ")
println(1 + -(1))
}

Which does it display? Some possibilities:

a. 0, 0

b. Won’t compile in line 4

c. 0, 2

d. -2, 0

Author: Marcin Moskala

上题答案:

b. Extension receiver rule

解析:

当我们有一个扩展接收者(Foo)时,接收者的方法(method)要优先于同class内的其他函数。

不可能会打印“Member extension function rule”,因为当方法(method)和扩展函数(extension function)有冲突时,方法(method)总是胜利。


Child apply

Level:Advanced

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
open class Node(val name: String) {
fun lookup() = "lookup in: $name"
}
class Example: Node("container") {
fun createChild(name: String): Node? = Node(name)
val child1 = createChild("child1")?.apply {
println("child1 ${lookup()}")
}
val child2 = createChild("child2").apply {
println("child2 ${lookup()}")
}
}

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

Which does it display? Some possibilities:

a. child1 lookup in: child1;child2 lookup in : child2

b. child1 lookup in: child1;child2 lookup in : container

c. child1 lookup in: container;child2 lookup in : child2

d. none of the above

Author: Dmitry Kandalov

上题答案:

d. -2, 0

解析:

这两种情况我们都给Int使用了前缀减(unaryMinus())操作符,-11.unaryMinux()是相等的。这就是为什么1+ -(1)是正常的。

-1.inc()返回 -2是因为inc是先使用的操作符,这表达式和1.inc().unaryMinus()是相等的.

可以加括号来解决此问题,(-1).inc()


Subtypes and Generics: List

Level:Advanced

1
2
3
4
5
6
sealed class LinkedList<T>
data class Node<T>(
val payload: T,
var next: LinkedList<T> = EmptyList
) : LinkedList<T>()
object EmptyList : LinkedList<Nothing>()

Will it compile?If not, what do I need to do? Some possibilities:

a. Looks great.This code will compile as is.

b. A sealed class cannot have a type parameter.

c. Write sealed class LinkedList.

d. Write sealed class LinkedList

Author: Allan Caine

上题答案:

b. child1 lookup in: child1;child2 lookup in : container

解析:

createChild 返回一个可空的对象,所以child2apply 的接收者是Node?

我们不能在未解包的情况下直接调用lookup.如果想要调用则应该使用this?.lookup()

因为没有这样做,编辑器会找他能使用的lookup方法,即Example的上下文的lookup。


Copy

Level:Advanced

1
2
3
4
5
6
7
8
data class Container(val list: MutableList<String>)
fun main(args: Array<String>) {
val list = mutableListOf("one", "two")
val c1 = Container(list)
val c2 = c1.copy()
list += "oops"
println(c2.list.joinToString())
}

What does it display? Some possibilities:

a. one, two.

b. one,two,oops

c. UnsupportedOperationException

d. will not compile

Author: Anton Keks

上题答案:

d. Write sealed class LinkedList

解析:

目前所写的代码不能正确编译。Node<T>的属性next不能给EmptyList的默认值。

LinkedList<T>对于泛型T是不变的。而EmptyListLinkedList<Nothing>。无论T的类型是什么LinkedList<Nothing>都是唯一类型。

而只要使LinkedList关于T协变,即使LinkedList<Nothing>是每个LinkedList的子类型,因为Nothing是任意类型的子类型。

密封类可以有类型参数(type parameters). Kotlin object不能有类型参数(type parameters). 所以b错

如果LinkedList是逆变的,LinkedList<Nothing>就变成所有LinkedList的父类型。然而本题要求的是子类型

更多解释可以参考:

https://blog.kotlin-academy.com/kotlins-nothing-its-usefulness-in-generics-5076a6a457f7


Cyclic object construction

Level:Expert

1
2
3
4
5
6
7
8
open class A(val x: Any?)
object B : A(C)
object C : A(B)

fun main(args: Array<String>) {
println(B.x)
println(C.x)
}

What does it print? Some possibilities:

a. null; null

b.C@2de80c; null

c. ExceptionInInitializerError

d. will not compile

Author: Hiroshi Kurokawa

上题答案:

b. one,two,oops

解析:

data class的copy方法是一个浅拷贝(仅复制字段的引用)。使得data class immutable

来避免此类问题。


Overriding properties that are used in a parent

Level:Expert

1
2
3
4
5
6
7
open class Parent(open val a: String) {
init { print(a) }
}
class Children(override val a: String): Parent(a)
fun main(args: Array<String>) {
Children("abc")
}

What will it print? Some possibilities:

a. abc

b.Unresolved reference: a

c.Nothing,it won’t compile

d. null

上题答案:

b.C@2de80c; null

解析:

Singleton对象的初始化顺序是通过尝试按照他们的依赖关系进行拓扑排序来确定的。

在初始化周期的情况下,完整的拓扑排序是不可能的,并且有可能观察循环中涉及的对象的值为null,

如果将此值传递给期望不可为空类型的函数,则可能导致异常

如果A的构造函数取非空的参数,就会抛出异常

B 初始化需要 CC 初始化需要 B。咦,B 还没初始化完成呢,那么哪来的 B 呢,只能是 null 了啊! 取自夜雀博客

参考链接:http://jetbrains.github.io/kotlin-spec/#_singleton_objects

本题答案:

d. null

解析:

这是kotlin的implement已知的最大问题,请看一下java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static class Parent{
private final String a;
public String getA(){
return this.a;
}
Parent(String a) {
super();
this.a = a;
System.out.print(this.getA());
}
}
public static final class B extends Parent {
private final String a;
public String getA() {
return this.a;
}
B(String a) {
super(a);
this.a = a;
}
}

正如你所看到的,使用getA方法来获得a,唯一的问题是a被重写在Child 它实际上是引用Child里的a,那时候还没被赋值,因为parent总是被先初始化。

在kotlin/js 也有相同的问题,结果是undefined


题目太多了,也就不全部摘写完了,想看的同学去这里看,好了写完了溜了。