网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
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的简化构造函数时,一定要注意属性的覆写。
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()
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))
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())
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
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()
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)
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()
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)
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)
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"))
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(...)
这又是一个骚操作。
Hello blocks
fun hello(block: String.() -> Unit) {
"Hello1".block()
block("Hello2")
}
调用:
hello { println(this) }
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())
a) IAm
b) IAm(foo=bar)
c) bar
d) Will not compile
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
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
[外链图片转存中...(img-oRV08UcN-1715749095565)]
[外链图片转存中...(img-6aiSw7hv-1715749095565)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618636735)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**