Kotlin基础 - 第六章函数

kotlin基础 - kotlin中的函数



#### [kotlin官方文档 https://www.kotlincn.net/docs/reference/](https://www.kotlincn.net/docs/reference/) ####

函数

为了是程序简洁明了,更具有逻辑性,我们通常的做法就是把相似的功能模块整合到一起,并设计成函数。函数是执行特定任务模块的代码,每个函数都有一个类型,你可以像使用Kotlin语言中其他类型一样使用函数类型,将函数作为参数传递给其他函数,或者将函数类型当做返回类型。你可以通过给定一个函数名称来标识它是什么,并在需要的时候使用该名称来调用函数以执行任务。

在Kotlin语言中,函数可以分为两类:一种是库和框架中的函数,要调用库函数或者使用框架中的函数,需要在单元顶部引用库或者框架的接口单元;另一种则是自定义函数,这类函数通常都是为了解决某一特别的问题而编写的,编写好函数之后,我们可以在程序的任意位置引用并调用它。学会编写和使用函数会使你的代码复杂度、可读性、可维护性都提高。

函数的定义和调用

要使用函数,必须先定义后调用。每一个函数都有一个函数名,用来表述函数执行的任务。要使用函数是,可以使用它的名称进行“调用”,并通过函数名来传递参数。函数形参是指函数定义时设定的参数名,函数实参是指调用函数时实际传递的参数,一个函数的实参必须始终和函数的形参顺序一致。函数定义需要使用关键字fun,其一般格式为:

	fun <函数名>(<参数名1> : <参数1类型>, <参数名2> : <参数2类型>...) : <函数返回类型> {
	    函数体...
	    return <返回值>
	}

例如下面的例子,我们定义了一个函数getPercent,从函数的名字我们可以看得出来,它的功能是获取一个数字的百分比显示内容。即,我给它一个浮点数number,它返回给我一个百分比显示的字符串,0.98 ——> 98%。我们需要一个参数来支出这个数字具体是什么,即设计形参number。参数通过函数名传进函数体李,通过函数处理之后需要一个返回处理结果,以表示百分显示的效果。所以有了一个返回值,这里是String类型。函数定义如下:

	/**
	 * 获取百分比显示的数字
	 */
	fun getPercent(number: Double): String {
	    val percentNumber = number * 100
	    return "$percentNumber %"
	}

我们可以直接通过使用函数名来调用这个函数,并且在调用的时候把参数传进去:

	println(getPercent(0.98))

函数getPercent将数字0.98传入到函数体内部,函数体内部的程序语句对数字进行处理判断,然后将处理后的的显示效果赋值给percentNumber变量,在通过return关键字将percentNumber的值返回给函数主体。

函数的形参

在Kotlin语言中,函数的形参和返回值是非常具有灵活性的,在需要的时候,你可以定义一个或者多个甚至选择性的省略。

在定义的时候可以忽略返回值,但一个定义了返回值的函数必须在函数体中设定返回值。对于一个定义了返回类型的函数来说,如果没有返回值,相当于没有指定函数的出口,这种情况下,编译器会报错的。事实上,在定义的时候忽略返回值等于是隐式声明函数的返回值是空。

如之前的查找“X”字符的例子:

	/**
	 * 查找x字符串
	 */
	fun findX(xArray : Array<String>) {
	    for (word in xArray) {
	        if (word == "x") {
	            println("find the 'x' word!")
	            return // 中断整个循环执行,退出函数
	        }
	    }
	    println("Can not find 'x' !")
	}

我们在查找到“X”之后,return一个空值,这里的return并没有给函数返回任何值。它的作用仅仅是告诉这个函数,在这个情况下return函数就该结束了。

参数默认值

当然,对于任何形参的定义来说,我们可以给其设定一个默认值。如果已经定义了默认值,那么调用函数时就可以省略该形参。和其他高级语言一样,为了避免遗漏参数或者参数传递二义性,请在函数形参列表的末尾放置带默认值的形参,不要在默认值的形参前放置。

	/**
	 * 给一个字符串拼接前缀和后缀
	 * 前缀默认值:***
	 * 后缀默认值:###
	 */
	fun catString(myString: String, prefix: String = "***", suffix: String = "###"): String{
	    return prefix + myString + suffix
	}
	
	// 使用的时候,可以忽略带有默认值的参数不传
	catString("hello")

在有定义默认值得情况下,当你没有指定外部名称时,Kotlin语言将为你定义的任何默认形参提供一个自动外部名,这个自动外部名和本地名类似。

可变个数参数(vararg)

可变个数形参是指可接受零个或者多个指定类型值得形参,你可以用它来传递任意数量的输入参数。声明可变个数形参需要用到vararg关键字,当参数传递进入函数体之后,参数在函数体内可以通过集合的形式访问。还有一点需要注意的是,函数最多可以有一个可变个数的形参,而且它必须出现在参数列表的最后。如:

	/**
	 * 求多个数字的和
	 */
	fun sumNumbers(vararg numbers : Double) : Double{
	    var result : Double = 0.0
	    for (number in numbers) {
	        result += number
	    }
	    return result
	}

	// 使用的时候,则可以传任意多个参数
	sumNumbers(1.2,2.56,3.14)

命名参数

如果别人第一次阅读你的代码,可能会不知道你使用的这个函数的形参的目的,那么使用外部形参名称就可以是你要表达的亿图更加的明确,上下文更加清晰。当然,如果每个形参名 的目的已经足够的简单清晰明确的话,那就无需在制定外部形参名。命名参数的方式,是在调用函数的时候带上这个形参的名称,并做赋值语句传入。

如,我们定义了一个格式化字符串的函数

	fun reformat(str: String,
	             normalizeCase: Boolean = true,
	             upperCaseFirstLetter: Boolean = true,
	             divideByCamelHumps: Boolean = false,
	             wordSeparator: Char = ' ') {
	    //...
	}

然而,当使用非默认参数调用它时,该调用看起来就像

	reformat(str, true, true, false, '_')

阅读起来相当的费劲,而且对于多个设置有默认值类型相同形参的函数,在调用使用上还很容易将参数传错。如果我们使用外部形参命名的方式,我们就可以写为:

	reformat(str,
	    normalizeCase = true,
	    upperCaseFirstLetter = true,
	    divideByCamelHumps = false,
	    wordSeparator = '_'
	)

使用上直观,阅读上清晰易懂,避免了传错参数导致的低级Bug。

返回 Unit 的函数

在函数中,所有定义了返回值的函数,我们都称之为显示函数。没有返回类型的函数,我们称之为隐式函数。

上文中我们又向大家介绍,在定义的时候忽略返回值等于是隐式声明函数的返回值是空。在Kotlin中,这种隐式返回的类型称之为:Unit。这个Unit类型的作用类似Java语言中的void类型。Unit是一种只有一个值——Unit的类型。这个值不需要显式返回。

即定义函数printHello具有Unit返回类型:

	fun printHello(name: String): Unit {
	   // ...
	}

和不写Unit返回类型的作用是一样的。

	fun printHello(name: String) {
	   // ...
	}

当然,在返回值return上,写不写Unit效果都一样。

	fun printHello(name: String): Unit {
	    return Unit// 和不写return
	            // 和return Unit作用一样
	
	}

单表达式函数

单表达式函数,即只有一个表单式的函数。当函数返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可

	fun doubleValue(x: Int): Int = x * 2

当返回值类型可由编译器推断时,显式声明返回类型是可选的

	fun doubleValue(x: Int) = x * 2

嵌套函数

在结构化编程盛行的年代,嵌套函数被广泛使用,在一个函数体中定义另外一个函数体就为嵌套函数。嵌套函数默认对外界是隐藏的,但仍然可以通过它们包裹的函数调用和使用它,举个例子:

	/**
	 * 嵌套函数demo
	 *
	 * 比较数字numberA和数字numberB的二次幂谁大
	 */
	fun compare(numberA: Int, numberB: Int) : Int{
	    var powerB = 0
	
	    // 嵌套函数,求一个数字的二次幂
	    fun power(num : Int) : Int{
	        return num * num
	    }
	    powerB = power(numberB)
	
	    if (numberA > powerB) {
	        return numberA
	    } else {
	        return powerB
	    }
	}
	
	
	fun main(args: Array<String>) {
	    // 报错!!! 
	    // 无法直接调用内部嵌套的函数
	    power()
	}

闭包

前言

先了解函数式编程(Functional Programming)

概念:它属于“结构化编程”的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。函数式编程语言最重要的基础是λ运算(Lambda表达式),λ运算的函数可以接受函数当做参数或返回值。

  • 对比函数式编程与面向对象编程

面向对象编程(Object-oriented programming,缩写OOP)
面向对象编程是一种具有对象概念的程序编程范型,它可能包含数据、属性、方法。它将对象作为程序的基本单元,将方法和数据封装其中,以提高软件的重用性、灵活性和扩展性。对象里的程序可以访问及经常修改对象相关联的数据。在面向对象编程里,计算机程序会被设计成彼此相关的对象。
面向对象编程的优点

  • 程序的结构化

面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想,相比传统的面向过程编程将程序看作一系列函数的集合这种无系统化和结构化的模式,面向对象编程将一系列关联性的数据、方法结构化,封装成类,通过类的对象进行方法、属性调用的方式,可以让编程者更加便于分析、设计和理解。

  • 程序的灵活性和可维护性

面向对象编程由于集成、封装、多态的特性,可以更好的设计出高内聚、低耦合的系统 结构,使得系统更灵活、更容易扩展,开发及维护成本更低。
面向对象编程的缺点

  • 运行效率较低

面向对象虽然开发效率高但是代码运行效率比起面向过程要低很多,这也限制了面向对象的使用场景不能包括那些对性能要求很苛刻的地方
多线程数据不安全
面向对象编程以数据为核心,所以在多线程并发编程中,多个线程同时操作数据的时候可能会导致数据修改的不确定性。
函数式编程优点(可以说就是为了解决面向对象的缺点问题而设计的)

  • 线程安全

在函数式编程中,数据全部都是不可变的,所以没有并发编程的问题,是多线程安全的,可以有效降低程序运行中所产生的副作用。对于快速迭代的项目来说,函数式编程可以实现函数与函数之间的热切换而不用担心数据的问题,因为它是以函数作为最小单位的,只要函数与函数的关系正确即可保证结果的正确性。

  • 代码可读性高

函数式编程的表达方式更加符合人类日常生活中的语法,代码可读性更强。实现同样的功能函数式编程所需要的代码比面向对象编程要少很多,代码更加简洁明晰。

  • 函数式编程的缺点
    • 运行速度更慢

由于所有的数据都是不可变的,所有的变量在程序运行期间都是一直存在的,非常占用运行资源。同时由于函数式的先天性设计导致性能一直不够。虽然现代的汗水编程语言使用了很多技巧,比如惰性计算等优化运行速度,但始终无法与面向对象相比,当然比面向过程的程序就更慢了

了解完函数式编程,再回归今天的主题——闭包

闭包是Kotlin语言的众多特性之一,对多数习惯使用了Java语言的开发者来说是一个很难理解的东西(实际上Java8也开始支持闭包特性),Kotlin中的闭包是一个功能性自包含模块,可以再代码中被当做参数传递或者直接使用。这个描述可能不太直观,你可能还是想问:“那么到底什么是闭包呢?闭包在函数中是以什么形式出现和使用的呢?”下面向大家介绍。

什么是闭包

一段程序代码通常由常量、变量和表达式组成,然后使用一对花括号“{}”来表示闭合,并包裹着这些代码,由这对花括号包裹着的代码块就是一个闭包。其实在签名我们也介绍了全局和嵌套函数就是一种特殊的闭包。这里,我们总结了一下,Kotlin语言中有三种闭包形式:全局函数、自嵌套函数、匿名函数体。

听着名词解释是挺让人费解,下面我们举个例子:

	fun main(args: Array<String>) {
	    // 执行test闭包的内容
	    test
	}

	// 定义一个比较测试闭包
	val test = if (5 > 3) {
	    println("yes")
	} else {
	    println("no")
	}
  • 先不说闭包的结构,从代码层面来看,上述逻辑我们都知道这段代码永远都只会输出“yes”。那么你可能会问:
      • 为什么能够将一个if逻辑语句赋值给test呢?
      • 为什么在main函数中单独写一个test就能执行test所指向的if逻辑呢?
      • 如果一个if逻辑快块是一个闭包?那么还有什么逻辑块可以是闭包呢?

下面,我们会一一给你解答。

为什么会设计闭包这种结构?

从上述的例子来说,我们可以看出来,其实定义一个函数就好了,为什么设计编程语言的人要设计闭包这么一个结构呢?这就得从作用域开始说起。变量的作用域无非就是两种:全局变量和局部变量。

全局变量

就Kotlin语言而言,函数内部可以直接读取全局变量。

	var n = 999;
	fun f1() {
	    println(n) // 打印999
	}
	f1()

局部变量

另一方面,在函数外部自然无法读取函数内的局部变量。

	fun f1(){
	    var n=999
	}
	println(n) // 报错!!! n是函数内局部变量,外部无法调用

那么,如何在外部调取局部的变量呢?答案就是——闭包。

这里,我们给闭包下一个定义:闭包就是能够读取其他函数内部变量的函数。

闭包的用途在哪里?

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。什么意思,没有听懂?下面,我们具体跑个例子试试,如:

首先看个简单的例子

		//这是一个返回值为一个函数的高阶函数
		fun justCount(age:Int): ( ) -> Unit {
		    var count = 0
		    return fun(){				//返回一个匿名函数,这个函数持有count的状态 
		        println("当前的数据为 ${++count} age_$age")
		    }
		}
		
		fun main(args: Array<String>) {
		    val count =   justCount(21) //函数调用,返回一个函数
		    count()						//调用这个返回的函数,此时makeFun持有makeFun()内部变量的状态
		    count()
		    count()
		}

运行结果

在比如一个稍微复杂一点的例子,实现斐波那契数列

	//斐波那契数列
	fun fibonacci(): () -> Long {
	    var first = 0L
	    var second = 1L
	    return fun(): Long {      		//返回返回值为Long类型的函数
	        var result = second
	        second += first
	        first = second - first
	        return result
	    }
	}
	
	
	fun main(args: Array<String>) {   	//此时,这个返回的函数fibo持有fibonnacci()函数内部变量的状态
	    val count = fibonacci()
	    count().println()
	    count().println()
	    count().println()
	    count().println()
	}

运行结果




使用迭代器实现斐波那契数列

	//使用迭代器实现斐波那契数列(这里就不是返回一个函数而是一个对象了)
	fun fibonacci2(): Iterable<Long> {
	    var first = 0L
	    var second = 1L
	
	    return Iterable {
	        object : LongIterator() {
	            override fun hasNext(): Boolean {
	                return true
	            }
	
	            override fun nextLong(): Long {
	                var result = second
	                second += first
	                first = second - first
	                return result
	            }
	        }
	    }
	}
	
	fun main(args: Array<String>) {
	    val iterable = fibonacci2()
	  
	
	    for (i in iterable) {
	        if (i > 5) break
	        i.println()
	    }
	}

运行结果

有没有发现闭包这点的好处,闭包就是在函数被创建的时候,存在的一个私有作用域,并且能够访问所有的父级作用域。每个功能模块我们都能够拆解到不同fun里,不同fun里的变量保持相互调用的可能性,相互独立还彼此不影响。我们可以函数式编程了!

广义上来说,在Kotlin语言之中,函数、条件语句、控制流语句、花括号逻辑块、Lambda表达式都可以称之为闭包,但通常情况下,我们所指的闭包都是在说Lambda表达式。
自执行闭包

自执行闭包就是在定义闭包的同时直接执行闭包,一般用于初始化上下文环境。 例如:

	{ x: Int, y: Int ->
	    println("${x + y}")
	}(1, 3)

复合函数

与高阶函数类似,复合函数的参数也是一个函数,不同的是,复合函数通过中缀表达式定义的函数,最终实现同参数及返回值的各种函数之间的复合运算。

举个例子,实现复合函数m(x) = f(g(x))的运算函数

	//首先自定义函数f(x)和g(x)
	//g(x)
	val add5 = {i:Int->i+5}
	//f(x)
	val multiplyBy2 = {i:Int -> i*2}
	
	//首先在学习复合函数之前,我们实现f(g(x))的运算会使用如下调用
	fun main(){
	    val result = multiplyBy2(add5(8))   //传入参数值8
	    println(result)
	}

运行结果

	26

下面使用kotlin中复合函数的功能实现该功能:

	//解析
	//infix 是中缀表达式,所以最终调用addThen的时候不需要用function.addThen的方式调用,这里addThen是自定义扩展函数
	//关于这里的几个泛型参数P1、P2、R,以m(x) = f(g(x))为例
	//P1为原始参数x,P2为g(x),R为最终返回值m(x)
	//关于Function1<P1,P2> 这里P1为参数,P2为函数返回值
	infix fun<P1,P2,R> Function1<P2,R>.andThen(function: Function1<P1,P2>):Function1<P1,R>{
	    return fun(p1:P1):R{
	        return this.invoke(function.invoke(p1))//这里是关键,定义了最终的函数表达式,这里为f(g(x)
	    }
	}
	
	//复合函数使用
	fun main(){
	    //普通函数调用
	    val result = multiplyBy2(add5(8))   //传入参数值8
	    println(result)
	    //复合函数定义
	    val fGx = multiplyBy2  andThen add5 //f(g(x)
	    println(fGx(8)) //预测结果:(8+5)*2 = 26
	}

运行结果

	26
	26

同理我们要实现m(x) = g(f(x)),因为f(x)与g(x)参数类型及返回值类型相同,我们同样可以使用andThen这个复合函数,此时我们只需调换他们的位置即可

	fun main(){
	    val gFx = add5  andThen multiplyBy2 //g(f(x)
	    println(gFx(8))     //预测结果:8*2+5 = 21
	}

运行结果

	21

现在我们实现多参复合函数m(x,y) = f(g(x,y)

	//f(x)
	val funFx = {i:Int -> i+2}
	//g(x,y)
	val funGxy = {i:Int,j:Int -> 3*i+100/j}
	//复合函数f(g(x,y))
	//因为g()有两个参数,所以这里使用Function2
	//同样的,P1,P2为对应的参数x,y,P3对应为g(x,y),R对应为f(g(x,y))
	infix fun<P1,P2,P3,R> Function1<P3,R>.complexFun(function: Function2<P1,P2,P3>):Function2<P1,P2,R>{
	    return fun(p1,p2):R{
	        return this.invoke(function.invoke(p1, p2))
	    }
	}
	
	//调用
	fun main() {
	    val funFxComplexFunGxy = funFx complexFun funGxy
	    println(funFxComplexFunGxy(3,5))      //预测运行结果:(3*3 + 100/5)+2 = 31
	}

运行结果

	31

举一反三,我们可以得到多参复合函数m(x,y) = g(f(x),y)

	infix fun <P1, P2, P3, R> Function2<P2, P3, R>.complexFun2(function: Function1<P1, P2>): Function2<P1, P3, R> {
	    return fun(p1, p3): R {
	        println("p1=$p1,p3=$p3")
	        return this.invoke(function.invoke(p1),p3)
	    }
	}

运行结果

	p1=2,p3=3
	45

总结

  • 复合函数利用中缀表达式(infix)实现类似加减乘除的运算方式形式

  • 只要函数它的参数及返回值类型符合我们定义的复合函数,都可以直接套用以实现复合调用

  • 复合函数定义需借助FunctionN来定义函数模式

总结来说

  • 使用符合函数可以大大的简化我们的代码

  • 使用符合函数可以轻松实现内部封装,我们仅将最外层复合函数暴露给用户,这样用户不需要关心函数的内部实现,仅仅关注函数的最终调用就可以了

  • 在写复合函数的时候,尽量先使用数学公式将复合函数表达出来,比如 m(x,y) = g(f(x),y),然后使用 Functionx<P,R> 函数去表示,其中 x 是参数的个数,最后一个泛型是返回值的类型

原文链接:https://blog.csdn.net/zsp765098084/article/details/91439054

Kotlin柯里化——函数调用链

传统的函数式

	/**定义一个Hello的方法*/
	//传入三个参数,返回一个Boolean类型的值
	fun hello(x: String, y: Long, z: Int): Boolean {
	    return x.isNotEmpty() && y > 0 && z % 2 != 0
	}



	
	fun main(args: Array<String>) {
	    hello("1", 2, 3).println()
	   
	}

函数运行结果

	true

使用柯里化改造后将会变成是这样的

	/**定义一个柯里化的方法
	 * 1.传入一个String类型的参数x
	 * 2.返回了一个函数,这个函数又传入了一个Long类型的y
	 * 3.又返回了一个函数,这个函数又传入了一个Int类型的z
	 * 柯里化就是完成:
	 * 有多个参数的函数,变换成一系列单参数的函数的变换
	 */

	fun curriedHello(x: String): (y: Long) -> (z: Int) -> Boolean {

		//每次变换,调用自己的匿名函数,同时函数减少一层,直到最后一个返回值为止
	    return fun(y: Long): (z: Int) -> Boolean {
	        return fun(z: Int): Boolean {
	            println("函数打印 x=$x , y = $y ,z = $z")
	            return x.isNotEmpty() && y > 0 && z % 2 != 0
	        }
	    }
	}



	
	fun main(args: Array<String>) {
	   
	    curriedHello("1")(2)(3).println()
	}

函数运行结果

	函数打印 x=1 , y = 2 ,z = 3
	true
柯里化更详细的例子(日志打印)

传统的写法是这样的

	/**
	 * 写一个打日志的东西
	 */
	fun log(tag: String, target: OutputStream, message: Any?) {
	    //打日志"\n"是换行的意思
	    target.write("[$tag] $message\n".toByteArray())
	}  


	fun main(args: Array<String>) {
	   	//直接可以调用
	  	log("Currying", System.out, "这里是输出的日志信息")
	}

输出结果

	[Currying] 这里是输出的日志信息

使用柯里化简化后的结果为

	/**
	 * 柯里化它
	 * 1.首先传tag
	 * 2.返回一个函数,函数再传target
	 * 3.返回一个函数,函数再传Any
	 */	
	fun log(tag: String) = fun(target: OutputStream) = fun(message: Any?) =
	    //打日志"\n"是换行的意思
	    target.write("[$tag] $message\n".toByteArray())
	
	fun main(args: Array<String>) {
	   	//调用变成每个参数一个括号,链式调用,通俗说就是函数调用函数
	  	log("Currying")(System.out)("这里是输出的日志信息")
	}

如果你没有看明白木关系的,展开来个你看

	fun log(tag: String): (target: OutputStream) -> (message: Any?) -> Unit {
	    return fun(target: OutputStream): (message: Any?) -> Unit {
	        return fun(message: Any?): Unit {
	            //打日志"\n"是换行的意思
	            target.write("[$tag] $message\n".toByteArray())
	        }
	    }
	}

输出结果

	[Currying] 这里是输出的日志信息

当然也可以用作扩展函数

	fun <P1,P2,P3,R> Function3<P1,P2,P3,R>.curried()
	    =fun (p1: P1) = fun (p2: P2) = fun (p3: P3) = this(p1,p2,p3)



	fun main(args: Array<String>) {
	   	//调用变成每个参数一个括号,链式调用,通俗说就是函数调用函数
	  	::log.curried()("Currying")(System.out)("这里是输出的日志信息")
	}

输出结果

	[Currying] 这里是输出的日志信息

偏函数

偏函数是在柯里化的基础上得来的,本身是柯里化函数的一部分,只不过原函数传入部分参数后得到的新函数

	/**
	 * 偏函数
	 * 1.偏函数是在科理化的基础上得来
	 * 2.原函数传入部分参数后得到的新函数
	 */
	
	//首先使用你喜欢的方式定义一个偏函数
	fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.curried() = fun(p1: P1) =fun(p2: P2) =fun(p3: P3)=this(p1,p2,p3)

	
	
	
	
	fun main(args: Array<String>) {
	  	//以前通常的调用,如果有还部分参数是固定的,比如一个app的日志信息前面的tag和文件的输出位置是固定的如果我们每一次都要重复的写很麻烦,有没有更简洁的解决方案呢,那是当然,就是使用偏函数嘛
	    ::log.curried()("Currying")(System.out)("这里是输出的日志信息")


		//之后利用柯里化的特性仅仅传入部分参数,得到一个新的函数
	    var partialFunction =::log.curried()("partialFunctionLog")(System.out)

		//每次需要打印日志的地方直接调用就可以了
	    partialFunction("日志信息1")
		partialFunction("日志信息2")
		partialFunction("日志信息3")
	}

输出结果

细心的朋友可能注意到了,上面的案例我们固定了一个函数前面的参数部分,但是对于固定参数在中间或者在末尾的函数我们又该怎么办呢

比如普通函数

val makeString = fun(byteArray: ByteArray, charset: Charset): String {
	return String(byteArray, charset)
}

如果我们想分别固定他们的第一个参数和第二个参数应该怎么做呢

val makeString = fun(byteArray: ByteArray, charset: Charset): String {
	return String(byteArray, charset)
}

//1.固定参数P2,传入可变参数P1
fun <P1, P2, R> Function2<P1, P2, R>.partial(p2: P2) = fun(p1: P1) = this(p1, p2)
//固定参数P1,传入可变参数P2
fun <P1, P2, R> Function2<P1, P2, R>.partial2(p1: P1) = fun(p2: P2) = this(p1, p2)

//2.定义扩展函数,将函数固定的参数部分传进去
val gbkStr = makeString.partial(charset("GBK"))
val gbkStr2 = makeString.partial2("中国你好".toByteArray(charset("GBK")))
fun main(args: Array<String>) {
    val string2ByteArray = "中国你好".toByteArray(charset("GBK"))
    gbkStr(string2ByteArray).println()
    gbkStr2(charset("GBK")).println()
}

最后我们完成一个小例子,统计一个文件的所有字符和他们出现的次数

	fun getAllStr() {
	    val text = File("build.gradle").readText()
	    val map = HashMap<Char, Int>()
	    text.toCharArray().filterNot { it.isWhitespace() }.forEach {
	        if (map[it] == null) map.put(it, 1)
	        else map[it] = 1 + map[it]!!
	    }
	   
	
	    map.forEach(::println)
	}
	
	
	fun main(args: Array<String>) {
	    getAllStr()
	}

代码还可以简化成这样的

	fun getAllStr() {
	    val text = File("build.gradle").readText()
	    val map = HashMap<Char, Int>()
	    text.toCharArray().filterNot { it.isWhitespace() }.groupBy {
    		it
		}.map { it.key to it.value.size }.forEach(::println)}
	   
	
	    map.forEach(::println)
	}
	
	
	fun main(args: Array<String>) {
	    getAllStr()
	}

运行结果

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值