肇庆做网站gdmkd,做投票网站,1元云主机,app在线设计函数引用、匿名函数、lambda表达式、inline函数的理解 双冒号对函数进行引用的本质是生成一个函数对象只有函数对象才拥有invoke()方法#xff0c;而函数是没有这个方法的kotlin中函数有自己的类型#xff0c;但是函数本身不是对象#xff0c;因此要引用函数类型就必须通过双…函数引用、匿名函数、lambda表达式、inline函数的理解 双冒号对函数进行引用的本质是生成一个函数对象只有函数对象才拥有invoke()方法而函数是没有这个方法的kotlin中函数有自己的类型但是函数本身不是对象因此要引用函数类型就必须通过双冒号的方式将函数转换成一个对象这样之后才能拿这个对象进行赋值 匿名函数是一个对象它不是函数匿名函数跟双冒号引用函数是一种东西而 lambda 表达式是一种特殊的匿名函数对象它可以省略参数和调用者括号等更加方便而已因为匿名函数是一个对象所以它包括lambda表达式才可以被当成一个参数传递双冒号函数引用、匿名函数对象、lambda这三者本质都是函数类型的对象Java8 中虽然也支持 lambda 方式的写法SAM 转换但是 Java8 中的 lambda 和 kotlin 中的 lambda 有本质的区别一个是编译器的简化写法一个是函数对象的传递。Java 中即便能写成 lambda 的方式它也是生成的一个接口类的匿名对象。 kotlin 中的 const 用来声明编译时常量作用同 java 里的 static final 会用字面量直接替换调用处的变量。但它只能写在顶级作用域。 kotlin中函数对象作为参数传递以后会创建一个临时对象给真正的函数调用这种函数如果是在for循环这样的高频调用的场景里就会因为创建大量的临时对象而导致内存抖动和频繁的GC, 甚至引发OOM 如果给函数加上inline关键字它会将调用的函数插入到调用处进行平铺展开这样就可以避免生成临时函数对象带来的影响。所以inline关键字的优化要是针对高阶函数的。
inline、nolinline、crossinline
inline通过内联即函数内容直插到调用处的方式来编译函数noinline局部关掉这个优化来摆脱「不能把函数类型的参数当对象使用」的限制crossinline让内联函数里的函数类型的参数可以被间接调用代价是不能在 Lambda 表达式里使用 return
noinline 和 crossinline 主要是用来解决加上 inline 之后可能导致的一些副作用或者附带伤害进行补救的措施至于什么时候需要使用它们不需要记住规则因为 Android Studio 会在需要的时候提示它。
什么是 SAM 转换
SAM 转换Single Abstract Method是针对只有一个方法的接口类的简化写法例如
// Single Abstract Method
public interface Listener {void onChanged();
}public class MyView {private Listener mListener;public void setListener(Listener listener) {mListener listener;}
}MyView view new MyView();
view.setListener(new Listener() {Overridepublic void onChanged() {}
});如果你写成这种写法编译器就会提示你可以将其转换成 lambda 表达式jdk 1.8: 于是代码就可以简化成下面这样
MyView view new MyView();
view.setListener(() - {// todo
});当然如果是在 kotlin 中调用 java 的这种代码还可以将小括号去掉直接调用方法后面跟上 {} 变成更彻底的 lambda 写法。
MyView().setListener { // todo
}泛型中的 out 与 in
在Kotlin中out代表协变in代表逆变为了加深理解我们可以将Kotlin的协变看成Java的上界通配符将逆变看成Java的下界通配符
// Kotlin 使用处协变
fun sumOfList(list: Listout Number)
// Java 上界通配符
void sumOfList(List? extends Number list)
// Kotlin 使用处逆变
fun addNumbers(list: Listin Int)
// Java 下界通配符
void addNumbers(List? super Integer list)我们知道 Java 的上界通配符和下界通配符主要用于函数的入参和出参它们俩一个只读一个只写而 kotlin 中将这两个分别命名为out和in在含义上更加明确了。 总的来说Kotlin 泛型更加简洁安全但是和 Java 一样都是有类型擦除的都属于编译时泛型。
另外kotlin 可以直接使用 out 或 in 在类上指定泛型的读写模式但是 Java 不可以
// 这个类就是只能获取不能修改了
// 声明的时候加入 一劳永逸了 out T
class Workerout T {// 能获取fun getData(): T? null// 不能修改/* * fun setData(data: T) { }* fun addData(data: T) { }*/
}// 这个类就是只能修改不能获取
// 声明的时候加入 一劳永逸了 in T
class Studentin T {/* fun a(list: Mutablelistin T) **/fun setData(data: T) {}fun addData(data: T) {}// 不能获取// fun getData() : T
}// Java 不允许你在声明方向的时候控制读写模式
public class Student /*? super T*/ { }类上的泛型 T 前面的 out 或 in 关键字作用于整个类范围所有使用该泛型的地方。Kotlin 为什么这样设计它表示所有使用 T 的场景都是只用来输出或者只用来输入的那么为了避免我在每个使用的位置都给变量或者参数写上out这么麻烦就干脆直接声明在了类上面。什么时候该用 out 或 in 这是一个设计问题类的设计者需要考虑这个类的职责是否是只用于生产或者只用于消费的。
在类上使用 out 或 in 时赋值的区别
子类泛型对象可以赋值给父类泛型对象用 out父类泛型对象可以赋值给子类泛型对象用 in // 子类泛型对象可以赋值给父类泛型对象用 out
val production1: ProducerFood FoodStore()
val production2: ProducerFood FastFoodStore()
val production3: ProducerFood BurgerStore()
// 父类泛型对象可以赋值给子类泛型对象用 in
val consumer1: ConsumerBurger Everybody()
val consumer2: ConsumerBurger ModernPeople()
val consumer3: ConsumerBurger American() 这赋值这一点上 使用 out 和 in 与 Java 的上界通配符和下界通配符是一样的行为。
Kotlin 泛型中的 * 相当于Java 泛型中的 ? Java 和 Kotlin 中类的泛型参数有多继承的区别 Kotlin 泛型方法示例
fun T instanceRetrofit(apiInterface: ClassT) : T {// OKHttpClient 用于请求服务器val mOkHttpClient OkHttpClient().newBuilder() .readTimeout(10000, TimeUnit.SECONDS) // 添加读取超时时间 .connectTimeout(10000, TimeUnit.SECONDS) // 添加连接超时时间 .writeTimeout(10000, TimeUnit.SECONDS) // 添加写出超时时间.build()val retrofit: Retrofit Retrofit.Builder().client(mOkHttpClient) // 请求方处理响应方.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 使用 RxJava 处理响应.addConverterFactory(GsonConverterFactory.create()) // 使用 Gson 解析 JavaBean.build()return retrofit.create(apiInterface)
}简单总结一下
kotlin的 out in 和 Java 的? extends T 和 ? super T 虽然含义是一样的但是写法上有点不同
Java 的上界通配符和下界通配符只能用作方法的参数用于入参和出参的作用不能直接写到类上面。kotlin 中的 out in 可以直接写到类上面直接表明整个类中使用到泛型 T 的地方都具备这个含义。当在类上面声明泛型 T 为 out 时表示 T 只能从当前类中输出而不能输入。当在类上面声明泛型 T 为 in 时表示 T 只能从当前类中输入而不能输出。
Kotlin 标准库中的常用扩展函数 apply、also、run、let
使用时可以通过简单的规则作出一些判断
需要返回自身 - 从 apply 和 also 中选 作用域中使用 this 作为参数 ---- 选择 apply作用域中使用 it 作为参数 ---- 选择 also 不需要返回自身 - 从 run 和 let 中选择 作用域中使用 this 作为参数 ---- 选择 run作用域中使用 it 作为参数 ---- 选择 let apply 适合对一个对象做附加操作的时候let 适合配合判空的时候 (最好是成员变量而不是局部变量局部变量更适合用 if )with 适合对同一个对象进行多次操作的时候
apply VS also
apply 和 also 的返回值都是当前调用者的对象也就是 T 类型的当前实例
public inline fun T T.apply(block: T.() - Unit): T { block()return this
}
public inline fun T T.also(block: (T) - Unit): T { block(this)return this
}这俩同是当前调用者 T 类型的扩展函数但是 apply 中的 block 同时也是当前调用者 T 类型的扩展函数所以其 lambda 中可以使用 this 访问当前对象而 also 中的 block 只是一个普通的函数不过这个普通函数的参数传入的是当前对象所以其 lambda 中只能用 it 访问当前对象。 apply 常用于创建对象实例后马上对其进行一些操作调用
ArrayListString().apply { add( testApply)add(testApply) add( testApply) println($this)
}.also { println(it)
} let VS run
let 和 run 的返回值都是 block 的返回值即 lambda 表达式的返回值
public inline fun T, R T.let(block: (T) - R): R { return block(this)
}
public inline fun T, R T.run(block: T.() - R): R { return block()
} 这俩同是当前调用者 T 类型的扩展函数但是 run 中的 block 同时也是当前调用者 T 类型的扩展函数所以其 lambda 中可以使用 this 访问当前对象而 let 中的 block 只是一个普通的函数不过这个普通函数的参数传入的是当前对象所以其 lambda 中只能用 it 访问当前的对象。 let 比较常用的一个操作是判空操作
// 避免为 nu1ll 的操作
str?.let {println(it.length)
}object 单例
我们知道 object 就是 kotlin 中天生的单例模式我们看一下它翻译成 Java 代码是什么样的
object Singleton {var x: Int 2 fun y() { }
}上面代码编译成Java字节码后反编译对应如下代码 其实它就是恶汉模式的单例。
如果是一个普通类想生成单例可以使用伴生对象 companion object 来生成
class Singleton {companion object {var x: Int 2fun y() { }}
}上面代码翻译成 Java 后长下面这样 我们看到它还是一个恶汉模式的单例只不过这个恶汉的对象实例是一个名为 Companion 的静态内部类的实例对象。另外只有 companion object 中的成员属性是放到外部类中的而 companion object 中的成员方法是放在静态内部类中的。
如果要实现 Java 中静态内部类版本的单例模式
public class Singleton {private Singleton() {}private static class SingletonHolder {/** 静态初始化器由 JVM 类加载过程来保证线程安全 */private static Singleton instance new Singleton();}public static Singleton getInstance() {return SingletonHolder.instance;}
}可以像下面这样写
class Singleton private constructor() {private object Holder {val INSTANCE Singleton()}companion object {val instance Holder.INSTANCE}
}Kotlin 中很没用的两个东西 Nothing 与 Unit
之所以说这两个东西没啥用是因为这两个相当于是用来进行标记、提示之类的作用只能说它们的实际用处不是那么大但是用来标记和提醒比较方便。
比如在 Java 中各种源码中可以看到类似下面的写法
/**
* 当遇到姓名为空的时候请调用这个函数来抛异常
* return throw NullPointerException
*/
public String throwOnNameNull() {throw new NullPointerException(姓名不能为空);
}对应到 Kotlin 的等价写法
/*** 当遇到姓名为空的时候请调用这个函数来抛异常*/
fun throwOnNameNull() : String {throw NullPointerException(姓名不能为空)
}这个函数的主要作用是用来抛出一个异常但是如果这么写会让人很困惑明明只是抛出异常返回值却是一个String所以这个时候可以用 Unit 或者 Nothing 代替它
/*** 当遇到姓名为空的时候请调用这个函数来抛异常*/
fun throwOnNameNull() : Nothing {throw NullPointerException(姓名不能为空)
}这样开发者在看到这个函数时就会知道它什么也不会返回。就是一个提醒的作用。
Nothing 还可以用作泛型实参以起到一个空白占位符的作用例如
val emptyList: ListNothing listOf()
var apples: ListApple emptyList
var users: ListUser emptyList
var phones: ListPhone emptyList
var images: ListImage emptyList val emptySet: SetNothing setOf()
var apples: SetApple emptySet
var users: SetUser emptySet
var phones: SetPhone emptySet
var images: SetImage emptySet val emptyMap: MapString, Nothing emptyMap()
var apples: MapString, Apple emptyMap
var users: MapString, User emptyMap
var phones: MapString, Phone emptyMap
var images: MapString, Image emptyMap val emptyProducer: ProducerNothing Producer()
var appleProducer: ProducerApple emptyProducer
var userProducer: ProducerUser emptyProducer
var phoneProducer: ProducerPhone emptyProducer
var imageProducer: ProducerImage emptyProducer 这样写唯一的好处就是简单方便节省内存。
Nothing 还有一个作用就是当你不知道返回什么的时候就可以返回它例如
fun sayMyName(first: String, second: String) {val name if (first Walter second White) { Heisenberg} else {return // 语法层面的返回值类型为 Nothing赋值给 name }println(name)
}这里虽然没有显示的写出来但是在语法层面这里的 return 返回的就是 Nothing。
对于 Unit它主要用来对标 Java 中的 void 关键字表示什么也不返回即返回空。为什么要设计一个这个呢直接使用 void 不就行了吗这是因为 Java 中的 void 关键字虽然表示返回空但是它不是一个实际的类型在某些地方你就不能用它来作为一个类型去表达“什么也不返回”的含义例如最常见的例子就是以前开发中经常使用的 AsyncTask
class BackgroundTask extends AsyncTaskString, Void, Void {Overrideprotected Void doInBackground(String... strings) {// do somethingreturn null;}Overrideprotected void onProgressUpdate(Void[] values) {super.onProgressUpdate(values);}Overrideprotected void onPostExecute(Void o) {}
}回忆一下AsyncTask的三个泛型参数public abstract class AsyncTaskParams, Progress, Result
Params doInBackground方法的接受参数类型ProgressonProgressUpdate方法的接受参数类型ResultonPostExecute方法的接受参数类型以及doInBackground方法的返回参数类型
在上面代码中由于void是一个关键字而不是一个类型所以不能用于泛型的位置作为实参所以就会出现很尴尬的场景我们必须再造一个类似于void关键字的类型也就是大写的 Void它其实是一个类并且上面代码中我们看到 doInBackground 方法的返回参数类型是 Void但实际上是 return 了一个 null而且你不写这个 null 又不行编译不过。这就令人非常尴尬了。
为了避免这种令人尴尬的场景kotlin 就干脆直接定义了一个 Unit 类型来代表这种 Void它是一个实际的类型你可以用它来定义变量它也可以出现在任何需要类型的地方。 但是 Kotlin 中一般函数没有返回值时我们可以省略写这个 Unit。
Unit 更常见的身影是 lambda 表达式以及高阶函数的参数定义中例如
fun foo(block : (Int, String) - Unit) : Unit {return block(123, hello)
}val sum : (Int, Int) - Unit { x, y - x y }
fun main() {val a sum(1, 2)
}确切的说在 Kotlin 中它的主要用途是用来表达函数类型因为如果只是一个关键字的话而不是一个类型那么你就无法把它在需要表达函数类型的地方写出来。
关于 Unit 另外一个比较常见的场景是在 Jetpack Compose 的副作用 API 中的使用例如
LaunchedEffect(Unit) {...
}这种写法显得比较奇怪但是看一眼 Unit 的源码就明白了
/*** The type with only one value: the Unit object. This type corresponds to the void type in Java.*/
public object Unit {override fun toString() kotlin.Unit
}我们发现它就是一个 object 单例而 object 对应到 Java 中就是一个 final 类因此 object 单例在运行时是不变的它就是一个常量。
所以LaunchedEffect(Unit)也可以写成LaunchedEffect(1)、LaunchedEffect(2)、LaunchedEffect(aaa)、LaunchedEffect(true)都可以只不过你想不到写啥的时候直接写 Unit 会更方便。
接口代理 by
它跟代理模式差不多但是有一点不同的 kotlin 的 by 更多的是将一个接口的实现委托给一个实现类对象。
比如 Android 中的 ContextWrapper 就是将对 Context 的操作全部都委托给了内部一个叫做 mBase 的 Context 成员对象去处理
public class ContextWrapper extends Context {Context mBase;public ContextWrapper(Context base) { mBase base; }Overridepublic AssetManager getAssets() {return mBase.getAssets(); }Overridepublic Resources getResources() {return mBase.getResources(); }Overridepublic PackageManager getPackageManager() {return mBase.getPackageManager(); }Overridepublic ContentResolver getContentResolver() { return mBase.getContentResolver(); }...
}再比如我想定制一个类型是 User 的 List 对象实现按年龄排序之类的需求可以这样写
class UserList implements ListUser {ListUser userList;public UserList(ListUser userList) { this.userList userList; } public ListUser higherRiskUsers() { ...}public void sortWithAge() { ...} Overridepublic int size() { return 0; } Overridepublic boolean isEmpty() { return false;} Overridepublic boolean contains(Nullable Object o) { return false;}......
} 这样就是将对 List的操作委托给了内部的userList去操作你可以传入一个ArrayList或者一个LinkedList的实现给它。
但是这样写的话我们发现里面多了很多不需要但是不得不写的方法例如size()、isEmpty()等等如果使用 Kotlin 的 by 关键字进行委托就会简化很多例如
class UserList(private val list: ListUser): ListUser by list {public ListUser higherRiskUsers() { ...}public void sortWithAge() { ...}
}这样那些不得不写但又没有营养的方法我们就可以不用手动去写了而是交给构造函数中传入的 list 参数的实现类实例对象去自动实现这些方法。我们只需要关心在这个类中如何实现真正需要添加的业务方法即可。
总结一下它的语法就是
class 类名(val 实际接口的实现者[a] : 接口类) : 接口类 by [a] {}其中 a 传入的就是要实现的接口的实际实现对象实例。
当然在使用 by 关键字也可以不完全委托给构造函数传入的实例对象如果在类中覆写了所实现接口的某个方法就会以你覆写的为准而不是交给委托的对象这一点比较灵活。
比如 Kotlin 的协程源码中有一个叫 SubscribedSharedFlow 就是对 SharedFlow 进行了委托但是它没有完全委托给构造函数传入的sharedFlow对象而是重写了SharedFlow接口的 collect 方法 通过这种 方式我们可以进行某种“局部定制”功能这个感觉有点类似 Java 里面你继承一个父类抽象类但是懒于实现其每个抽象方法但是又想在需要的时候自由覆盖其某个抽象方法kotlin一个by关键字解决了这个麻烦。