点击上方蓝字关注我,知识会给你力量
鱿鱼游戏来了,现在开始,看看你闯过第几关。
在不借助IDE的情况下,看你的人肉编译器能否编译出正确的结果。
Scala-like functions
fun?hello()?=?{
????println("Hello,?World")
}
hello()
提示:在IDE里面,lint会提示,Unused return value of a function with lambda expression body
答案:C
要执行这个lambda,需要使用hello()(),或者使用hello().invoke()
这让我想到了Flutter中的一个骚操作:immediately invoked function expression (IIFE),即(){}()
Indent trimming
val?world?=?"multiline?world"
println(
????"""
????????Hello
????????\$world
????""".trimIndent()
)
Hello
$world
Hello
????$world
Hello
\multiline?world
答案:C
在Kotlin中,raw tring是由一个三引号("")定义的,不包含转义,但可以包含换行符和任何其他字符。即使有转义字符\,也不能在那里使用。如果我们一定要展示 string,那么我们需要使用${'$'}来代替。
If-else chaining
fun?printNumberSign(num:?Int)?{
????if?(num?<?0)?{
????????"negative"
????}?else?if?(num?>?0)?{
????????"positive"
????}?else?{
????????"zero"
????}.let?{?Log.d("xys",?it)?}
}
printNumberSign(-2)
Log.d("xys",?",")
printNumberSign(0)
Log.d("xys",?",")
printNumberSign(2)
答案:D
,
zero
,
positive
请记住,在Java编译器处理之后,else if结构实际上只是用一个"单行"if调用else来处理的,也就是说,不管有多少个else if,实际是都会转化为else中的嵌套。在Kotlin中,函数在if-else块之前被解析,所以.let { print(it) }只适用于最后的else if。所以在这种情况下,第一个if语句的结果将不会被使用,函数将立即返回。为了避免这种情况,你可以将整个if ... else ... 包裹在小括号中,然后在其上加上.let。
Lambda runnables
fun?run()?{
????val?run:?()?->?Unit?=?{
????????println("Run?run?run!")
????}
????Runnable?{?run()?}.run()
}
调用:
run()
a) “Run run run!”
b) Doesn’t compile
c) StackOverflowError
d) None of the above
答案:A
这道题实际上是考察的Kotlin局部函数的使用,上面的代码,实际上等价于下面的代码:
val?run1:?()?->?Unit?=?{
????println("Run?run?run!")
}
fun?run()?{
????Runnable?{?run1()?}.run()
}
使用局部函数,可以将逻辑隐藏在函数内部。
Making open abstract
open?class?A?{
????open?fun?a()?{}
}
abstract?class?B:?A()?{
????abstract?override?fun?a()
}
open?class?C:?B()
a) Compiles fine
b) Error: Class ‘C’ is not abstract and does not implement abstract base class member
c) Error: ‘a’ overrides nothing
d) Error: Function ‘a’ must have a body
答案:B
我们可以用抽象的函数来覆写一个open的函数,但这样它还是抽象的,我们需要在所有子类中覆写需要实现的方法,像下面这样的代码就可以执行了。
open?class?A?{
????open?fun?a()?{}
}
abstract?class?B:?A()?{
????abstract?override?fun?a()
}
open?class?C:?B()?{
????override?fun?a()?{}
}
C().a()
List minus list
val?list?=?listOf(1,?2,?3)
println(list?-?1)
println(list?-?listOf(1))
val?ones?=?listOf(1,?1,?1)
println(ones?-?1)
println(ones?-?listOf(1))
选项:
a)?[2,?3][2,?3][1,?1][1,?1]
b)?[2,?3][2,?3][1,?1][]
c)?[1,?3][2,?3][][1,?1]
d)?[2,?3][2,?3][][]
答案:B
这道题实际上就是考察minus函数的实现,在Kotlin中:
Composition
operator?fun?(()?->?Unit).plus(f:?()?->?Unit):?()?->?Unit?=?{
????this()
????f()
}
({?print("Hello,?")?}?+?{?print("World")?})()
a) “Hello, World”
b) Error: Expecting top-level declaration
c) Error: Expression f cannot be invoked as a function
d) Error: Unresolved reference (operator + not defined for this types)
e) Works, but prints nothing
答案:A
操作符重载函数plus的定义是完全正确的。它返回新的函数(使用lambda表达式创建),该函数由两个作为参数的函数组成。当我们添加两个函数时,我们就有了另一个可以调用的函数。当我们调用它时,我们有一个接一个的lambda表达式被调用。
What am I?
val?whatAmI?=?{}()
println(whatAmI)
答案:B
这道题考察的是lambda表达式的基本知识,这个lambda表达式没有返回内容,所以它的类型就是Unit。
Return return
fun?f1():?Int?{
????return?return?42
}
fun?f2()?{
????throw?throw?Exception()
}
f1和f2能执行吗?
a) returns 42; throws exception
b) returns 42; doesn’t compile
c) doesn’t compile; throws exception
d) doesn’t compile; doesn’t compile
答案:A
f1中的第一个return,其实无效,如果在IDE中,就会有Lint提示。
return表达式有返回类型,可以作为表达式使用,在f1中,它也以结果42结束f1的执行。同样地,throw声明类型——Nothing也是一个返回类型,所以两个函数都能编译,但是在f2调用的时候,会以异常结束。
Extensions are resolved statically
open?class?C
class?D?:?C()
fun?C.foo()?=?"c"
fun?D.foo()?=?"d"
fun?printFoo(c:?C)?{
????println(c.foo())
}
调用:
printFoo(D())
a) Doesn’t compile
b) Runtime error
c) c
d) d
答案:C
这个例子考察的是拓展函数的具体实现原理,因为被调用的扩展函数只依赖于参数c的声明类型,也就是C类,所以,它只会调用C类的拓展函数foo。
扩展实际上并不会修改它们所扩展的类。通过定义一个扩展函数,你并没有真实的在一个类中插入新的成员,而只是让新的函数可以在这个类型的变量上用点号来调用,相当于一层Wrapper。
Expression or not
fun?f1()?{
????var?i?=?0
????val?j?=?i?=?42
????println(j)
}
fun?f2()?{
????val?f?=?fun()?=?42
????println(f)
}
fun?f3()?{
????val?c?=?class?C
????println(c)
}
f1、f2、f3的结果分别是什么?
a)
42 () -> kotlin.Int class C
b)
42 () -> kotlin.Int doesn’t compile
c)
doesn’t compile () -> kotlin.Int doesn’t compile
d)
doesn’t compile doesn’t compile doesn’t compile
答案:C
变量初始化和类声明都是Kotlin中的语句,它们没有声明任何返回类型,所以我们不能将这种声明分配给变量,因此不能编译。而在f2中我们实际上是实现了一个匿名函数,所以输出一个函数。
Eager or lazy?
val?x?=?listOf(1,?2,?3).filter?{
????print("$it?")
????it?>=?2
}
print("before?sum?")
println(x.sum())
答案:A
与Java8的Stream API不同,Kotlin中的集合扩展函数是Eager的。如果需要使用Lazy方式,可以使用sequenceOf或asSequence,序列都是使用的惰性初始化。
Map default
val?map?=?mapOf<Any,?Any>().withDefault?{?"default"?}
println(map["1"])
a) default
b) nothing
c) null
d) will not compile*
答案:C
不要被withDefault的字面意义骗了,withDefault只能用于委托属性的场景,所以,不知道的拓展函数,一定要进去看下实现,不能瞎猜。
val?map?=?mutableMapOf<String,?Set<String>>().withDefault?{?mutableSetOf()?}
var?property:?Set<String>?by?map?//?returns?empty?set?by?default
Null empty
val?s:?String??=?null
if?(s?.isEmpty())?println("is?empty")
if?(s.isNullOrEmpty())?println("is?null?or?empty")
答案:D
当s == null时,s?.isEmpty()会返回null,所以,这个表达式的返回类型应该是Boolean?,所以,不能编译通过。可以通过下面的方式进行修改:
val?s:?String??=?null
if?(s?.isEmpty()?==?true)?{
????println("is?empty")
}
if?(s.isNullOrEmpty())?{
????println("is?null?or?empty")
}
List or not
val?x?=?listOf(1,?2,?3)
println(x?is?List<*>)
println(x?is?MutableList<*>)
println(x?is?java.util.List<*>)
a) true false true
b) false false true
c) true true true
d) true false false
答案:C
在Kotlin中,listOf、MutableList、Java ArrayList,返回的都是java.util.List,所以它们的类型是一样的。
Everything is mutable
val?readonly?=?listOf(1,?2,?3)
if?(readonly?is?MutableList)?{
????readonly.add(4)
}
println(readonly)
答案:C
类似listOf、Array.asList()这样的Helper functions它们返回的是java.util.Arrays$ArrayLis,而不是java.util.ArrayList,所以,他们是不能修改的。
Fun with composition
val?increment?=?{?i:?Int?->?i?+?1?}
val?bicrement?=?{?i:?Int?->?i?+?2?}
val?double?=?{?i:?Int?->?i?*?2?}
val?one?=?{?1?}
private?infix?fun?<T,?R>?(()?->?T).then(another:?(T)?->?R):?()?->?R?=?{?another(this())?}
operator?fun?<T,?R1,?R2>?((T)?->?R1).plus(another:?(T)?->?R2)?=?{?x:?T?->?this(x)?to?another(x)?}
调用:
val?equilibrum?=?one?then?double?then?(increment?+?bicrement)
println(equilibrum())
答案:D
这是一个经典的复合函数问题,重载Plus函数返回了一个又两个函数生成的pair,所以,我们都{1}开始,通过then中缀运算符double了,变成了2,在plus中,分别被执行为3和4。
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函数,它有两种:
所以,换成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))
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))
a) 2,2
b) 1,1
c) 2,1
d) Will not compile
答案:C
这道题的考察点主要是下面几个:
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")
答案: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))
答案: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())
答案: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)
答案: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
答案:C
不要被迷惑了,这就是一段废代码。
Overextension
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的参数。
又是一个骚操作,可以在对象初始化的时候进行其它初始化操作。
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()
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))
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)?})
答案:E
这道题搞清楚了,lambda就算是真的搞清楚了,foo {},代表的是lambda省略()的写法,{}实际上是foo的最后一个参数,而foo(),括号中的内容,实际上是foo中按顺序的第一个参数。
?
案例来自于Puzzlers on Kt. Academy
?
大奖
var?reward:?大奖??=?null
很多人说,这些玩意儿到底有啥用,很多代码放IDE里面就能知道到底是对是错,运行结果是什么,为什么还要这样去做呢?
实际上,理解这些东西,对你的编程思维和对语言的理解能力会有很大帮助,在IDE里面,它帮助我们做了太多的事,以至于我们很多时候都不能真正发现问题的本质是什么,借助这些题目的训练,我们可以理解编译器是如何处理代码的,可以理解代码是如何执行的,这才是我们训练这些题目的目的。
so,这次鱿鱼游戏,你活到最后了吗?
向大家推荐下我的网站?https://xuyisheng.top/??点击原文一键直达
专注 Android-Kotlin-Flutter 欢迎大家访问
往期推荐
本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。
< END >
作者:徐宜生
更文不易,点个“三连”支持一下👇