kotlin 学习教程(五) |函数和函数式编程

1. 前言

我们知道,在程序中,通常情况下。一个类会有自己的方法(函数)以及属性,这些方法代表了该类的特性或者说具有的能力。今天我们一起来研究一下 kotlin中的函数。

2.如何声明一个函数

在 kotlin 中,我们通过关键字 fun 来声明一个函数

fun multiply(x:Int,y:Int):Int{
   ...  //代码块
    return x*y
}

如上面的代码所示:我们定义了函数multiply()并指定其返回类型为 Int 类型。

3.Lambda表达式

3.1 Lambda表达式介绍

从Java8 开始,Lambda表达式在 Lambda表达式,在其他的编程语言中(例如:Scala语言),这种表达式是语法糖中的一种,在 kotlin 中,也支持这种语法,它允许把函数作为一个方法的参数,可以使代码变的更加简洁紧凑。

Lambda表达式的本质其实是匿名函数,因为在其底层实现中还是通过匿名函数来实现的。但是我们在用的时候不必关心起底层实现。不过Lambda的出现确实是减少了代码量的编写,同时也是代码变得更加简洁明了。

3.2 Java 8 Lambda 表达式
3.2.1 语法:
(parameters) -> expression
或
(parameters) ->{ statements; }
3.2.2 特征
  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。

  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。

  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。

  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

3.2.3 代码如下:
// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

从上面可以看出,代码简洁了不少。

我们再来看一个例子,在java中假如要计算两个数的和,则代码如下:

import java.util.function.BiFunction;

public class helloword {
    public static void main(String[] args) {
        BiFunction<Integer,Integer,Integer> sum=(Integer x, Integer y) ->{
            return x+y;
        };
        System.out.println("sum(3, 4) = " + sum.apply(3, 4));
    }

}

运行结果如下:


Snipaste_2020-02-17_10-31-55.png

但是,在 java 8 以下版本,并不支持 Lambda 表达式,而这个时候.kotlin 完美的兼容了 java 8 以下版本对 lamdba 表达式的支持,并且能够进行混合开发。

3.3 kotlin Lamdba 表达式

好,我们再来看看 Android 中 kotlin 的使用,Kotlin语言中

3.3.1 Lamdab 表达式 的声明:

lambda表达式的完整语法如下:

{ params -> expressions }
  • params表示参数列表,expressions表示具体实现,可以是单行语句,也可以是多行语句。

  • Lamdba 表达式的值为大括号最后一行的值。

3.3,2 Lamdba 表达式的类型表示:
  • () ->unit 无参,返回值为Unit

  • (Int)-> Int 传入整型,返回一个整型

  • (String,(String)->String)->Boolean 传入字符串,Lamdba 表达式,返回 Boolean

3.3.3 Lamdba 表达式的调用:
  • 使用 () 进行调用,相当于invoke()
3.3.4 Lamdba 表达式的简化:
args.forEach(){ println(it) }  // 如果函数的最后一个参数是lamdba 表达式,则可以将lamdba 放在括号外面
args.forEach{ println(it) }  // 如果函数参数只有一个lamdba表达式,则调用时小括号可以省略。
args.forEach(::print)    //入参,返回值与形参一致的函数可以用函数引用的方式作为实参传入。

上述同样的功能,kotlin 语言的实现如下:

  • 自定义函数来实现:
fun main(args: Array<String>) {
  val result=sum(3,4)
    println(result)
}
  fun sum(arg1:Int,arg2:Int)=arg1+arg2
  • 使用 Lamdba 表达式实现:
fun main(args: Array<String>) {
    println(result(3, 4))
}

val result = { arg1: Int, arg2: Int ->
    println("$arg1+$arg2=${arg1 + arg2}")
    arg1 + arg2
}

我们再来看个例子,要求:遍历kotlin 中main函数的参数,

  • 示例代码:Android 中最常见的点击事件
tv_toLogin.setOnClickListener(object:View.OnClickListener{
            override fun onClick(v: View?) {
                Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show() 
            }

        })
  • 使用 Lambda 表达式的点击事件
tv_toLogin.setOnClickListener({
            Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show() 
        })

怎么样?有没有感觉 lamdba 表达式使用起来既简洁又优雅。

4. 高阶函数

4.1 什么是高阶函数

高阶函数就是以另一个函数作为参数或返回值的函数,Kotlin 可以以 lambda 或参数引用作为参数或返回值,所以,任何以 lambda 或函数引用作为参数或返回值的都是高阶函数。

4.2 常见的高阶函数
4.2.1 forEach()函数
fun main(args: Array<String>) {
   val list= listOf(1,2,3,4,5,6,7,8)
    val newList=ArrayList<Int>()
    list.forEach{
        val newElement=it *2+3
        newList.add(newElement)
    }
    newList.forEach(::println)
}

运行结果如下:

Snipaste_2020-02-17_10-31-55.png
4.2.2 :map()函数

map: 接受一个lambda表达式,并且有返回值,形成一个新的list,实现对集合中的元素进行修改

好,我们通过高阶函数 map 来实现如上的效果:

fun main(args: Array<String>) {
   val list= listOf(1,2,3,4,5,6,7,8)
   val newList=list.map {
       it*2+3
   }
    println(newList)
}

运行结果如下:

Snipaste_2020-02-17_10-31-55.png

以上代码中通过高阶函数 map 实现了对集合中每个元素乘以2再加3的操作,不用去遍历集合中每个元素,是不是简单了许多。

4.2.3 flatMap()函数

flatMap是map和flat两个函数的“复合逻辑,可以将集合中的数据进行合并成一个集合。

示例代码如下:

fun main(args: Array<String>) {
  val list= listOf(
      1..20,
      21..30,
      31..100
  )
  
   val flatList=list.flatMap {
       it.map {
           "NO.$it"
       }
   }

    flatList.forEach{
        println(it)
    }
}

运行结果如下: 将集合中数字从1~100打印输出,每个数字前标有“NO.”


Snipaste_2020-02-17_10-31-55.png
4.2.4 filter()函数

传入Lambda 表达式为 true 是,保留该元素;使用filter对集合进行按条件过滤

fun main(args: Array<String>) {
    val list= listOf(1,2,3,4,5,6,7,8,9)
    val result=list.filter {
        it%2==0
    }
    println(result)
}

运行结果如下:


Snipaste_2020-02-17_10-31-55.png
4.3 kotlin 中的 特殊函数
4.3.1 run()函数

该函数实际上可以说是let和with两个函数的结合体,run函数接收一个lambda函数为参数,以闭包形式返回,返回值为最后一行的值或者指定的return的表达式。

示例代码如下:

fun main(args: Array<String>) {
    FunKotlin().myFun()
    run {
        FunKotlin().myFun()
    }
    run {
        println("kotlin")
    }
}

class FunKotlin{

    fun myFun():String{
        println("开始执行myFun()函数")
        return "kotlin 中的特殊函数"
    }
}

运行结果如下:

Snipaste_2020-02-17_10-31-55.png

在上面的代码中,我们定义了myFun()函数并通过run() 进行调用,调用的结果即为myFun()的结果。

run() 源码如下:

/**
 * Calls the specified function [block] and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

如代码所示:我们传入block()参数,最终返回了block() 的执行结果。

4.3.2 apply()函数

源码如下:

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

如代码所示:我们传入 block() 函数,先是调用了block()函数,然后返回当前的调用者对象this,也就是说先执行完block()代码块逻辑后,再次返回当前的调用者对象。

示例代码如下:

fun main(args: Array<String>) {
    testApply()
}

fun testApply(){
    val list= mutableListOf<String>()
    list.add("A")
    list.add("B")
    list.add("C")
    list.add("D")
    list.add("E")
    println("普通写法:list=${list}")

    val applyList=mutableListOf<String>().apply {
        add("A")
        add("B")
        add("C")
        add("D")
        add("E")
        println("使用apply 函数写法 this=${this}")
    }
}

运行结果如下:


Snipaste_2020-02-17_10-31-55.png

如代码所示:我们的需求是创建一个集合并向其中添加元素"A",“B”,"C",“D”,“E”,然后打印出该集合,相比普通写法,使用apply() 函数显然简洁了许多。

4.3.3 let() 函数

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

源码如下:

/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

如代码所示,我们重点看最后一行return block(this),就是说把当前调用对象作为参数传入block()代码块中。

示例代码如下:我们以Android中在适配器 adapter 中进行网络图片的加载。

context?.let {         
  Glide.with(it).load(item.envelopePic).crossFade().into(helper.getView<ImageView>(R.id.iv_envelopePic))
                }

在上面的代码中,it 指代的即是 context,意为上下文。</pre>

4.3.4 also()函数

源码如下:

/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

看最后两行代码,先是执行了block(this),随后返回this,即当前的调用者对象。

示例代码如下:

fun testAlsoFun() {
    val a = "ABC".also {
        println(it) //输出:ABC
    }
    println(a) //输出:ABC
    a.let {
        println(it) //输出:ABC
    }
}
fun main(args: Array<String>) {
    testAlsoFun()
}

在上面的代码中,字符串“ABC”调用了also(),会打印出调用者 “ABC”.

4.3.5 with() 函数

源码如下:

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

我们看到with()函数传入了一个接收者对象receiver,然后使用该对 象receiver去调用传入的Lambda代码块receiver.block()。

  • 示例代码在 Android中我们初始化一个控件并给其赋值,Java 语言实现如下:
TextView text=(TextView)findViewById(R.id.tv_text)
text.setText("哈哈哈")
text.setTextSize(23)
  • kotlin 语言实现如下:
with(tv_text){
text="哈哈哈"
textSize=23
}

在上面的代码中,实现的功能是一样的,但是显然 kotlin 语言更加的简洁。

5.扩展函数与属性

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容