通过面试题学习Kotlin的知识点(1)
1、如果定义一个int数组,下面哪个正确:
val arrs = IntArray(5)
val arrs = Array<Int>(5)
val arrs : Int[5]
2、哪种方法可以合并下面两个数组a和b到一个list:
val a = arrayOf(1, 2, 3)
val b = arrayOf(100, 200, 300)
val c = a + b
val d = listOf(a + b)
val e = listOf(a, b)
val f = listOf(*a, *b)
3、init blocks和properties在初始化时的执行顺序是哪个:
- 所有的properties按照出现的顺序初始化,然后是init blocks
- 所有的init blocks按照出现的顺序初始化,然后是properties
- init blocks 和 properties按照出现的顺序初始化
4、const和@JvmField都可以创建常量,拿一下是@JvmField可以做但是const做不了的:
class Styles {
companion object {
const val COLOR = "Blue"
@JvmField val SIZE = "Really big"
}
- @JvmField总是被inline使得代码执行更快
- @JvmField跟Java兼容,const不行
- @JvmField可以作为顶层变量,但是const只能在类中使用
- const只能标记原始变量和String,@JvmField没有这种限制
5、Kotlin的接口和抽象类非常像,哪一件事是抽象类可以做但是接口做不了的:
- 只有抽象类可以有抽象方法
- 只有抽象类可以被子类继承
- 只有抽象类可以存储状态
- 只有抽象类可以从多个父类继承
6、fun foo(obj : Any), 怎么获得obj的原始类型,包括package信息:
a. obj.classInfo()
b. obj::class
c. obj.typeInfo()
d. obj::class.simpleName
7、定义一个带有name属性的类,要求延迟初始化,且不能为null。如果要用delegate来做,一个怎么声明:
a. var name: String by Delegates.nonNull()
b. var name: String? = null
c. lateinit var name: String
d. var name: String by lazy
答案
1、第1个是正确的,第2个没有初始化,所以编译失败,需要写成Array<Int>(5) {i} 这样的形式。
Kotlin有一些预先定义的Array类,比如IntArray, LongArray, ByteArray, DoubleArray, 他们都有一个只需一个size参数的constructor,会转换成java的对应类型的数组,比如int[], long[], byte[], double[]。他们的初始值都是对应类型的0值,比如int,long,short都是0,而char的0值是‘\u0000’。
如果要建一个int/long的数组,用IntArray/LongArray是比较方便的,因为初始化的init块是optional的。
上面的数组都不能接受null,如果要传入null,可以使用如下方法:
val arr = arrayOfNulls<Int>(5)
arrayOfNulls方法定义如下:
/**
* Returns an array of objects of the given type with the given [size], initialized with null values.
*/
public fun <reified @PureReifiable T> arrayOfNulls(size: Int): Array<T?>
生成的是Array<Int?>类型
2、正确的是 listOf(*a, *b),把每个数组展开,然后生成一个list
a + b: [Ljava.lang.Integer;@4517d9a3 -> a+b是一个包含各个元素的新数组
listOf(a + b): [[Ljava.lang.Integer;@372f7a8d] -> 生成的是只有一个数组元素的列表
listOf(*a, *b): [1, 2, 3, 100, 200, 300] -> 正确
listOf(a, b): [[Ljava.lang.Integer;@279f2327, [Ljava.lang.Integer;@2ff4acd0] -> 生成的是有两个数组元素的列表
之所以可以用+把两个array放到一个数组里,是因为Array类有一个扩展方法:
/**
* Returns an array containing all elements of the original array and then all elements of the given [elements] array.
*/
public actual operator fun <T> Array<T>.plus(elements: Array<out T>): Array<T> {
val thisSize = size
val arraySize = elements.size
val result = java.util.Arrays.copyOf(this, thisSize + arraySize)
System.arraycopy(elements, 0, result, thisSize, arraySize)
return result
}
3、正确答案是 init blocks 和 properties按照出现的顺序初始化
class Test{
private val a = 1.also { println("init a") }
init {
println("init 1")
}
private val b = Long.MAX_VALUE / 0
init {
println("init 2")
}
}
---output---
init a
init 1
Exception in thread "main" java.lang.ArithmeticException: / by zero
可以看到"init 2"没有输出,"init 1"在抛出异常前输出了。
4、第4个正确,const只能修饰Int/Long等原始类和String,其他的需要用@JVMField修饰。const修饰的常量可以被inline,效率更高,而@JVMField修饰的常量会被转化为public final static的常量,跟Java定义的类似。
我们在开发中一般会把常量定义在一个companion object里,但是编译之后也会自动生成一个单例类实例,所以如果companion object中只有一些常量定义,还是把这些常量放到顶层,不放到任意类中。比如:
Constants.kt类:
const val MAX = 100
const val TYPE = "PAYMENT"
class Foo { }
推荐阅读:where-do-i-put-my-constants-in-kotlin
5、第3个正确,只有抽象类可以存储状态。
一个类只能继承一个抽象类,但是可以实现多个接口,所以选项4错误。抽象类和接口都可以有抽象方法。
抽象类可以定义状态:
abstract class Animal {
var name: String = "Default name"
var type: String = "Default type"
val fullName: String
get() = "$name of type $type"
}
class Lion : Animal()
Interfaces 不能有fields,但是可以有properties
interface Animal {
val name: String
}
--- decompile成Java:---
public interface Animal {
@NotNull
String getName();
}
可以看到只有一个getter方法,所以Animal接口里不能对name指定初始值
推荐阅读:abstract-class-vs-interface-in-kotlin
6、obj::class正确,可以自己试试,考察对获取类信息方法的熟悉度。
下面是Any类的定义:
public open class Any {
public open operator fun equals(other: Any?): Boolean // 正是因为有了这个operator,才可以用==来判断两个对象是否内容相等
public open fun hashCode(): Int
public open fun toString(): String
}
Any类跟java的Object类很像,但是方法却少了几个。可以看到并没有classInfo()和typeInfo()方法。
7、c选项[lateinit var name: String]是正确的。
d选项,name必须是val不变的,而且lazy后需要加一个初始化代码块:
class Lion : Animal {
var name: String by lazy {
"Lion"
}
}
---编译失败---
Type 'Lazy<String>' has no method 'setValue(Lion, KProperty<*>, String)' and thus it cannot serve as a delegate for var (read-write property)
调用lazy方法其实是生成了一个Lazy<String>的实例返回代替name属性,由于Lazy没有setValue的方法,所以不能用于修饰var变量。