Kotlin
协程并发异常怎么处理?
- try-catch 直接在一个父协程内部使用try-catch可能无法捕获其直接启动的某个子协程内部抛出的异常,这是因为协程的异常会沿着作用域层级向上传播,而不是直接抛给启动它的父协程
- 使用后SupervisorJob实现任务隔离:核心作用是改变协程的异常传播方式:因为默认是Job,任何一个子协程的失败(抛出异常未处理)都会导致整个作用域以及所有其他子协程被取消,这叫做一损俱损。SupervisorJob行为:一个子协程的失败不会牵连父协程和兄弟协程。这叫失败隔离。这非常适用于执行多个独立任务的场景,例如同时下载多张图片,你希望即使是一张下载失败,其他的下载任务也能继续
- 使用CoroutineExceptionHandler全局兜底:是用于处理未被捕获的异常的最后手段。通常用于记录日志。报告错误或者执行一些清理工作
各种操作符、关键字
变量与函数
val
- 声明不可变变量,类似于Java的final变量
var
- 可变变量
fun
- 用于声明函数(方法)
const
- 用于修饰val,生命编译时常量,它必须在顶层(文件级)或者伴生对象(companion object)中声明,且类型必须是基本类型或者String
- const val PI = 3.1415926
- const val APP_VERSION = "1.0"
流程控制
if
- 不仅作为条件判断,还可以作为表达式返回结果
- val max = if(a > b) a else b
when
- 强大的多路分支条件判断,可以替代Java的switch,但是支持任意类型、范围判断等多种条件
for
- 主要用于循环遍历范围、集合或者数组
- for (item in list) {printIn(item)}
- for (i in 1..10) {printIn(i)}
while
try-catch-finally
- 用于异常处理,kotlin的try-catch同样可以作为表达式使用
- val result = try { "123".toInt() } catch (e: NumberFormatException) { null }
类型系统
is !is
- 用于智能类型检查,使用is检查通过后,在作用域内该变量会自动转换为目标类型,无需显示转换
- if (obj is String) {printIn(obj.length)} // 此处obj被智能转换为String类型
as as?
- 用于类型转换,as是不安全转换,失败时抛出异常。as?则是安全转换,失败时先返回null,常与Elvis操作符(?:)结合使用
- val str1: String = anyObj as String // 不安全
- val str2: String? = anyObj as? String // 安全
- val safeStr = anyObj as? String ?: "Default" // 安全并提供默认值
面向对象
class
- 用于声明类
interface
- 用于声明接口
object
- 用于实现单例模式。使用object声明的对象是线程安全且延迟加载的单例
- object Singleton {fun doSomething() {} }
- Singleton.doSomething()
data
- 用于生命数据类,编译器会自动为其生成eauals()、hashCode()、toString()、copy()等标准方法
- data class User(val id: Int, val name: String) // 一行代码定义一个数据类
companion
- 用于声明伴生对象。伴生对象内的成员类似于java的静态成员,可以通过类名直接访问。kotlin中没有static关键字,这通常是实现静态成员的方式
class MyClass {
companion object {
const val CONSTANT = 100
fun create(): MyClass = MyClass()
}
}
// 使用:MyClass.CONSTANT 或者 MyClass.create()
enum
高级特性
by
- 实现了委托模式。包括类委托(将接口实现委托给另一个对象)和属性委托(如by lazy实现懒加载)
// 类委托 将MyInterface 的实现委托给 myImplementation
class MyDelegator(myImplementation: MyImplementation): MyInterface by myImplementation
// 属性懒加载委托 只在第一次访问的时候被初始化
val heavyObject by lazy {HeavyObject()}
lateinit
- 延迟初始化非空(non-null)属性,常用于依赖注入或在onCreate方法中初始化Android组件的时候,避免使用可空类型
class MyActivity : Activity() {
lateinit var recyclerView: RecyclerView // 声明的时候未初始化
override fun onCreate() {
recyclerView = findViewById(R.id.recycler_view) // 稍后初始化
}
}
inline、noinline、crossinline
- 简单理解:普通函数调用就像是打开APP,找到餐厅,下单,等待外卖,就像一次函数调用,需要时间。Inline就是自己在家做,无需下单,直接按照菜谱函数体,自己完成,虽然可能省去了点餐等餐地过程,但是你的厨房操作台调用出看起来会摆满菜谱所需要的食材和步骤,显得有些乱
- 简单来讲,就是inline就是把函数里面地代码,直接复制粘贴到它被调用地地方,而不是跳转过去执行,为什么要这么做呢?主要还是为了效率,特别是当函数需要你交代具体怎么做即接收lambda参数时候
- 与内联函数相关,inline建议编译器将函数体直接复制到调用的地方,可以提升高阶函数的性能。noinline和crossinline用于更精确地控制内联行为
reified
- 配合line使用,使内联函数地泛型类型参数在运行时不配擦除,可以直接使用T::class.java等操作
inline fun <reified T> String.formJson(): T? {
return Gson().fromJson(this, T::class.java)
}
val user = jsonString.fromJson<User>()
sealed
- 用于声明密封类,它定义了一组受限地子类,在when表达式中检查所有子类时,如果覆盖了所有情况则不需要else分支,使得代码更安全
open 与 final
- 类默认是final不可继承地,只有使用了open修饰地类和方法才能被继承或者重写
Java
线程有哪几种状态?
- 创建之后处于NEW状态,调
- 用start()方法是进入RUNNABLE状态的唯一方式,切记不要直接调用run()方法,那只是在当前线程中执行一个普通方法,不会创建新线程
- RUNNABLE的内涵:一个处于RNNNABLE状态的线程不一定正在消耗CPU,它可能只是在等待系统的调度
- BLOCKED、WAITING、TIMED_WAITING:这三个状态都表示线程的暂停,但原因和恢复条件不同:BLOCKED指的是线程在竞争进入synchronized同步块时失败而陷入的状态,它等待的锁;WAITING通常是线程主动调用特定方法进入的,一个关键区别是:调用Object.wait()会释放当前持有的锁,而Thread.sleep()则不会释放任何锁
- Java线程的六种状态清晰地描绘了其出生到结束的完整生命周期。RUNNABLE是线程的工作状态,而BLOCKED、WAITING、TIMED_WAITING则是不同原因而暂停的等待状态。理解这些状态及其转换条件,是掌握Java并发编程的基石。
List的实现类、以及原理
- ArrayList和Linked
- 核心区别在于底层数据结构(数组VS链表)带来地性能差异
- 都实现了List接口,但是底层实现不同,导致性能有差异。前者地底层是动态数组,最大优点是支持随机访问,通过索引查询元素地速度非常快,时间复杂度是O(1),缺点是在中间位置插入或者删除元素的时候,需要移动后续所有元素,效率较低,时间复杂度是O(n)。LinkedList底层是双向链表。它的优点是在任意位置插入和删除元素的都非常地高效,只需要修改相邻节点地指针即可,时间复杂度是O(1),但缺点是随机访问很慢,需要从链表头开始遍历,时间复杂度是O(n),因此,选择依据是:如果应用场景以查询为主,推荐使用ArrayList,如果频繁在集合中间进行插入和删除操作,则LinkedList更为合适
- HashMap vs HashTable vs ConcurrentHashMap: 线程安全性地区别
- HashMap扩容机制
- ConcurrentHashMap高并发
多线程的使用
泛型、泛型擦除
线程隔离
- 服务器就像是一个热闹的游乐园,有过山车、旋转木马、碰碰车
- 如果没有线程隔离,假如过山车因为检修特别慢,排队就会越积越多,结果那些只想玩旋转木马地也要跟着排队
- 解决方案:为每个项目开设独立通道
- ThreadLocal是Java中一个用于实现线程局部变量的工具类。它允许你创建一个变量,每个访问该变量的线程都会拥有一个独立的副本,从而实现线程隔离,避免多个线程环境下的资源竞争问题
public class SimpleThreadLocalExample {
private static ThreadLocal<Integer> threadLocalCounter = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Runnable task = () -> {
int currentValue = threadLocalCounter.get();
currentValue++;
threadLocalCounter.set(currentValue);
System.out.printIn(Thread.currentThread().getName() + "的计数器值:" + threadLocalCounter.get());
threadLocalCounter.remove();
}
Thread thread1 = new Thread(task, "线程-A");
Thread thread2 = new Thread(task, "线程-B");
Thread thread3 = new Thread(task, "线程-C");
// 启动所有线程
thread1.start();
thread2.start();
thread3.start();
}
}
Synchroized修饰非静态方法和静态方法的区别
- 前者是实例对象锁,后者是类锁
- 前者锁住的是同一个实例内的所有同步非静态方法,后者是锁该类的所有实例中的同步静态方法
Android
MVI(Model-View-Intent)
- 是近年来Android开发中备受关注的一种应用架构模式。它强调单向数据流和响应式编程,旨在让应用的状态变化更加可预测和易于调试
Model(模型)
代表UI的状态(State),是一个不可变的数据结构(如Kotlin的data class)。它包含了当前界面展示所需的所有信息(如数据列表、加载状态、错误信息)
View(视图)
- 负责将状态(State)渲染到屏幕上,并捕捉用户操作(如点击、输入),将其转换为意图(Intent)发送出去
Intent(意图)
- 代表用户的动作或者意图(如按钮点击、下拉刷新)注意,它并非安卓中的android.content.Intent,而是一个密封类(sealed class),用于枚举所有可能的用户交互
- 这个闭环确保了数据流动的方向是单一且明确的,从而避免了双向数据流可能带来的状态混乱的问题
优势
- 可预测性、易于调试:由于状态不可变且变化路径单一,任何UI问题都可以通过回溯状态变化历史来定位,甚至可以实现时间旅行调试
- 强可预测性:ViewModel处理Intent的逻辑通常是纯函数(给定相同输入、必然产生相同输出)。极易进行单元测试
- 一致性:在复杂界面中,所有UI元素都源自于同一个状态对象,有效避免了数据不同步的情况