当前位置:   article > 正文

游戏开始?Kotlin 版本鱿鱼游戏,最后一战_kotlin 游戏

kotlin 游戏

如果你没有读过上篇,建议直接过去阅读,不要往下看啦,上篇有很多有意思的题:

Kotlin 版本鱿鱼游戏,你能闯几关?

鱿鱼游戏来了,现在开始,看看你闯过第几关。

在不借助IDE的情况下,看你的人肉编译器能否编译出正确的结果。

Sorting


val list = arrayListOf(1, 5, 3, 2, 4)  
val sortedList = list.sort()  
println(sortedList)  
  • 1
  • 2
  • 3

a) [1, 5, 3, 2, 4]

b) [1, 2, 3, 4, 5]

c) kotlin.Unit

d) Will not compile

答案:C

这道题考察的是Kotlin中sort函数,它有两种:

  1. sort():对一个可变集合进行排序,返回Unit

  2. sorted():排序后返回集合

所以,换成list.sorted()就对了。

Collection equality

println(listOf(1, 2, 3) == listOf(1, 2, 3))  
println(listOf(1, 2, 3).asSequence() == listOf(1, 2, 3).asSequence())  
println(sequenceOf(1, 2, 3) == sequenceOf(1, 2, 3))  
  • 1
  • 2
  • 3

a) true; true; true

b) true; true; false

c) true; false; true

d) true; false; false

e) false; false; false

答案:D

集合的相等判断使用的是引用判断,所以两个不同的list,不会相等,sequence也一样,判断的是引用地址。

Good child has many names

open class C {  
    open fun sum(x: Int = 1, y: Int = 2): Int = x + y  
}  
  
class D : C() {  
    override fun sum(y: Int, x: Int): Int = super.sum(x, y)  
}  
  
  
调用:  
val d: D = D()  
val c: C = d  
print(c.sum(x = 0))  
print(", ")  
print(d.sum(x = 0))  

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

a) 2,2

b) 1,1

c) 2,1

d) Will not compile

答案:C

这道题的考察点主要是下面几个:

• open functions的多态的,其类型有jvm虚拟机在运行时决定。

• 具名函数是静态的,在编译期就固定了。

Overriding properties that are used in a parent

open class Parent(open val a: String) {  
    init { println(a) }  
}  
  
class Children(override val a: String): Parent(a)  
  
调用:  
Children("abc")  

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

a) abc

b) Unresolved reference: a

c) Nothing, it won’t compile

d) null

答案:D

这个问题是Kotlin implementing的一个比较让人困扰的地方,所以,我们来分析下Kotlin生成的Java代码。

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 Children extends Parent {  
    private final String a;  
  
    public String getA() {  
        return this.a;  
    }      
  
    Children(String a) {  
        super(a);  
        this.a = a;  
    }  
}  
  • 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

As you can see, to get a we use getA method which references a. The only problem is that it is overriten in Child so it actually references a from Child which is not set yet at this point. It is because parent is always initialized first.

可以看见,Parent中的a,在Child中被重写了,所以它实际上引用了Child中的a,而这个a在此时还没有被设置,因为父类总是先被初始化。所以,在使用Kotlin的简化构造函数时,一定要注意属性的覆写。

Child apply

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()}")  
    }  
}  
  
调用:  
Example()  

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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

答案:B

由于createChild返回nullable,所以在child2的apply中,我们收到的context是Node?。我们不能在没有unpack的情况下直接调用lookup。如果我们想这样做,我们应该使用this?.lookup()。由于我们没有这样做,编译器会搜索它可以使用的lookup,并在Example上下文中找到它的实现。

Negative numbers

print(-1.inc())  
print(", ")  
print(1 + -(1))
  • 1
  • 2
  • 3

a) 0, 0

b) Won’t compile in line 4

c) 0, 2

d) -2, 0

答案:D

在这两种情况下,我们在Int类型上使用unaryMinus操作。当你输入-1时,它与1.unaryMinus()相同。这就是为什么1 + -(1)能正确工作。-1.inc()返回-2,因为inc用在了运算符之前。这个表达式等同于1.inc().unaryMinus()。为了解决这个问题,你应该使用小括号(-1).inc()。

Copy

data class Container(val list: MutableList<String>)  
  
val list = mutableListOf("one", "two")  
val c1 = Container(list)  
val c2 = c1.copy()  
list += "oops"  
  
println(c2.list.joinToString())  

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

a) one, two

b) one, two, oops

c) UnsupportedOperationException

d) will not compile

答案:B

data class的copy()方法只做了一个浅层拷贝,即只复制了对字段的引用。如果要实现深拷贝,可以使用不可变data class来避免这个问题。

Covariance

class Wrapper<out T>  
  
val instanceVariableOne : Wrapper<Nothing> = Wrapper<Any>()//Line A  
val instanceVariableTwo : Wrapper<Any> = Wrapper<Nothing>()//Line B
  • 1
  • 2
  • 3
  • 4

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

答案:D

这道题考察的是kotlin的协变,Wrapper是Wrapper的一个子类型,因为Nothing是Any的一个子类型。Wrapper的子类型与T的子类型相同。B行是好的。A行不能编译。它把超类型分配给一个子类型。

Receivers wars

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()  
    }  
}  
  
调用:  
Test().testFoo()  

  • 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

a) Top-level rule

b) Extension receiver rule

c) Dispatch receiver rule

d) Member extension function rule

答案:B

当我们有一个extension receiver (Foo)时,它的方法总是比dispatch receiver(同一类中的方法)有更高的优先级。

而当Member extension和extension receiver冲突时,extension receiver一定会被调用,所以Member extension的优先级是最低的。

Int plus-plus

var i = 0  
println(i.inc())  
println(i.inc())  
  
var j = 0  
println(j++)  
println(++j)  

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  

  • 1
  • 2

a) 0, 1, 0, 1

b) 0, 1, 0, 2

c) 1, 1, 0, 2

d) 1, 2, 0, 1

答案:C

这个问题从C++就开始存在了,又想起了谭浩强的支配。前缀运算符++(++j)增加数字并返回新值,后缀运算符也增加属性,但返回前值。

但会令人疑惑的部分是,前缀和后缀都是对Kotlin函数inc的引用,你从ide中点击++i和i++,都会跳到inc的引用,inc返回了一个新值,但是未被赋值。

Return in function literal

fun f1() {  
    (1..4).forEach {  
        if (it == 2) return  
        println(it)  
    }  
}  
  
fun f2() {  
    (1..4).forEach(  
        fun(it) {  
            if (it == 2) return  
            println(it)  
        })  
}  
  
调用:  
f1()  
f2()  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

a) 134134

b) 1134

c) 1341

d) Doesn’t compile

答案:B

当我们想在lambda表达式中使用return时,我们需要使用return@forEach这样的标签,否则它会跳出整个lambda。

而因为for-each是内联函数,所以在f2中,实际上使用了一个匿名函数,这里return就可以退出函数,而不是lambda。

WTF with labels

val j = wtf@ { n: Int -> wtf@ (wtf@ n + wtf@ 2) }(10)  
println(j)
  • 1
  • 2

a) It won’t compile

b) 10

c) 2

d) 12

答案:D

标签在这里毫无作用,不要被他迷惑了。

Order of nullable operators

val x: Int? = 2  
val y: Int = 3  
  
val sum = x?:0 + y  
  
println(sum)  

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

a) 3

b) 5

c) 2

d) 0

答案:C

Elvis operator的优先级比+低,所以加号先被执行,就变成了x?:3,答案是2,可以通过加括号的方式(x3:0)来改变优先级。

Extended enums

enum class Color {  
    Red, Green, Blue  
}  
  
fun Color.from(s: String) = when (s) {  
    "#FF0000" -> Color.Red  
    "#00FF00" -> Color.Green  
    "#0000FF" -> Color.Blue  
    else -> null  
}  
  
调用:  
println(Color.from("#00FF00"))  

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

a) Green

b) Color.Green

c) null

d) will not compile

答案:D

对Color的扩展函数只适用于Color的实例,例如,Color.Blue.from(),对枚举本身的扩展函数只有在它有一个Companion object时才能进行。

enum class Color {  
  Red, Green, Blue;  
  companion object   
}  
  
fun Color.Companion.from(...)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这又是一个骚操作。

Hello blocks

fun hello(block: String.() -> Unit) {  
    "Hello1".block()  
    block("Hello2")  
}  
  
调用:  
hello { println(this) }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

a) Hello1

b) Hello2

c) Hello1Hello2

d) will not compile

答案:C

这道题的重点是分清楚哪个是lambda,哪个是带接收器的拓展函数。

I am this

data class IAm(var foo: String) {  
    fun hello() = foo.apply {  
        return this  
    }  
}  
  
调用:  
println(IAm("bar").hello())  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

a) IAm

b) IAm(foo=bar)

c) bar

d) Will not compile

答案:C

不要被迷惑了,这就是一段废代码。

Overextension

operator fun String.invoke(x: () -> String) = this + x()  
  
fun String.z() = "!$this"  
fun String.toString() = "$this!"  
  
调用:  
println("x"{"y"}.z())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

a) !x

b) !xy

c) !xy!

d) Will not compile

答案:B

这道题重点是理清"x"{“y”}.z(),去掉z(),实际上就是重载的invoke函数,所以等价于String{},{}就是invoke的参数。

又是一个骚操作,可以在对象初始化的时候进行其它初始化操作。

Lazy delegate

class Lazy {
    var x = 0
    val y by lazy { 1 / x }

    fun hello() {
        try {
            print(y)
        } catch (e: Exception) {
            x = 1
            print(y)
        }
    }
}

调用:
Lazy().hello()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

a) 0

b) 1

c) NaN

d) ArithmeticException

答案:B

Lazy delegate可以被多次调用,直到它真正返回一个值为止,所以抛出异常后,x的值修改了,y可以被赋值,从而print出来。

Sneaky return

fun numbers(list: List<Int>) {  
    list.forEach {  
        if (it > 2) return  
        println(it)  
    }  
    println("ok")  
}  
  
  
调用:  
numbers(listOf(1, 2, 3))  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

a) 123ok

b) 12ok

c) 12

d) Infinite loop

答案:C

lambda中的return,会直接从函数中返回,所以函数中断了。

Two lambdas

typealias L = (String) -> Unit  
  
fun foo(one: L = {}, two: L = {}) {  
    one("one")  
    two("two")  
}  
  
调用:  
foo { println(it) }  
foo({ println(it) })  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

a) oneone

b) twotwo

c) onetwo

d) none of the above

e) none of the above (twoone)

答案:E

这道题搞清楚了,lambda就算是真的搞清楚了,foo {},代表的是lambda省略()的写法,{}实际上是foo的最后一个参数,而foo(),括号中的内容,实际上是foo中按顺序的第一个参数。

• 这对DSL来说是非常好的,可以通过Kotlin完成各种DSL的写法。

• 但是当与默认参数结合在一起时,可能会引起混淆,不要把许多lambda作为参数,如果你仍然这样做,要避免使用默认值。

案例来自于Puzzlers on Kt. Academy

很多人说,这些玩意儿到底有啥用,很多代码放IDE里面就能知道到底是对是错,运行结果是什么,为什么还要这样去做呢?

实际上,理解这些东西,对你的编程思维和对语言的理解能力会有很大帮助,在IDE里面,它帮助我们做了太多的事,以至于我们很多时候都不能真正发现问题的本质是什么,借助这些题目的训练,我们可以理解编译器是如何处理代码的,可以理解代码是如何执行的,这才是我们训练这些题目的目的。

so,这次鱿鱼游戏,你活到最后了吗?

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/175390
推荐阅读
相关标签
  

闽ICP备14008679号