美文网首页MVVM + kotlin+ jetpack
4 .遥遥领先 Android kotlin高级用法,高级函数,

4 .遥遥领先 Android kotlin高级用法,高级函数,

作者: 鹏城十八少 | 来源:发表于2024-04-01 11:48 被阅读0次
封面.jpg

目录

1. Lameda

1.1 Lamebda演变过程
1.2 Lambda语法
1.3 、lambda中下划线(_)
1.4 Lambda和高阶函数的关系
1.5 Lambda的底层原理:
1.6 Lambda和闭包的关系

2.kotlin的核心是啥? 高阶函数
3.扩展函数

3.1 let、with、apply区别是使用

4.内敛函数inline
5 .匿名函数和顶层函数
6.泛型 ,使用Kotlin Reified 让泛型更简单安全
7.DSL-Anko布局 高深,看不懂
8.Kotlin系列之顶层函数和属性

1. Lambda介绍

1.1 Lamebda演变过程

这里举例一个Android中最常见的按钮点击事件的例子

 gl_surface_view.setOnClickListener(View.OnClickListener {
    
    
})

简化;

view.setOnClickListener { v: View ->
  switchToNextPage()
}

另外,如果这个 Lambda 是单参数的,它的这个参数也省略掉不写:
简化成: ()-> 都省了!

gl_surface_view.setOnClickListener {
   
}

1.2 Lambda语法

有多个参数不能用it、自己定义
当另外一个参数可以用下划线表示_
为了让大家彻底的弄明白Lambda语法,我这里用三种用法来讲解。并且举例为大家说明

箭头后面是返回值 : -> 返回值

语法如下:

   1). 无参数的情况 :
    val/var 变量名 = { 操作的代码 }

    2). 有参数的情况
    val/var 变量名 : (参数的类型,参数类型,...) -> 返回值类型 = {参数1,参数2,... -> 操作参数的代码 }

    可等价于
    // 此种写法:即表达式的返回值类型会根据操作的代码自推导出来。
    val/var 变量名 = { 参数1 : 类型,参数2 : 类型, ... -> 操作参数的代码 }

    3). lambda表达式作为函数中的参数的时候,这里举一个例子:
    fun test(a : Int, 参数名 : (参数1 : 类型,参数2 : 类型, ... ) -> 表达式返回类型){
        ...
    }

实战分析:

//给传参数命名, 2个参数, 要自己写名字aNum,bNum
var method5:(Int,Int)->Unit={aNum,bNum->
    Log.d("dd","updateGrade: $aNum","+$bNum")
}

//屏蔽传参, 下划线占位
var method6:(Int,Int)->Unit={aNum,_ ->
    Log.d("dd","updateGrade: $aNum","+$aNum")
}

//传入值和返回值(高级的写法)
var method07:(String) ->String ={str->str}

//省去返回值
val method2={ Log.d("dd","updateGrade:")}

//一个参数默认是it
var method3:(String) ->Unit={
    Log.d("dd",":$it")
}

var method4:(Int)->Unit={
    when(it){
        1->  Log.d("dd",":$it")
        else ->Log.d("dd",":$it")
    }
}

经过上面的实例讲解与语法的介绍,我们对其作出一个总结:

1).lambda表达式总是被大括号括着。
2). 定义完整的Lambda表达式如上面实例中的语法2,它有其完整的参数类型标注,与表达式返回值。当我们把一些类型标注省略的情况下,

就如上面实例中的语法2的另外一种类型。当它推断出的返回值类型不为'Unit'时,它的返回值即为->符号后面代码的最后一个(或只有一个)表达式
的类型。

3). 在上面例子中语法3的情况表示为:高阶函数,当Lambda表达式作为其一个参数时,只为其表达式提供了参数类型与返回类型,

所以,我们在调用此高阶函数的时候我们要为该Lambda表达式写出它的具体实现。
invoke()函数:表示为通过函数变量调用自身,因为上面例子中的变量b是一个匿名函数。
it
it并不是Kotlin中的一个关键字(保留字)。
it是在当一个高阶函数中Lambda表达式的参数只有一个的时候可以使用it来使用此参数。it可表示为单个参数的隐式名称,是Kotlin语言约定的。

1.3 、lambda中下划线(_)

在使用Lambda表达式的时候,可以用下划线(_)表示未使用的参数,表示不处理这个参数。

同时在遍历一个Map集合的时候,这当非常有用。

举例:

val map = mapOf("key1" to "value1","key2" to "value2","key3" to "value3")

map.forEach{
     key , value -> println("$key \t $value")
}

// 不需要key的时候
map.forEach{
    _ , value -> println("$value")
}


输出结果:
key1     value1
key2     value2
key3     value3
value1
value2
value3

1.4 Lambda和高阶函数的关系

函数里面传入一个lambda表达式是什么意思?
举例: 登录

 fun loginAction(userName:String,userPwd:String, loginResponseResult:(Boolean)->Unit){

    if(userName==null){
       return
    }
    loginEngine(userName,userPwd,loginResponseResult)

}

Lambda表达式是为我们减少了大量的代码,但是Lambda表达式是为后面的高阶函数章节打下基础
在 Java 里是不允许把方法作为参数传递的,但是我们有一个历史悠久的变通方案:接口把方法当成参数传递到参数

1.4.1 双冒号 ::method 到底是什么?

解释:
1). 一个函数名的左边加上双冒号,它就不表示这个函数本身了,而表示一个对象
2).通过变量, 把函数赋值给变量

@JvmStatic
fun startActivity(context: Context, bean: SingleTrainBean) {
    val intent = Intent(context, AICoachSingleTrainActivity::class.java)
    intent.putExtra(SINGLE_TRAIN_BEAN, bean)
    context.startActivity(intent)
}

这个双冒号的写法叫做函数引用 Function Reference
因为加了两个冒号,这个函数才变成了一个【对象】。这样就可以赋值给左边的变量!

fun b(param: Int): String {
  return param.toString()
}
val d = ::b
val e = d

创建一个函数类型的对象有三种方式:双冒号加函数名、匿名函数和 Lambda;
Lambda 和匿名函数的联系!
匿名函数:没有函数名

a(fun b(param: Int): String {
  return param.toString()
});
val d = fun b(param: Int): String {
  return param.toString()
}

另外,这种写法的话,函数的名字其实就没用了,所以你可以把它省掉:

a(fun(param: Int): String {
  return param.toString()
});
val d = fun(param: Int): String {
  return param.toString()
}

Kotlin 的匿名函数和 Lambda 表达式的本质,它们都是函数类型的对象。
因为对象才能当参数传递匿名函数虽然叫匿名函数,但是它不是函数,是函数类型的对象!

1.5 Lambda的底层原理:

Kotlin 的匿名函数和 Lambda 表达式的本质,它们都是函数类型的对象
Lambda表达式的本质其实是匿名函数,因为在其底层实现中还是通过匿名函数来实现的。但是我们在用的时候不必关心起底层实现。不过Lambda的出现确实是减少了代码量的编写,同时也是代码变得更加简洁明了。
原理:lameda表达式---->高阶函数--------->函数类型的对象------>匿名函数------(简化通过lemeda)----->lameda
内部类: 内部类访问外部类的变量, 需要用final修饰, 不然会报错!

面试官:kotlin的lameda表达式和java的lameda有啥区别?

问题: Java有lambda表达式吗?

Java 8 的 Lambda 只是一种便捷写法,本质上并没有功能上的突破,而 Kotlin 的 Lambda 是实实在在的对象。
Java 8 Lambda 表达式来创建匿名类对象。lambda 表达式不生成匿名类文件,但它本质上还是在创建一个匿名类对象,只是一种简化写法而已

Kotlin lambda 表达式完全通过生成匿名类实现,其它特点类似。 Kotlin 的 Lambda 还能当函数参数?

支持动态传入函数,

因为 Kotlin 的 Lambda 是实实在在的函数类型的对象

1.6 Lambda和闭包的关系

<pre style="margin: 8px 0px;">在 Java8 版本使用 lambda 表达式是有限制的,它不是真正意义上的闭包,而 Kotlin 中的 lambda 才是真正意义上支持闭包的实现。 </pre>

2. 高阶函数

kotlin的核心是啥? 高阶函数, 携程是扩展库

Kotlin 函数是一级函数,这意味着它们可以存储在变量和数据结构中,作为参数传递给其他高阶函数,也可以从其他高阶函数返回。

对于其他非函数值,你可以以任何可能的方式对函数进行操作。

高阶函数定义-Heigher-Order-Functions

  • 高阶函数定义:参数类型是函数类型,或者返回值是函数类型的函数称为高阶函数,lambmda也算是一个函数, 除此之外函数类型还能赋值给变量

源码: Functions.kt里面定义了函数

高阶函数的作用: 可以动态执行哪个方法, 具体在 a 执行的时候内部调用哪个方法,我希望可以动态设置

问题: java中是如何实现: 方法中调用方法?

通过接口, 接口里面定义方法, 传入接口搞定!

2.1 举例:登入:

    /**
     * 传入2个值,还有一个是内部得到的结果
     */
    loginAction("peng","cai"){
        if(it){
            println("suc")
        }else{
            println("fail")
        }
    }
}
 fun loginAction(userName:String,userPwd:String, loginResponseResult:(Boolean)->Unit){

    if(userName==null){
       return
    }
    loginEngine(userName,userPwd,loginResponseResult)

}

/***
 * 传入3个值,最后一个赋值结果变量
 */
 fun loginEngine(userName:String,userPwd:String, loginResponseResult:(Boolean)->Unit){

    if("peng"==userName||userName=="cai"){
        loginResponseResult(true)
    }else{
        loginResponseResult(false)
    }
}
2.2 apply()高阶函数源码分析:
2.3 内敛高阶函数:

高阶函数:
把lambda作为参数传递!

() -> Unit //表示无参数无返回值的Lambda表达式类型

(T) -> Unit //表示接收一个T类型参数,无返回值的Lambda表达式类型

(T) -> R //表示接收一个T类型参数,返回一个R类型值的Lambda表达式类型

(T, P) -> R //表示接收一个T类型和P类型的参数,返回一个R类型值的Lambda表达式类型

(T, (P, Q) -> S) -> R //表示接收一个T类型参数和一个接收P、Q类型两个参数并返回一个S类型的值的Lambda表达式类型参数,返回一个R类型值的Lambda表达式类型

3.扩展函数

3.1 什么是扩展函数

T.()是啥 T.()也是一个泛型扩展函数
扩展函数: 不需要再添加类, 或者类中增加方法!
Kotlin允许开发者在不改变已有类的情况下,为某个类添加新的函数。这个特性叫做扩展函数。
你可以给已有的类去额外添加函数和属性,而且既不需要改源码也不需要写子类
问题: 谁可以调用这个扩展函数?
.前面的可以用, T类型的可以调用

class Extensions {

  fun String.method1(i: Int) {
    ...
  }

  ...

kotlin FunctionType: function references , lambda , 匿名函数

扩展方法编译成java后,实际上是一个 public static的静态方法,传入了对象实例和参数

扩展函数:
为什么要使用Kotlin中的扩展函数?
如何使用扩展函数和扩展属性?
扩展函数和属性原理
扩展函数和成员函数区别
扩展函数不可以被重写

3.2 Kotlin写一个let的扩展函数,扩展函数和扩展属性?Kotlin中的扩展函数本质原理是什么?
kotlin的扩展方法是怎么实现的,inline关键字的作用
因为只要反编译过Kotlin扩展函数的代码都知道回答
扩展函数的缺点:  找不到! 

3.3. 扩展函数的使用场景:

3.3.1 比方说,把dp转成px
val Float.dp
  get() = TypedValue.applyDimension(
    TypedValue.COMPLEX_UNIT_DIP,
    this,
    Resources.getSystem().displayMetrics
  )

...

val RADIUS = 200f.dp
3.3.2 举例: Math.pow()

顶层函数:所有地方都可以访问,就像局部变量.
并且它不属于任何类,但是可以声明限制某个类才可以使用!
举一个简单的例子。如果要关闭一个I/O流,使用Java可能是写一个工具方法。

/**
 * 安全关闭io流
 * @param closeable
 */
public static void closeQuietly(Closeable closeable) {
    if (closeable != null) {
        try {
            closeable.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

对Kotlin而言,可以对Closeable扩展一个函数closeQuietly()。

fun Closeable?.closeQuietly() {
    try {
        this?.close()
    } catch (e: Throwable) {
    }
}

之后,任何实现了Closeable接口的类,都可以使用它本身的closeQuietly()方法来关闭流。我们不再需要那个工具方法了。

使用扩展函数

fun loge(tag: String, content: String?) {
    Log.e(tag, content ?: tag)
}

3.3.3 项目中的实战:

在项目中,我们使用扩展函数对Glide做了封装,大大简化了Glide的使用。

/**
     * 占位符矩形
     */
    fun ImageView.load(url: String) {
        get(url).placeholder(R.drawable.shape_default_rec_bg)
                .error(R.drawable.shape_default_rec_bg)
                .into(this)
    }

    /**
     * 占位符圆角矩形
     */
    fun ImageView.loadRound(url: String) {
        get(url).placeholder(R.drawable.shape_default_round_bg)
                .error(R.drawable.shape_default_round_bg)
//            .apply(RequestOptions.bitmapTransform(RoundedCornersTransformation(DisplayUtil.dp2px(context, 6f), 0)))
                .transform(RoundedCornersTransformation(DisplayUtil.dp2px(context, 6f), 0))
                .into(this)
    }

    /**
     * 占位符圆形
     */
    fun ImageView.loadCircle(url:Drawable) {
        get(url).placeholder(R.drawable.shape_default_circle_bg)
                .error(R.drawable.shape_default_circle_bg)
                .into(this)
    }

    fun ImageView.loadCircle(url: String) {
        get(url).placeholder(R.drawable.shape_default_circle_bg)
                .error(R.drawable.shape_default_circle_bg)
                .into(this)
    }

    fun ImageView.get(url: String): GlideRequest<Drawable> = GlideApp.with(context).load(url)
    fun ImageView.get(url: Drawable): GlideRequest<Drawable> = GlideApp.with(context).load(url)
fun Context.toast(str: String) = Toast.makeText(this, str, Toast.LENGTH_LONG).show()

// 当我们需要toast的时候,我们可以这样调用

context.toast("test")

function type: 对应function这么一个类

fun printChar2(str: String, block: String.() -> Char) { // 高阶函数变成函数的扩展
    println(str.block())
}

fun printChar1(str: String, block: (String) -> Char) { // string是入参, char是返回值
    println(block(str)) // 函数里面调用函数
}


fun String.lastChar(): Char { // 得到最后一个字符
    return this.get(this.length - 1)
}

fun main2() {
    val lastChar: (String) -> Char = String::lastChar
    printChar1("Kotlin") { // 如何传入函数,是在{}中传入的lastChar()函数
        lastChar(it)
    }
}

3.4 问题: Kotlin的高阶函数使用过吗(平时使用的map,let ,apply ,also之类的都是高阶函数),let、with、apply的适用场景和区别?

说到扩展函数,就不得不提的几个基础库的扩展函数,也叫做作用域函数,它们是:

T.run、T.let、T.also、T.apply 以及run、width两个函数。对于这几个函数的使用刚开始的时候我还是很懵逼的,只是大概了解用法,以及带来的链式调用的便捷

let、with、apply、run、also这几个高阶函数非常相似

let 经常和?.操作符号联合使用,替代IF判断,比如非空判断

let 扩展函数的实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,let函数的是一个不错的选择;let函数另一个作用就是可以避免写一些判断null的操作。

apply 创建对象并初始化设置

run run函数有两种:一个是全局函数、一个是扩展函数

全局函数run 替代 java中 { } 代码作用域

run 函数实际上可以说是let和with两个函数的结合体,run函数只接收一个lambda函数为参数,以闭包形式返回,即返回 lambda 表达式的返回值。

let比较.jpg

4.内敛函数inline

@InlineOnly
public inline fun <T> Continuation<T>.resume(value: T): Unit =
    resumeWith(Result.success(value))

高阶函数有性能缺陷一般: 会把代码拷贝到引用的地方, 会增加很多代码! 主要是因为lameda, lameda的本质是函数类型的

对象, 会产生多余的对象, 如果是循环里面创建, 就会影响性能
inline: 打标记, 编译的时候, 可以把代码铺平, 不产生临时对象, 减少临时对象的创建!

内敛函数, inline关键字的作用,知不知道编译后Java中如何表示

Inline关键字修饰的函数是内联函数,用于优化函数调用的压栈操作,相当于在编译期间把内联函数的代码拷贝到函数调用处,可以提升性能,但是增加了代码量

LiveData源码里面用到了inline
noinline: 不内敛, 有啥用, 会把高阶函数的功能缩减! 主要作用: 本来可以拿到的对象, 现在获取不到了
crossinline: return的使用的时候, 解除限制,

总结下来就是:

inline 可以让你用内联——也就是函数内容直插到调用处——的方式来优化代码结构,从而减少函数类型的对象的创建;
noinline 是局部关掉这个优化,来摆脱 inline 带来的「不能把函数类型的参数当对象使用」的限制;
crossinline 是局部加强这个优化,让内联函数里的函数类型的参数可以被当做对象使用。

5 .匿名函数

val d = fun(param: Int): String {
  return param.toString()
}

它没有名字 , 变成lambda程这样

val b = { param: Int -&gt;
  return param.toString()
}

另外 Lambda 的返回值不是用 return 来返回,而是直接取最后一行代码的值:
匿名函数的本质:
因为 Kotlin 的匿名函数不——是——函——数。它是个对象. (匿名函数不是函数)

6. 解释一下泛型的逆变和协变?

泛型:in和out *
限制集合和数组不一样
?extend 上界 用来解除限制------->, out 使用 extend通配符表示可以读,不能写
? super 下界------------------------------> in。 使用 super 通配符表示可以写,不能读
一个代表消费,一个代表产出
如果in操作符在if语句中,则表示判断含义
Kotlin 中 out 相当于 <? extends T>,in 相当于 <? super T>

public interface Continuation<in T> { // 
    /**
     * The context of the coroutine that corresponds to this continuation.
     */
    public val context: CoroutineContext

    /**
     * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
     * return value of the last suspension point.
     */
    public fun resumeWith(result: Result<T>)
}

intercepted扩展函数也是expect的,又得去kotlin仓库里面找jvm相关的实现
// 接口.接口。 接口里面嵌套接口
*代表的含义!

 override val key: CoroutineContext.Key<*> get() = Job    

相关文章

网友评论

    本文标题:4 .遥遥领先 Android kotlin高级用法,高级函数,

    本文链接:https://www.haomeiwen.com/subject/nncitjtx.html