Android中高级进阶开发面试题冲刺合集(六)

2,267 阅读23分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

以下主要针对往期收录的面试题进行一个分类归纳整理,方便大家统一回顾和参考。本篇是第六集~

强调一下:因篇幅问题:文中只放部分内容,全部面试开发文档需要的可在公众号<Android苦做舟>获取。

第一篇面试题在这: Android中高级进阶开发面试题冲刺合集(一)

第二篇面试题在这: Android中高级进阶开发面试题冲刺合集(二)

第三篇面试题在这: Android中高级进阶开发面试题冲刺合集(三)

第四篇面试题在这: Android中高级进阶开发面试题冲刺合集(四)

第五篇面试题在这: Android中高级进阶开发面试题冲刺合集(五)

设计模式

1.请简要谈一谈单例模式?

参考答案:

单例模式,主要用于某些环境下对象的唯一性,分为线程安全和不安全,写法也比较多,但是个人认为只要熟悉主要的几种就行,包括双检查、静态内部类、枚举

2.对于面向对象的六大基本原则了解多少?

参考答案:

  1. 单一职责(Single Responsibility Principle):一个类只做一件事,可读性提高
  2. 里式替换原则( Liskov Substitution Principle):依赖继承和多态,就是能用父类的地方就可以用子类替换,用子类的但不能用父类。
  3. 依赖倒置原则(Dependence Inversion Principle):依赖抽象,就是模块之间的依赖通过抽象发生。
  4. 开闭原则(Open-Close Principle):不管是实体类,模块还是函数都应该遵循对扩展开放对修改关闭。还是要依赖封装和继承
  5. 接口隔离原则(Interface Segregation Principle):一个类对另一个类的依赖应该建立在最小的接口上,如果接口太大,我们需要把它分割成一些更细小的接口,也是为了降低耦合性
  6. 迪米特原则(Law of Demeter ):也称最少知识原则,也就是说一个类应该对自己需要耦合或者调用的类知道的最少,只需知道该方法即可,实现细节不必知道。

3.请列出几种常见的工厂模式并说明它们的用法?

参考答案:

工厂模式 1.简单工厂(杂而不精) 一个工厂方法根据参数的不同创建不同的对象 (多重ifelse) 2.工厂方法(过于专一) 一个工厂负责生产一个具体对象(太多工厂类的使用) 3.抽象工厂(职责分离又不缺灵活) 通过参数选取不同的工厂生产对象

4.说说项目中用到的设计模式和使用场景?

参考答案:

单例模式

常见应用场景:网络请求的工具类、sp存储的工具类、弹窗的工具类等

工厂模式

常见应用场景:activity的基类等

责任链模式

常见应用场景:OKhttp的拦截器封装

观察者模式

常见应用场景:Rxjava的运用

代理模式

常见应用场景:AIDL的使用

建造者模式

常见应用场景:Dialog的创建

详细的说不出来,哈哈 感兴趣的可以看我的博客,最近有整理这一块,六大原则、23设计模式

5.什么是代理模式?如何使用?Android源码中的代理模式?

参考答案:

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。用图表示如下: 个人感觉和装饰者模式很类似。

其它问题可参考:www.jianshu.com/p/f82a03ec5…

6.谈一谈单例模式,建造者模式,工厂模式的使用场景?如何合理选择?

参考答案:

  1. 单例模式:保证全局只有一个实例,如网络请求
  2. 建造者模式:用于需要设置比较多的属性可以用直接链式,如AlerDialog
  3. 工厂模式:用于业务的实体类创建,易于扩展,如BitMapFactory

7.谈谈你对原型模式的理解?

参考答案:

1.定义 用原型对象的实例指定创建对象的种类,并通过拷贝这些原型创建新的对象.

2.使用场景
    (1)类初始化需要消耗比较多的资源,通过原型拷贝可以避免这些消耗
    (2)当new一个对象需要非常繁琐的数据准备等,这时可以使用原型模式
    (3)当一个对象需要提供给其他调用者使用,并且各个调用者都可能修改其值时, 通过原型模式拷贝多个对象供调用者使用,保护性拷贝

Android 源码中例子: Intent ,Intent的查找与匹配

原型模式实质上就是对象拷贝,要注意深拷贝和浅拷贝问题. 还有就是保护性拷贝,就是某个对象对外是只读的,为了防止外部对这个只读对象修改,通常可以通过返回一个对象的拷贝来实现只读的限制.

优点: 原型模式是在内存中的二进制流的拷贝,性能要比new一个对象好的多.减少了约束.

缺点: 直接在内存中拷贝,构造函数是不会执行的

8.请谈谈策略模式原理及其应用场景?

参考答案:

1.定义 定义了一系列算法,并将每一个算法封装起来,而且是它们之间可以相互替换. 策略模式让算法独立于使用它的客户而独立变化.

2.使用场景
    (1)对同一类型问题的多种处理方式,仅仅是具体行为有差别时.
    (2)需要安全封装多种同一类型的操作时.
    (3)出现同一抽象类有多个子类, 而又需要使用if-else或者switch-case来选择具体子类时

Android 源码中例子: 时间插值器,加减速插值器, (内部封装了不同的算法,外部只需调用可实现不同的动态效果)

策略模式主要用来分离算法,在相同的行为抽象下有不同的具体实现策略
​
优点:
    (1)结构清晰明了,使用简单直观
    (2)耦合度相对而言比较低,扩展方便
    (3)操作封装也更为彻底,数据更为安全
​
缺点:
    (1)随着策略的增加,子类也会变得繁多

9.静态代理和动态代理的区别,什么场景使用?

参考答案:

静态代理类:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 动态代理类:在程序运行时,运用反射机制动态创建而成。 静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。 静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。 动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过Proxy里的newProxyInstance得到代理对象。 还有一种动态代理CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。

10.谈一谈责任链模式的使用场景?

参考答案:

责任连模式定义: 将多个对象连成一条链,并沿着这条链传递该请求,只到有对象处理该请求为止。使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。

Android中责任链场景: 1) Android 源码中对于事件分发是基于该模式,Android 会将事件包装成一个事件对象从ViewTree的顶部至上而下的分发传递,只到有View处理该事件为止。 2)OkHttp 的拦截器也是基于责任链模式,用户请求和服务器返回数据,会经过内置拦截器链逐级的处理 。

计算机网络方面

1.请简述 Http 与 Https 的区别?

参考答案:

HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。 1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。 最后一点在Android 9.0 如果用http进行传输,需要在application节点下设置 android:usesCleartextTraffic="true"

2.说一说 HTTPS、UDP、Socket 之间的区别?

参考答案:

TCP(Transmission Control Protocol,传输控制协议)与UDP(User Data Protocol,用户数据协议)是互联网传输数据较为常用的协议,我们熟知的HTTP就是基于TCP的.

而HTTPS就是HTTP 加上SSL的加密方式:

UDP是非面向连接的协议,发送数据时不管对方状态直接发送,无需建立连接,如同微信发送一个消息或者语音信息,对面在不在线无所谓.

Socket不属于协议范畴,别名套接字通过调用Socket,才能使用TCP/IP协议,Socket连接是长连接,理论上客户端和服务器端一旦建立连接将不会主动断开此连接。Socket连接属于请求-响应形式,服务端可主动将消息推送给客户端。

3.请简述一次 HTTP 网络请求的过程?

参考答案:

1.dns解析,域名对应 ip 2.TCP建立连接,三次握手 3.C端向S端发送请求行命令 4.C端发送请求头信息 5.S端应答,发送响应命令 6.S端发送响应头信息 7.S端向C端发送数据,以及消息体 8.S端关闭链接 tcp 四次挥手

4.谈一谈 TCP/IP 三次握手、四次挥手过程?

参考答案:

全在文章里:TCP之三次握手和四次挥手

5.为什么说Http是可靠的数据传输协议?

参考答案:

HTTP是属于应用层的协议,TCP(传输控制协议)和UDP(用户数据报协议)是属于传输层的协议。

我们都知道TCP协议是面向连接的,每次进行连接都要进行三次握手和四次挥手,所以它的连接是可靠的。而HTTP是在TCP上层的协议,所以它也是可靠的。

那为什么TCP可靠?

首先来讲一下网络的分层,因特网协议可以分为五层,分别是:

应用层->传输层->网络互联层->网络访问层->物理层

或许你觉得很抽象,但是通过栗子你就会发现并没有那么复杂。

如访问一个Http请求:http://45.124.252.66:9090/main/

怎么访问到这个网站呢?首先我们需要通过网络,可能是移动网或者宽带网等(这就是物理层,它是一个传输介质),然后找到对应那一台被我们访问的服务器的mac地址(网络访问层)进行连接,再匹配它的IP(网络互联层)是否对应,确定了主机后,再通过端口号9090(传输层)访问对应的进程,由于一个进程里面有很多业务模块,而我们需要访问main模块(应用层),最终通过不同层来实现网站的访问。

每个层都是相互独立,并且向下依赖,而传输层是能确定唯一主机的,因为我们可以通过mac地址、host和端口来确定唯一的一台访问主机上面的进程。或许有的人会问,那如果网络中断呢?那不就不可靠了吗,我们常说的网络中断是属于物理层,由于是向下依赖,传输层的建立是依赖于下面的三层(网络互联层、网络访问层、物理层)已经连接成功,如果下面的层都没有连接成功,也就没有传输层这一说了,所以传输层协议是一个“靠谱”的协议。

我们通过分层了解了传输层是“靠谱”的协议,那么怎么保证它是可靠的呢?

那就要讲到三次握手和四次挥手的作用了。

三次握手就是在建立连接之前需要客户端需要先给服务端发出SYN(c)报文,当服务器收到后需要返回客户端ACK=SYN(c)+1,并且传输自己生成的SYN(s)给客户端,客户端收到后进入已连接状态,需要再回一个ACK=SYN(s)+1给服务器,服务器收到ACK后也进入了连接状态,这就是一个三次握手的过程,通过双方进行三次通信保证此时双方都已经进入准备状态。

四次挥手就是在结束连接的时候,客户端会发送FIN(c)给服务器,服务器收到后回复客户端ACK=FIN(c)+1告知客户端收到客户端的结束请求了,这时客户端就会进入CLOSING(半关闭状态),等待服务器的结束请求。 在一段小延迟时间后,服务器也会发送一个FIN(s)请求给客户端,客户端收到后发送ACK=FIN(s)+1给服务器,服务器收到ACK后就进入技术状态。客户端在等待2个MSL(避免服务器收不到ACK)后也进入结束状态。

在每次进行连接和断开连接都需要经过复杂的三次握手和四次握手,从而保证了每个连接都是可靠的,所以TCP协议是可靠的,而HTTP就是TCP上层的协议,所有连接都是基于TCP协议的。

在我们能够确定每个请求对应的唯一主机和端口号,并且通过Http协议添加响应的请求数据信息(如模块名字等)确定请求的代码位置,并且在每次请求都通过三次握手和四次挥手保证连接的可靠性,所以一个Http请求是可靠的。

6.TCP/IP 协议分为哪几层?TCP 和 HTTP 分别属于哪一层?

参考答案:

四层 应用层 传输层 网络层 数据链路层 http是 应用层 tcp 是传输层 ip是网络层 http 每次请求 需要 三次握手四次挥手 三次握手 第一次 客户端发送seq 确定了 客户端的 发送能力 和服务端的接收能力 第二次 服务端返回 seq 和 ack 客户端确认了自己的发送能力和接收能力 第三次 客户端发送 ack 服务端确定了自己的发送能力 由此进行数据传输 tpc断开时 需要四次挥手 第一次 客户端发送 fin 给服务端,第二次服务端收到 返回ack 等于 甲乙通话中,甲告诉乙我已经说完了,乙说我知道了 然后中间可能还有传输内容 乙还有话对甲说 第三次 服务端发送fin给客户端 第四次 客户端发送ack给服务端 等于 乙告诉甲 我要说的话说完了,甲说知道了, 由此双方挂断电话

tcp是基于连接的 所以相对可靠, udp是直接发送 速度快但是不可靠 tcp可靠基于三次握手和四次挥手,和ack(回执机制) 如果客户端给服务端发送数据后没收到回执,会在一定条件下重复发送, 并且他们在连接过程中中断 又会重新三次握手

http1.1 引入了 keepalive机制 长连接 不必每次请求 都是三次握手四次挥手, 而是在超时时间内利用同一个 连接 http2.0 把基于文本传输改为基于二进制传输 多路复用

https 是在 http的基础上加上ssl 安全套接字 加入了认证加密 增加了一定的安全性,但也不是完全安全.在app中需要将https证书改为严格模式,并且要提前将证书放在客户端,如果放在服务端下证书有可能被人抓走. https 如果不是严格模式 也是可以进行抓包的

7.Post 中请求参数放在了哪个位置?

参考答案:

1,大部分情况,数据,放到body中 2,少部分情况,参数,拼接到url上,也可以

Kotlin 方面

1.请简述一下什么是 Kotlin?它有哪些特性?

参考答案:

kotlin和java一样也是一门jvm语言最后的编译结果都是.class文件,并且可以通过kotlin的.class文件反编译回去java代码,并且封装了许多语法糖,其中我在项目中常用的特性有

1.扩展,(使用非集成的方式 扩张一个类的方法和变量):比方说 px和dp之间的转换 之前可能需要写个Util 现在,通过扩展Float的变量 最后调用的时候仅仅是 123.dp 这样px转成dp了

2.lamdba表达式,函数式编程. lamdba表达式并不是kotlin的专利,java中也有,但是有限制, 像setOnClickListener一样,接口方法只有一个的情况才能调用, 而在kotlin中对接口的lambda也是如此,有这样的限制,但是他更推荐你使用闭包的方式而不是实现匿名接口的方式去实现这样的功能,闭包对lambda没有接口这么多的限制,另外就是函数式编程 在java8中提供了streamApi 对集合进行map sort reduce等等操作,但是对android api有限制,为了兼容低版本,几乎不可能使用streamApi

3.判空语法 省略了许多if xxx==null 的写法 也避免了空指针异常 aaa?.toString ?: "空空如也" 当aaa为空的时候 它的值被"空空如也"替代 aaa?.let{ it.bbb } 当aaa不为空时 执行括号内的方法

4.省略了findViewById ,使用kotlin 就可以直接用xml中定义的id 作为变量获取到这个控件,有了这个 butterknife就可以淘汰了,使用databinding也能做到,但是,非常遗憾,databinding的支持非常不好,每次修改视图,都不能及时生成,经常要rebulid才能生成.

5,默认参数 减少方法重载 fun funName(a :Int ,b:Int = 123) 通过如上写法 实际在java中要定义两个写法 funName(a) 和funName(a,b)

6.kotlin无疑是android将来语言的趋势,我已经使用kotlin一年了,不仅App工程中使用,而且封装的组件库也是用kotlin,另外说明,kotlin会是apk大小在混淆后增加几百k.但对于更舒适的代码来说这又算的了什么呢

2.Kotlin 中注解 @JvmOverloads 的作用?

参考答案:

Kotlin@JvmOverloads注解的作用就是:在有默认参数值的方法中使用@JvmOverloads注解,则Kotlin就会暴露多个重载方法。

@JvmOverloads fun f(a: String, b: Int=0, c:String="abc"){
}
// 相当于Java三个方法 不加这个注解就只能当作第三个方法这唯一一种方法
void f(String a)
void f(String a, int b)
// 加不加注解,都会生成这个方法
void f(String a, int b, String c)

3.Kotlin 中 List 与 MutableList 的区别?

参考答案:

Kotlin中List、Set、Map与Java中的List、Set、Map有一些不同,kotlin中为只读,只能访问集合中的内容,不能进行修改操作。 如过需要进行添加修改操作需使用Mutable(可变的)前缀

4.Kotlin 中实现单例的几种常见方式?

参考答案:

  • 饿汉式:
object StateManagementHelper {
​
    fun init() {
        //do some initialization works
​
    }
}
  • 懒汉式:
class StateManagementHelper private constructor(){
    
    companion object {
        private var instance: StateManagementHelper? = null 
            @Synchronized get() {
            if (field == null)
                field = StateManagementHelper()
            return field
        }
    }
​
    fun init() {
        //do some initialization works
        
    }
}
  • 双重检测:
class StateManagementHelper private constructor(){
​
    companion object {
        val instance: StateManagementHelper 
                by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { StateManagementHelper() }
    }
​
    fun init() {
        //do some initialization works
​
    }
}
  • 静态内部类:
class StateManagementHelper private constructor(){
​
    companion object {
       val INSTANCE = StateHelperHolder.holder
    }
    
    private object StateHelperHolder {
        val holder = StateManagementHelper()
    }
​
    fun init() {
        //do some initialization works
        
    }
}

5.谈谈你对 Kotlin 中的 data 关键字的理解?相比于普通类有哪些特点?

参考答案:

Data Classes

We frequently create classes whose main purpose is to hold data. In such a class some standard functionality and utility functions are often mechanically derivable from the data. In Kotlin, this is called a data class and is marked as data:

data class User(val name: String, val age: Int)

The compiler automatically derives the following members from all properties declared in the primary constructor:

  • equals()/hashCode() pair;
  • toString() of the form "User(name=John, age=42)";
  • componentN() functions corresponding to the properties in their order of declaration;
  • copy() function (see below).

To ensure consistency and meaningful behavior of the generated code, data classes have to fulfill the following requirements:

  • The primary constructor needs to have at least one parameter;
  • All primary constructor parameters need to be marked as val or var;
  • Data classes cannot be abstract, open, sealed or inner;
  • (before 1.1) Data classes may only implement interfaces.

Additionally, the members generation follows these rules with regard to the members inheritance:

  • If there are explicit implementations of equals(), hashCode() or toString() in the data class body or final{: .keyword } implementations in a superclass, then these functions are not generated, and the existing implementations are used;
  • If a supertype has the componentN() functions that are open{: .keyword } and return compatible types, the corresponding functions are generated for the data class and override those of the supertype. If the functions of the supertype cannot be overridden due to incompatible signatures or being final, an error is reported;
  • Deriving a data class from a type that already has a copy(...) function with a matching signature is deprecated in Kotlin 1.2 and is prohibited in Kotlin 1.3.
  • Providing explicit implementations for the componentN() and copy() functions is not allowed.

Since 1.1, data classes may extend other classes (see Sealed classes for examples).

On the JVM, if the generated class needs to have a parameterless constructor, default values for all properties have to be specified (see Constructors).

data class User(val name: String = "", val age: Int = 0)

Properties Declared in the Class Body

Note that the compiler only uses the properties defined inside the primary constructor for the automatically generated functions. To exclude a property from the generated implementations, declare it inside the class body:

data class Person(val name: String) {
    var age: Int = 0
}

Only the property name will be used inside the toString(), equals(), hashCode(), and copy() implementations, and there will only be one component function component1(). While two Person objects can have different ages, they will be treated as equal.

data class Person(val name: String) {
    var age: Int = 0
}
fun main() {
//sampleStart
    val person1 = Person("John")
    val person2 = Person("John")
    person1.age = 10
    person2.age = 20
//sampleEnd
    println("person1 == person2: ${person1 == person2}")
    println("person1 with age ${person1.age}: ${person1}")
    println("person2 with age ${person2.age}: ${person2}")
}

Copying

It's often the case that we need to copy an object altering some of its properties, but keeping the rest unchanged. This is what copy() function is generated for. For the User class above, its implementation would be as follows:

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)     

This allows us to write:

val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

Data Classes and Destructuring Declarations

Component functions generated for data classes enable their use in destructuring declarations:

val jane = User("Jane", 35) 
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"

Standard Data Classes

The standard library provides Pair and Triple. In most cases, though, named data classes are a better design choice, because they make the code more readable by providing meaningful names for properties.

6.什么是委托属性?请简要说说其使用场景和原理?

参考答案:

类委托: class A: Base by BaseImp() 实现一个接口了正好有一个类可以使用

属性委托:委托的类需要实现 getValue和setValue 函数加上operator关键字

委托延迟: by lazy ,lazy内只执行一次,后续只返回结果

委托工厂:主要需要实现ReadWriteProperty 接口

属性监听: Delegates.observable 无条件赋值 和Delegates.vetoable (有条件的赋值)

7.请举例说明 Kotlin 中 with 与 apply 函数的应用场景和区别?

参考答案:

with` 不怎么使用,因为它确实不防空;
经常使用的是 `run` 和 `apply
  1. run 闭包返回结果是闭包的执行结果;apply 返回的是调用者本身。
  2. 使用上的差别:run 更倾向于做一些其他复杂逻辑操作,而 apply 更多的是对调用者自身配置。
  3. 大部分情况下,如果不是对调用者本身进行设置,我会使用 run

8.Kotlin中 Unit 类型的作用以及与Java中 Void 的区别?

参考答案:

1.在java中,必须指定返回类型,即void不能省略,但是在kotlin中,如果返回为unit,可以省略。 2.java中void为一个关键字,但是在kotlin中unit是一个类

9.Kotlin 中 infix 关键字的原理和使用场景?

参考答案:

infix : 中缀函数,主要使用在只有一个参数的成员函数或者扩展函数上,同时比一般函数具有可读性; 使用条件:1、函数必须只有一个参数;2、函数必须是扩展函数或者成员函数;3、必须使用infix修饰;

用扩展函数举个例子:

infix fun Int.add(num:Int):Int{ return this + num }

调用的时候:

val sum = 1 add 1

10.Kotlin中的可见性修饰符有哪些?相比于 Java 有什么区别?

参考答案:

kotlin存在四种可见性修饰符,默认是public。 private、protected、internal、public 1.private、protected、public是和java中的一样的。 2.不同的是java中默认是default修饰符(包可见),而kotlin存在internal修饰符(模块内部可见)。 3.kotlin可以直接在文件顶级声明方法、变量等。其中protected不能用来修饰在文件顶级声明的类、方法、变量等。 构造方法默认是public修饰,可以使用可见性修饰符修饰constructor关键字来改变构造方法的可见性。

11.你觉得 Kotlin 与 Java 混合开发时需要注意哪些问题?

参考答案:

1: Kotlin 默认是非null 类型,java 返回 null,kotlin 需要添加? 表示可为null 2: kotlin 使用!! 时,要确认变量不为null, 不然会直接抛异常

12.在 Kotlin 中,何为解构?该如何使用?

参考答案:

给一个包含N个组件函数(component)的对象分解为替换等于N个变量的功能,而实现这样功能只需要一个表达式就可以了。 例如 有时把一个对象 解构 成很多变量会很方便,例如: val (name, age) = person 这种语法称为 解构声明 。一个解构声明同时创建多个变量。 我们已经声明了两个新变量: name 和 age,并且可以独立使用它们: println(name) println(age) 一个解构声明会被编译成以下代码: val name = person.component1() val age = person.component2()

13.在 Kotlin 中,什么是内联函数?有什么作用?

参考答案:

对比java 函数反复调用时,会有压栈出栈的性能消耗

kotlin优化 内联函数 用来解决 频繁调用某个函数导致的性能消耗

使用 inline标记 内联函数,调用非内联函数会报错,,需要加上noinline标记

noinline,让原本的内联函数形参函数不是内联的,保留原有数据特征

crossinline 非局部返回标记 为了不让lamba表达式直接返回内联函数,所做的标记 相关知识点:我们都知道,kotlin中,如果一个函数中,存在一个lambda表达式,在该lambda中不支持直接通过return退出该函数的,只能通过return@XXXinterface这种方式

reified 具体化泛型 java中,不能直接使用泛型的类型 kotlin可以直接使用泛型的类型

使用内联标记的函数,这个函数的泛型,可以具体化展示,所有 能解决方法重复的问题

14.谈谈Kotlin中的构造方法?有哪些注意事项?

参考答案:

一、概要简述

  1. kotlin中构造函数分为主构造次级构造两类
  2. 使用关键词constructor标记构造函数,部分情况可省略
  3. init关键词用于初始化代码块,注意与构造函数的执行顺序,类成员的初始化顺序
  4. 继承,扩展时候的构造函数调用逻辑
  5. 特殊的类如data classobject/componain objectsealed class等构造函数情况与继承问题
  6. 构造函数中的形参声明情况

二、详细说明

  • 主/次 构造函数

    1. kotlin中任何class(包括object/data class/sealed class)都有一个默认的无参构造函数
    2. 如果显式的声明了构造函数,默认的无参构造函数就失效了。
    3. 主构造函数写在class声明处,可以有访问权限修饰符private,public等,且可以省略constructor关键字。
    4. 若显式的在class内声明了次级构造函数,就需要委托调用主构造函数。
    5. 若在class内显式的声明处所有构造函数(也就是没有了所谓的默认主构造),这时候可以不用依次调用主构造函数。例如继承View实现自定义控件时,三四个构造函数同时显示声明。
  • init初始化代码块

    kotlin中若存在主构造函数,其不能有代码块执行,init起到类似作用,在类初始化时侯执行相关的代码块。

    1. init代码块优先于次级构造函数中的代码块执行。
    2. 即使在类的继承体系中,各自的init也是优先于构造函数执行。
    3. 在主构造函数中,形参加有var/val,那么就变成了成员属性的声明。这些属性声明是早于init代码块的。
  • 特殊类

    1. object/companion object是对象示例,作为单例类或者伴生对象,没有构造函数。
    2. data class要求必须有一个含有至少一个成员属性的主构造函数,其余方面和普通类相同。
    3. sealed class只是声明类似抽象类一般,可以有主构造函数,含参无参以及次级构造等。

15.谈谈 Kotlin 中的 Sequence,为什么它处理集合操作更加高效?

参考答案:

listOf(1, 2, 3, 4)
    .asSequence()
    .map { it * it }
    .find { it > 3 }
// 结果: 4

如图: List 处理数据时, 每一个操作, 都会应用到所有元素, 且 生成新的 List 并继续下一步操作; (感觉是横向的) Sequence 处理数据时, 针对每一个元素, 执行所有操作流, 直接得出单个元素的最终结果; (感觉是纵向的)

结论 Sequence 高效的原因在于:

  • 每一步操作之间不会产生临时数据
  • 由于可以直接得到单个元素的最终结果, 所以减少运算次数, 如上图, map 只进行了 2 次

补充: 也有例外, 当只有一次操作时, 比如 进行 filter or average or sum ...等, List 的效率是要高于 Sequence 的

16.请谈谈 Kotlin 中的 Coroutines,它与线程有什么区别?有哪些优点?

参考答案:

先列出协程几个特点: 1,在单个进程内,多个协程串行执行,只挂起不阻塞 2,协程最终的执行还是在各个线程之中

优点: 1,由于不阻塞线程,异步任务是编译器主动交到线程池中执行。因此,在异步任务执行上,切换和消耗的资源都较少。 2,由于协程是跨多个线程,并且能够保持串行执行;因此,在处理多并发的情况上,能够比锁更轻量级。通过状态量实现

16.Kotlin中该如何安全地处理可空类型?

参考答案:

对于方法传入的参数直接通过if判断,例如:

fun a(tag: String?, type: String) {
    if (tag != null && type != null){
        // do something
    }
}

还有就是

a?.let{}
a?.also{}
a?.run{}
a?.apply{}

然后接着有一个疑问,假如同时判断两个变量,写成:

a?.let{
    b?.let{
        //do something
    }
}

17.说说 Kotlin中 的 Any 与Java中的 Object 有何异同?

参考答案:

同:

  • 都是顶级父类 异:
  • 成员方法不同 Any只声明了toString()、hashCode()和equals()作为成员方法。

我们思考下,为什么 Kotlin 设计了一个 Any ?

当我们需要和 Java 互操作的时候,Kotlin 把 Java 方法参数和返回类型中用到的 Object 类型看作 Any,这个 Any 的设计是 Kotlin 兼容 Java 时的一种权衡设计。

所有 Java 引用类型在 Kotlin 中都表现为平台类型。当在 Kotlin 中处理平台类型的值的时候,它既可以被当做可空类型来处理,也可以被当做非空类型来操作。

试想下,如果所有来自 Java 的值都被看成非空,那么就容易写出比较危险的代码。反之,如果 Java 值都强制当做可空,则会导致大量的 null 检查。综合考量,平台类型是一种折中的设计方案。

18.Kotlin中的数据类型有隐式转换吗?为什么?

参考答案:

kotlin中没有所谓的'基本类型',本质上不存在拆装箱过程,所有的基本类型都可以想象成Java中的包装类型,所以也不存在隐式转换,对应的都是强类型,一旦声明之后,所有转换都是显示转换。

19.分别通过对象表达式 object 和 lambda 表达式实现的函数式接口内部有何不同?

参考答案:

1.object是匿名内部类的形式,匿名内部类是在编译后形成一个class 2.Lambda表达式是在程序运行的时候动态生成class

20.Kotlin 中集合遍历有哪几种方式?

参考答案:

`var mutableList: MutableList = mutableListOf(1, 2, 3, 4) // 方式1 mutableList.forEach { println("Mutable List Elements:$it") }

//方式和2 for (value in mutableList) { print("value:value") } //方式3 for ((index, str) in mutableList.withIndex()) { LogUtils.d("indexindex value:$str") }

21.为什么协程比线程要轻量?

参考答案:

我认为轻量可以从两个方面来说:

  1. 编码复杂度: kotlin 提供了优秀的编译机制, 写法上我就不多说了, 的确很简单就是了, 可以搜索kotlin 协程原理
  2. 性能消耗: 2.1 在不考虑线程池的情况下, 我们基本上是每个任务创建一个线程,,那么这个时候协程肯定是比线程轻量的;因为协程内部是由线程池来执行的 2.2 考虑线程池的情况:没有差异: 协程再某些情况下会因为额外的线程切换导致更多的性能消耗

再说一句: 没有kotlin 协程,没有suspend, 我们也可以实现协程的, 只不过要手写很多复杂的代码就是了

开放性问题

1.你知道哪些提升开发效率的骚操作?

参考答案:

可能我说的不是骚操作,但是的确能提高一些效率

1 搭建自己的(或公司的)maven/git,最好能翻墙,自己平时没事写一些组件,可以放进去,不要求多么的优秀,简单易用即可 2 如果有时间,学会开发 AS 插件,gradle plugin ,一旦完成,能帮省写很多代码,和一些特殊处理 \3. 以前有一个 jrebel for android ,现在好像不更新了,也不知道怎么样了, 可以实现编译期热更新,像flutter 的 reload 一样 \4. charles 这种就不用说了吧,了解一下

2.在开发过程中你遇到过的最大的难题是什么?如何解决的?

参考答案:

这个问题其实面试官给你炫技的机会!如果你真的深入参与过复杂的app开发,肯定会碰到一些比较棘手的问题。你需要把问题的症状描述清楚,然后告诉面试官自己如何一步一步找到root cause,最后给出解决方案。

3.说说你未来的职业规划是怎样的?

参考答案:

正常来说职业规划,一般回答: 1.持续在技术方向精进,走技术架构方向。 2.以技术为基础,向外扩展管理技能,走管理路线。

不过这都比较难吧,主要还是国内环境太卷了,且对大龄工作者极度不友好。 一些技术大佬开始从精进技术转向割韭菜卖课,卖星球。 期待新的00后能整治职场内卷邪风。

4.你是如何看待 Flutter,React Native 与 Android 关系的?

参考答案:

Flutter: 优点: 目前已经完成一个完整的flutter项目,整体感受就是开发速度很快,有些偏web端的开发,入门容易,上手写页面也不错。 缺点: 涉及到原生插件等问题,需要你同时处理Android和iOS两端的问题。 涉及原生view嵌入flutter的布局树中效果不佳,操作上有些很难处理。目前flutter还在持续改进。 插件市场中个人的插件不如开始热度高,有些不错的插件都没人管了

目前原生推出的compose和flutter相似度极高,不知道google在搞什么玩意

有兴趣的可以去看看项目效果:Android 商店/App Store 搜索 Gwadar Pro