Kotlin 笔记 -- Kotlin 语言特性的理解(一)

本文深入探讨了Kotlin的函数引用、匿名函数、lambda表达式以及内联函数的理解,强调了它们之间的区别。此外,还讲解了SAM转换的概念,并对比了Java与Kotlin的差异。在泛型中,介绍了out和in的用法。接着,讨论了标准库中的扩展函数如apply、also、run和let的应用场景。文章还详细阐述了object单例的实现方式以及Nothing和Unit的特殊用途。最后,解释了接口委托by的功能和用法。
摘要由CSDN通过智能技术生成

函数引用、匿名函数、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(禁止非局部返回,那样会导致函数内部的流程遭到破坏)

noinlinecrossinline 主要是用来解决加上 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 中将这两个分别命名为outin在含义上更加明确了。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总的来说,Kotlin 泛型更加简洁安全,但是和 Java 一样都是有类型擦除的,都属于编译时泛型。

另外,kotlin 可以直接使用 outin 在类上指定泛型的读写模式,但是 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>*/ {
    

}

在这里插入图片描述

  1. 类上的泛型 T 前面的 outin 关键字作用于整个类范围所有使用该泛型的地方。
  2. Kotlin 为什么这样设计&#
  • 35
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值