赞
踩
如果你没有读过上篇,建议直接过去阅读,不要往下看啦,上篇有很多有意思的题:
鱿鱼游戏来了,现在开始,看看你闯过第几关。
在不借助IDE的情况下,看你的人肉编译器能否编译出正确的结果。
Sorting
val list = arrayListOf(1, 5, 3, 2, 4)
val sortedList = list.sort()
println(sortedList)
a) [1, 5, 3, 2, 4]
b) [1, 2, 3, 4, 5]
c) kotlin.Unit
d) Will not compile
答案:C
这道题考察的是Kotlin中sort函数,它有两种:
sort():对一个可变集合进行排序,返回Unit
sorted():排序后返回集合
所以,换成list.sorted()就对了。
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))
a) true; true; true
b) true; true; false
c) true; false; true
d) true; false; false
e) false; false; false
答案:D
集合的相等判断使用的是引用判断,所以两个不同的list,不会相等,sequence也一样,判断的是引用地址。
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))
a) 2,2
b) 1,1
c) 2,1
d) Will not compile
答案:C
这道题的考察点主要是下面几个:
• open functions的多态的,其类型有jvm虚拟机在运行时决定。
• 具名函数是静态的,在编译期就固定了。
open class Parent(open val a: String) {
init { println(a) }
}
class Children(override val a: String): Parent(a)
调用:
Children("abc")
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; } }
❝
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的简化构造函数时,一定要注意属性的覆写。
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()
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上下文中找到它的实现。
print(-1.inc())
print(", ")
print(1 + -(1))
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()。
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())
a) one, two
b) one, two, oops
c) UnsupportedOperationException
d) will not compile
答案:B
data class的copy()方法只做了一个浅层拷贝,即只复制了对字段的引用。如果要实现深拷贝,可以使用不可变data class来避免这个问题。
class Wrapper<out T>
val instanceVariableOne : Wrapper<Nothing> = Wrapper<Any>()//Line A
val instanceVariableTwo : Wrapper<Any> = Wrapper<Nothing>()//Line B
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行不能编译。它把超类型分配给一个子类型。
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()
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的优先级是最低的。
var i = 0
println(i.inc())
println(i.inc())
var j = 0
println(j++)
println(++j)
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返回了一个新值,但是未被赋值。
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()
a) 134134
b) 1134
c) 1341
d) Doesn’t compile
答案:B
当我们想在lambda表达式中使用return时,我们需要使用return@forEach这样的标签,否则它会跳出整个lambda。
而因为for-each是内联函数,所以在f2中,实际上使用了一个匿名函数,这里return就可以退出函数,而不是lambda。
val j = wtf@ { n: Int -> wtf@ (wtf@ n + wtf@ 2) }(10)
println(j)
a) It won’t compile
b) 10
c) 2
d) 12
答案:D
标签在这里毫无作用,不要被他迷惑了。
val x: Int? = 2
val y: Int = 3
val sum = x?:0 + y
println(sum)
a) 3
b) 5
c) 2
d) 0
答案:C
Elvis operator的优先级比+低,所以加号先被执行,就变成了x?:3,答案是2,可以通过加括号的方式(x3:0)来改变优先级。
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"))
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(...)
这又是一个骚操作。
fun hello(block: String.() -> Unit) {
"Hello1".block()
block("Hello2")
}
调用:
hello { println(this) }
a) Hello1
b) Hello2
c) Hello1Hello2
d) will not compile
答案:C
这道题的重点是分清楚哪个是lambda,哪个是带接收器的拓展函数。
data class IAm(var foo: String) {
fun hello() = foo.apply {
return this
}
}
调用:
println(IAm("bar").hello())
a) IAm
b) IAm(foo=bar)
c) bar
d) Will not compile
答案:C
不要被迷惑了,这就是一段废代码。
operator fun String.invoke(x: () -> String) = this + x()
fun String.z() = "!$this"
fun String.toString() = "$this!"
调用:
println("x"{"y"}.z())
a) !x
b) !xy
c) !xy!
d) Will not compile
答案:B
这道题重点是理清"x"{“y”}.z(),去掉z(),实际上就是重载的invoke函数,所以等价于String{},{}就是invoke的参数。
又是一个骚操作,可以在对象初始化的时候进行其它初始化操作。
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()
a) 0
b) 1
c) NaN
d) ArithmeticException
答案:B
Lazy delegate可以被多次调用,直到它真正返回一个值为止,所以抛出异常后,x的值修改了,y可以被赋值,从而print出来。
fun numbers(list: List<Int>) {
list.forEach {
if (it > 2) return
println(it)
}
println("ok")
}
调用:
numbers(listOf(1, 2, 3))
a) 123ok
b) 12ok
c) 12
d) Infinite loop
答案:C
lambda中的return,会直接从函数中返回,所以函数中断了。
typealias L = (String) -> Unit
fun foo(one: L = {}, two: L = {}) {
one("one")
two("two")
}
调用:
foo { println(it) }
foo({ println(it) })
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,这次鱿鱼游戏,你活到最后了吗?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。