函数引用、匿名函数、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:使用
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() {
@Override
public 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: List<out Number>)
// Java 上界通配符
void sumOfList(List<? extends Number> list)
// Kotlin 使用处逆变
fun addNumbers(list: List<in Int>)
// Java 下界通配符
void addNumbers(List<? super Integer> list)
我们知道 Java 的上界通配符和下界通配符主要用于函数的入参和出参,它们俩一个只读,一个只写,而 kotlin 中将这两个分别命名为out
和in
在含义上更加明确了。
总的来说,Kotlin 泛型更加简洁安全,但是和 Java 一样都是有类型擦除的,都属于编译时泛型。
另外,kotlin 可以直接使用 out
或 in
在类上指定泛型的读写模式,但是 Java 不可以:
// 这个类,就是只能获取,不能修改了
// 声明的时候加入 <out T> 一劳永逸了
class Worker<out T> {
// 能获取
fun getData(): T? = null
// 不能修改
/*
* fun setData(data: T) { }
* fun addData(data: T) { }
*/
}
// 这个类,就是只能修改,不能获取
// 声明的时候加入 <in T> 一劳永逸了
class Student<in T> {
/* fun a(list: Mutablelist<in T>) **/
fun setData(data: T) {
}
fun addData(data: T) {
}
// 不能获取
// fun getData() : T
}
// Java 不允许你在声明泛型的时候,控制读写模式
public class Student /*<? super T>*/ {
}
- 类上的泛型
T
前面的out
或in
关键字作用于整个类范围所有使用该泛型的地方。 - Kotlin 为什么这样设计&#