前言
继续深入学习,并记录相关的知识点。
高阶函数
高阶函数是指将函数作为参数或者返回值的函数。
介绍:Kotlin使用类似 (Int) -> String
的一系列函数类型来处理高阶函数的声明。例如: val onclick:() ->Unit = ....
这些函数有下面几种特点
- 所有函数类型都一个圆括号括起来的参数类型列表,以及一个返回类型:例如
(A,B) -> C
表示两个接受类型,分别为A
和B
,并返回一个C
类型的值。 - 传参可以为空: 例如
()-> C
表示没有接受类型,但是有一个返回类型为C
的值。 - 返回类型为空时不能省略:普通函数返回值为空的时候,我们可以省略
Unit
关键字,如果是高阶函数则不行。实例 :(A,B)->Unit
表示两个类型传参,返回值为空。 - 函数类型可以有一个额外的接收者类型,需要在表达式的点号之前指定。实例:
A.(B) ->C
表示可以在A
的接收者对象上以一个B
类型参数来调用并返回一个C
类型的值。 - 挂起函数属于特殊种类的函数类型,它的表示法中有一个
suspend
修饰符,例如supspend() -> Unit
或者suspend A.(B) -> C
。
函数类型标识符可以选择性的包含函数的参数名:(x :Int ,y:Int)-> Point
。
如果需要函数类型指定为可以为空: ((Int,Int)-> Int)?
需要用括号包裹起来。
那么,问题来了。高阶函数是如何实例化?
1.使用函数字面值的代码块实现:
- Lambda表达式 :
{ a, b -> a + b }
- 匿名函数 :
fun(s: String): Int { return s.toIntOrNull() ?: 0 }
2.使用已有声明的可调用引用:
- 顶层,局部,成员,扩展函数:
::isOdd, String::toInt
- 顶层,成员,扩展属性:
List<Int> :: size
- 构造函数:
::Regex
- 指向特定实例成员的绑定的可调用引用:
foo::toString
3.使用实现函数类型接口的自定义类的实例:
class IntTransformer: (Int) -> Int {
override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()
函数式(SAM)接口
介绍: 在Kotlin之中,只有一个抽象方法的接口被称为函数式接口,或者SAM(单一抽象方法)接口。SAM的全称:Single abstract method
函数式接口可以有多个非抽象的成员(函数,属性)。但是只能有一个抽象成员。
只有函数式接口 可以在interface
前面加上fun
修饰符
实例1:
fun interface KK {
fun invoke()
}
因为Kotlin针对SAM接口提供了一种简洁的转换。
通常情况下,我们实现接口后,需要将它的抽象方法实例化。
实例2:
fun interface KK {
fun invoke(i: Int): Boolean
}
fun main(array: Array<String>) {
val ee = object : KK {
override fun invoke(i: Int): Boolean {
return i % 2 == 0
}
}
}
但是针对只有一个抽象方法的接口。我们可以将上面的方法直接简写为
实例3:
fun interface KK {
fun invoke(i: Int): Boolean
}
fun main(array: Array<String>) {
val ee = KK { it % 2 == 0 }
}
这两个在程序上来说,结果是等价的。
it 是一个关键字,可以用来代替只有一个传参的参数。
如果接口传的两个参数
实例4:
fun interface KK {
fun invoke(i: Int, i2: Int): Boolean
}
fun main(array: Array<String>) {
val ee = KK { i, i2 -> i > i2 }
}
类型别名(关键字:typealias)
介绍:类型别名为现有类型提供替代名称。如果类型名称太长,我们可以引入较短的名称,并使用新的名称,替代原类名称。
类型别名不会创建新的类型。他们只是给现有类型取了另一个名称而已
它有助于缩短较长的泛型类型名称。可以让我们更直观的了解函数想表达的意义
实例1:
interface Test {
fun test1(restaurant: Array<String>): Array<String> {
return arrayOf("Z同学", "Y同学")
}
针对这种情况 我们可以将Array<String>
进行别名定义
typealias UserList = Array<String>
interface Test {
//第一种 普通模式
fun test1(restaurant: Array<String>): Array<String> {
return arrayOf("Z同学", "Y同学")
}
//第二种 使用了别名
fun test2(restaurant: UserList): UserList {
return arrayOf("Z同学", "Y同学")
}
}
我们除了可以在参数类型进行别名定义,我们还可以针对Lambda表达式定义别名。
实例2:
//定义一个不参数从,并返回String 的的函数,它的别名为aliasdSuppliser
typealias aliasedSupplier = () -> String
fun writeAliased(supper: aliasedSupplier) {
println(supper.invoke())
}
fun main(array: Array<String>) {
writeAliased { "Z同学" }
}
//输出
Z同学
关于别名可以使用的场景:
// Classes and Interfaces (类和接口)
typealias RegularExpression = String
typealias IntentData = Parcelable
// Nullable types (可空类型)
typealias MaybeString = String?
// Generics with Type Parameters (类型参数泛型)
typealias MultivaluedMap<K, V> = HashMap<K, List<V>>
typealias Lookup<T> = HashMap<T, T>
// Generics with Concrete Type Arguments (混合类型参数泛型)
typealias Users = ArrayList<User>
// Type Projections (类型投影)
typealias Strings = Array<out String>
typealias OutArray<T> = Array<out T>
typealias AnyIterable = Iterable<*>
// Objects (including Companion Objects) (对象,包括伴生对象)
typealias RegexUtil = Regex.Companion
// Function Types (函数类型)
typealias ClickHandler = (View) -> Unit
// Lambda with Receiver (带接收者的Lambda)
typealias IntentInitializer = Intent.() -> Unit
// Nested Classes and Interfaces (嵌套类和接口)
typealias NotificationBuilder = NotificationCompat.Builder
typealias OnPermissionResult = ActivityCompat.OnRequestPermissionsResultCallback
// Enums (枚举类)
typealias Direction = kotlin.io.FileWalkDirection
// (but you cannot alias a single enum *entry*)
// Annotation (注解)
typealias Multifile = JvmMultifileClass
其实别名就是用来代替底层类型的实现。
可以精简代码的复杂程度。
别名其实主要是用来代替数据类型的写法它的使用场景是:
- 在声明变量类型,参数类型和返回值类型的时候。
- 在作为类型参数约束和类型参数的时候
- 在使用比较类型
is
或者强转类型as
的时候 - 在获得函数引用的时候
注意: typealias
只能定义在kt文件中。它不能内嵌到一个类,对象,接口或者其他的代码块中。否则会提示错误:Nested and local type aliases are not supported
同时:针对typealias 也可以添加访问权限修饰符: internal和private
Import As 引入类的别名
介绍:在Kotlin之中有一个很类型别名很类似的概念,就是import as
它主要是在我们导入了相同名称的类时, 避免第二个类名称写的时候过长问题。我们可以给这个导入的类 创建一个别名而已。
实例:
import cn.zinyan.demo.kotlin.d.Test1 as TT
import cn.zinyan.demo.kotlin.e.Test1
fun main(array: Array<String>) {
var te1 = TT()
var te2 = Test1()
}
例如我们在两个包中都存在Test1
对象。但是我们在当前类下,同时使用这两个Test1
的时候 。我们可以通过as 给改个别名,避免冲突
中缀函数(关键字:infix)
介绍:我们在使用mapOf
的时候,使用了很多 to
。
val map = mapOf(1 to "one", 2 to "two", 3 to "three")
这种写法就是因为mapOf使用了中缀函数造成的。
在mapOf的源码中我们可以看到:public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
使用了infix
关键字
infix函数必须满足以下几个条件:
- 必须是成员函数或者扩展函数。
- 必须只有一个参数
- 其参数不能接受可变数量的参数,且不能有默认值。
实例:
infix fun String.builder(str: String): String {
return StringBuilder(this).append(str).toString()
}
fun main(array: Array<String>) {
var name = "zin".builder("yan")
println(name)
//也可以这样表示 两个方法等效
name = "zin" builder "yang"
}
输出:
zinyan
注意:
中缀函数调用的优先级低于算数操作符,类型转换以及rangeTo
操作符
而高于布尔操作符:&& 与 || , is- 与in- 检测以及其他一些操作符。
那么中缀函数有什么用呢?
它就是用来省略调用函数时候的 :".
和()
的"
Kotlin之中常见的使用中缀函数的还有for (i in 0 until 10 step 2 ) { .. }
他的完整使用应该是: 0.until(10).step(2)
--中缀之后--> 0 until 10 step 2
可变数量的参数(关键字:varargs)
介绍:在java之中,我们如果传参数量是可变数组的话我们通常这样定义
public void str(String... str){
}
使用...
来代替。而在Kotlin之中使用vararg关键字来修饰。
实例:
class Test {
fun asList(vararg ss: String) {
for (t in ss) {
println("t :$t")
}
}
}
fun main(array: Array<String>) {
var ss = Test()
ss.asList("A", "B", "C")
}
输出:
t :A
t :B
t :C
针对可变参数,我们还有一种情况叫做伸展操作符,就是在数组前面添加*
号
接着上面的实例补充
class Test {
fun asList(vararg ss: String) {
for (t in ss) {
println("t :$t")
}
}
}
fun main(array: Array<String>) {
var ss = Test()
var a = arrayOf("a1", "a2", "a3", "a4")
ss.asList("A", "B", "C", *a)
}
输出:
t :A
t :B
t :C
t :a1
t :a2
t :a3
t :a4
评论区