全面了解Kotlin,2021大厂Android高级面试题及答案

val str : String = “test”

全面了解Kotlin,2021大厂Android高级面试题及答案_第1张图片 根据上图,我们需要注意两点:

2.1.val等于final

我们可以看到var定义的num可以被重新赋值,str却不可以。上图的val实际上就等于Java中的final String,也就是val定义的变量默认添加了final关键字。

2.2.可空?以及空匹配

第二点就是num变量在定义为Int的时候是不能赋值为Null的,如果需要我们需要这么定义

var num : Int?

2.3.类型推断

val str = “”

kotlin具有类型推断功能,上面的语句等于Java

final String str = “”

自定义getter&setter

var msg: String
get() = field
set(value) {
field = value
}

3.函数

3.1.定义

3.1.1普通函数

定义一个名为test函数,返回值为String?,可能返回为空

fun test(): String? {
return null
}

调用:

调用和Java类似,由于test返回的是可空的字符串,添加?:表示当前面为空取冒号后面的值。

val result = test() ?: “x”

也可以

fun isEmpty(str: String) = str.isEmpty()

其中isEmpty函数的返回值为后面isEmpty()的返回值。

3.1.2默认参数

kotlin支持带默认参数的函数,默认参数不传则为默认值。

data class EnvConfig(val baseUrl: String, val isDebug: Boolean = false)

//构造1 等于 EnvConfig(“https://xx.com”,false)
val env1 = EnvConfig(“https://xx.com”)

3.1.3命名参数

kotlin方法调用可以指明参数名称,以避免混淆。更加直观。

EnvConfig(
baseUrl = “https://xx2.com”,
isDebug = true
)

3.2顶层函数和属性

kotlin可以定义全局可以调用的工具函数,它会编译成该文件的静态方法以供调用。

TopFunc.kt

fun toString(obj:Any) = obj.toString()

翻译成Java类

public final class TopFuncKt {
@NotNull
public static final String toString(@NotNull Object obj) {
Intrinsics.checkParameterIsNotNull(obj, “obj”);
return obj.toString();
}
}

同理顶层属性

var count = 0

Java

public static final int getCount() {
return count;
}

public static final void setCount(int var0) {
count = var0;
}

3.3 给别人的类添加方法: 拓展函数和属性

3.3.1拓展函数

拓展函数非常简单,它就是一个类的成员函数。

TopFunc.kt

//定义一个成员函数 方法内的this会指向被拓展的对象。
//即这里的this是这个字符串
fun String.print() = println(this)

//使用
“string extension”.print()

//输出
string extension

拓展函数也是顶层函数,所以它在Java中也是静态函数,调用如下:

TopFuncKt.print(“extension in Java”);

拓展函数仅仅是为了缩短语法而存在。并非真正意义上的"拓展",也做不到真正的拓展,所以拓展函数无法进行重写,或者在Java中当作成员函数来调用。

3.3.2拓展属性

类似拓展函数,拓展属性提供了一种方法,用来拓展类的API,可以用来访问属性,用的是属性语法而不是函数的语法。**尽管它们被称为属性,但他们可以没有任何状态,也没有合适的地方来存储它们,**不能给现有的Java对象的实例添加额外的字段。

var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char) {
setCharAt(length - 1, value)
}

**上面的代码只是利用拓展属性提供了一种快捷访问该类成员方法的途径,但是并没有给StringBuilder这个类添加lastChar这个属性。**尽管如此,拓展属性依旧十分实用,比如Android中常用的Float转换为dp:

val Float.dp
get() = get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics)

3.4 可变参数和中缀调用

vararg:可变参数。只需要注意一点

fun test(vararg string: String){
test2(string)//这里传递可变参数需要添加
}

fun test2(vararg string: String){

}

中缀调用:

//允许使用中缀符号调用函数,函数会返回一个Pair对象
infix fun Any.with(other:Any) = Pair(this,other)

val c = “3” with 4 // c为pair对象
val (f, s) = “3” with 4 //val (f,s)称为解构声明,将Pair的first给f,second给s.
println(“f: f , s : f,s: f,s:s”)

//输出结果
f:3,s:4

4.程序逻辑控制

4.1带返回值的if

kotlin没有三目运算符,取而代之的是带返回值的if语句。而且是把每一个条件中的最后一行代码作为返回值

//Kotlin 中把每一个条件中的最后一行代码作为返回值
fun largeNumber(number1: Int,number2: Int) : Int{
return if(number1 > number2){
number1
}else {
number2
}
}

//根据上面学习的语法糖和 Kotlin 类型推导机制,我们还可以简写 largeNumber 函数,最终变成了这样
fun largeNumber(number1: Int,number2: Int) = if(number1 > number2) number1 else number 2

4.2带返回值的when

类比 Java 中的 Switch 语句学习,Java 中的 Switch 并不怎么好用:

①Switch 语句只能支持一些特定的类型,如整型,短于整型,字符串,枚举类型。如果我们使用的并非这几种类型,Switch 并不可用

②Switch 语句的 case 条件都要在最后加上一个 break

这些问题在 Kotlin 中都得到了解决,而且 Kotlin 还加入了许多强大的新特性:

when 条件语句也是有返回值的,和 if 条件语句类似,条件中的最后一行代码作为返回值

when 条件语句允许传入任意类型的参数

when 条件体中条件格式:匹配值 -> { 执行逻辑 }

⑥when 条件语句和 if 条件语句一样,当条件体里面只有一行代码的时候,条件体的 {} 可省略

//when 中有参数的情况
fun getScore(name: String) = when (name) {
“tom” -> 99
“jim” -> 80
“lucy” -> 70
else -> 0
}

//when 中无参数的情况,Kotin 中判断字符串或者对象是否相等,直接使用 == 操作符即可
fun getScore(name: String) = when {
name == “tom” -> 99
name == “jim” -> 80
name ==“lucy” -> 70
else -> 0
}

4.3循环语句

kotlin中主要有两种循环语句:while和for-in。其中while和Java中使用一致。

而kotlin中的for-in则比Java中更为方便易用。

for-in:

//使用 … 表示创建两端都是闭区间的升序区间[0,10]
for (i in 0…10){
print("$i ")
}
//打印结果
0 1 2 3 4 5 6 7 8 9 10

for-until:

//使用 until 表示创建左端是闭区间右端是开区间的升序区间[0,10)
for (i in 0 until 10){
print("$i ")
}
//打印结果
0 1 2 3 4 5 6 7 8 9

for-downTo:

//使用 downTo 表示创建两端都是闭区间的降序区间[10,0]
for (i in 10 downTo 0){
print("$i ")
}
//打印结果
10 9 8 7 6 5 4 3 2 1 0

步进:

//使用 downTo 表示创建两端都是闭区间的降序区间,每次在跳过3个元素
for (i in 10 downTo 0 step 3){
print("$i ")
}

迭代list

//使用withIndex迭代list
val list = arrayListOf(“10”,“11”,“100”)
for ((index,element) in list.withIndex()){//解构申明
println(“ i n d e x : index: index:element”)
}

//打印结果
0:10
1:11
2:100

迭代map

val map = mapOf(1 to “Java”, 2 to “Kotlin”, 3 to “Flutter”)//中缀调用
for ((key, value) in map) { //解构
println(“ k e y : key: key:value”)
}

//打印结果
1:Java
2:Kotlin
3:Flutter

5.类

和Java一样,类的定义如下

class Base {
var num = 1
}

但是意义却不太一样。

5.1可见性

**Kotlin中,默认类的可见性为public以及final的。**内部类默认为static的,用inner标记非静态内部类。

①Kotlin 中可见性
  • private :私有,本类内部可见
  • protected :子类可见
  • internal :模块内可见
  • public :默认,公有
②对比 Java
  • private :私有,本类内部可见
  • protected :子类可见
  • default :默认,包内可见
  • public :公有

单个构造函数&私有构造

class Response private constructor(val code: Int, val msg: String){
override fun toString(): String {
return “code: c o d e , m s g : code,msg: code,msg:msg”
}
}

多个构造函数

//非open不可被继承
class Response {
val code: Int
val msg: String

constructor(code: Int) {
this.code = code
msg = “”
}

constructor(code: Int, msg: String) {
this.code = code
this.msg = msg
}

override fun toString(): String {
return “code: c o d e , m s g : code,msg: code,msg:msg”
}
}

其中code和msg的getter会自动生成。

kotlin中也是单继承多实现,共同存在时,继承类写到第一位,后面追加逗号跟上接口接口。

public class String : Comparable, CharSequence{

}

密封类(private构造,默认open)

sealed class Expr {

class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()

}

5.2Object关键字

①类单例

object SingleTon {

@JvmStatic
fun isEmpty(str: String) = str.isEmpty()
}

反编译成Java

public final class SingleTon {
public static final SingleTon INSTANCE;

@JvmStatic
public static final boolean isEmpty(@NotNull String str){
Intrinsics.checkParameterIsNotNull(str, “str”);
CharSequence var1 = (CharSequence)str;
boolean var2 = false;
return var1.length() == 0;
}

private SingleTon() {
}

static {
SingleTon var0 = new SingleTon();
INSTANCE = var0;
}
}

根据面可以看出,object单独修饰一个类,表示为静态代码块实现的单例

@JvmStatic修饰的方法,kotlin会在其方法上添加static关键字,而static关键字在kotlin中是不存在的。

②匿名类

test(object : Listener {

})
interface Listener{

}

kotlin中没有new关键字,所以匿名类用object实现。

③伴生对象

kotlin没有static,那么如何实现静态变量以及常量等的定义和使用呢?

答案是伴生对象

class UserManager {

companion object{
val USER_TYPE = 0x01
}
}

上面的companion object会生成一个内部类Companion,并添加返回USER_TYPE的静态getter,如下

public final class UserManager {
private static final int USER_TYPE = 1;
public static final UserManager.Companion Companion = new UserManager.Companion((DefaultConstructorMarker)null);

public static final class Companion {
public final int getUSER_TYPE() {
return UserManager.USER_TYPE;
}

}
}

PS:const关键字

const关键字只能用在静态类中, 只能与val连用,即const val,而且只能修饰基本类型。意义为编译期常量,在用到的地方替换为该常量的值。如下:

object SingleTon {
const val str = “const”
}

fun test(): String? {
return SingleTon.str
}

其中全面了解Kotlin,2021大厂Android高级面试题及答案_第2张图片
test反编译Java如下:

public final String test() {
return “const”;
}

可以看到kotlin对const常量做了内联

5.3类委托

equals

在Java中,可以使用来比较基本数据类型和引用类型,基本数据类型比较的是值,引用类型上比较的是引用。在kotlin中就等于调用Java中的equals。如果需要比较引用则需要用===。

by关键字

装饰器模式的代码通常就较为模板,kotlin中可以利用by关键字来实现类的委托。比如:

class MyList : List by ArrayList() {
//这里面默认利用ArrayList实现了List的所有接口
}

转换成Java:

public final class MyList implements List, KMappedMarker {
// $FF: synthetic field
private final ArrayList KaTeX parse error: Expected '}', got 'EOF' at end of input: … { return this.delegate_0.get(index);
}

}

当然,我们也可以通过重写来实现自己的逻辑。

by也可以用来实现延迟加载:

private val arr by lazy { MyList() }

它的实现是double-check的懒加载方式,如下:

private class SynchronizedLazyImpl(initializer: () -> T, lock: Any? = null) : Lazy, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this

override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress(“UNCHECKED_CAST”)
return _v1 as T
}

return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress(“UNCHECKED_CAST”) (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}

override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

override fun toString(): String = if (isInitialized()) value.toString() else “Lazy value not initialized yet.”

private fun writeReplace(): Any = InitializedLazyImpl(value)
}

6.lambda

6.1语法

lambda表达式,本质上就是可以传递给其他函数的一小段代码。

kotlin中简单lambda:

button.setOnclickListener{

}

kotlin中lambda始终被花括号{}包围。可以把lambda表达式存储在变量中:

val sum = { x:Int,y:Int -> x + y }
println(sum(1,2))

3

kotlin中,lambda作为最后一个参数可以把它放到()后面如下1;如果只有lambda作为参数,可以省略(),如下2。

list.maxBy({it.length})
list.maxBy(){it.length}//1
list.maxBy{it.length}//2

6.2集合函数API

比如list过滤:

val list = arrayListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.filter { it % 2 == 0 }
.forEach { print(it) }

//打印结果
246810

类似的还有:

all:全部匹配返回true

any:存在一个匹配返回true

count:返回匹配的数量

firstOrNull:第一个匹配,没有返回null

first:第一个匹配,没有抛出异常

find:找到第一个匹配 等于 firstOrNull

还有map,flatmap,groupby…

基本涵盖了RxJava的常用操作符。

apply,let,also…

apply改变this指向调用者。方便各种操作,返回调用者

val str = “123456”
val rec = str.apply {
println(“lastChar:KaTeX parse error: Expected 'EOF', got '}' at position 20: …(lastIndex)}") }̲ println("rec:rec”)

//打印结果
lastChar:6
rec:123456

with改变this指向参数,返回lambda最后一行结果

let创建局部变量,返回最后一行结果。

val str :String ? = “123456”
val res = str?.let {
println(“it:KaTeX parse error: Expected 'EOF', got '}' at position 15: it") "return" }̲ println("res:res”)

//打印结果
it:123456
res:return

also:创建it,返回调用者

run:改变this指向调用者,返回最后一行

类似的语法糖takeIf,repeat等等,都在Standard.kt中有定义。

二、深入理解

1.kotlin类型系统

1.1可空性

kotlin中类型定义如果没有添加为可空,当它接受到一个null时,kotlin会在运行时抛出ERROR:Type mismatch的错误。

当一个类规定为可空,可以使用安全调用?.,后面可以跟上Elvis运算符?:。标识在前面?.调用者为null时执行。

val str :String ? = “123456”
str?.get(str.lastIndex) ?: toast(“str is null”) //toast会在str为null时执行

安全转换:as?

is:检查类型,可以自动转型

val obj: Any = “str”
if (obj is String) {
println(“obj:$obj”)//obg自动转型为string
}

//打印结果
obj:str

as:类型转换,as?安全类型转换

val obj: Any = “str”
(obj as? String)?.print()//在obj为String时才会执行后面的语句,print为本地定义的拓展函数

//打印结果
str

!!非空断言

让kotlin在对象为空的时候抛出异常。

val obj: Any? = “str”
obj!!.toString()
obj.hashCode()//不需要再加!!,kotlin编译器自动检查

2.运算符重载

data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
}

val p1 = Point(1,2)
val p2 = Point(5,6)
val p = p1 + p2
println(“p:$p”)//自动调用toString

//打印结果
p:Point(x=6, y=8)

可用于重载的运算符:

表达式 函数名
a * b times
a / b div
a % b mod
a + b plus
a - b minus

PS:位运算也有自己的符号

运算符 操作
shl 带符号左移
shr 带符号右移
ushr 无符号右移
and 按位与
or 按位或
xor 按位异或
inv 按位取反

3.lambda作为形参

无其他参数:

fun exec(func:()->Unit){
func.invoke()
}
exec {
println(“hello world”)
}

带其他参数:

fun exec(msg: String,func:(msg:String)->Unit){
func.invoke(msg)
}
exec(“hello world”) {msg->
println(msg)
}

以上的lambda是作为不能为空的形参。如果为空,需要将其定义用()?包裹。如下:

fun exec(msg: String,func:((msg:String)->Unit)?){
func?.invoke(msg)
}

lambda作为参数传递虽然好,但是其实现传递的还是对象(匿名类),在每一次调用都会创建一个对象,如何避免这部分开销提升性能?

答案是内联函数。

al p = p1 + p2
println(“p:$p”)//自动调用toString

//打印结果
p:Point(x=6, y=8)

可用于重载的运算符:

表达式 函数名
a * b times
a / b div
a % b mod
a + b plus
a - b minus

PS:位运算也有自己的符号

运算符 操作
shl 带符号左移
shr 带符号右移
ushr 无符号右移
and 按位与
or 按位或
xor 按位异或
inv 按位取反

3.lambda作为形参

无其他参数:

fun exec(func:()->Unit){
func.invoke()
}
exec {
println(“hello world”)
}

带其他参数:

fun exec(msg: String,func:(msg:String)->Unit){
func.invoke(msg)
}
exec(“hello world”) {msg->
println(msg)
}

以上的lambda是作为不能为空的形参。如果为空,需要将其定义用()?包裹。如下:

fun exec(msg: String,func:((msg:String)->Unit)?){
func?.invoke(msg)
}

lambda作为参数传递虽然好,但是其实现传递的还是对象(匿名类),在每一次调用都会创建一个对象,如何避免这部分开销提升性能?

答案是内联函数。

你可能感兴趣的:(程序员,架构,移动开发,android)