五、泛型

class Box<T>(t:T) {
    var value = t
}
//一般需要提供 类型参数
val box:Box<Int> = Box<Int>(1)

//但是若能推断出来,则允许省略
val box = Box(1)

1.型变

Java 类型系统中最棘⼿的部分之⼀是通配符类型(Effective Java)。首先Java中泛型是不型变的,这意味着List<String> 并不是 List<Object> 的子类型。

// Java 
interface Collection<E> …… {  
    void addAll(Collection<? extends E> items);     //不能是:void addAll(Collection<E> items); 
} 

// Java 上界 协变
通配符类型参数 ? extends E 表⽰此⽅法接受 E 的⼀些⼦类型 对象的集合,⽽不是 E 本⾝。
这意味着我们可以安全地从其中(该集合中的元素是 E 的⼦类的实例)读取 E,但不能写⼊,因为我们不知道什么对象符合那个未知的 E 的⼦类型。
反过来,该限制可以让Collection<String>表⽰ 为Collection<? extends Object>的⼦类型。 
简⽽⾔之,带 extends 限定(上界)的通配符类型使得类型是 协变的(covariant) 

// Java 下界 逆变性
如果只能从集合中 获取 项⽬,那么使⽤ String 的集合,并且从其中读取 Object 也没问题。
反过来,如果只能向集合中 放⼊ 项⽬,就可以⽤ Object 集合并向其中放⼊ String:
    在 Java 中有 List<? super String> 是 List<Object> 的⼀个超类。

并且对于 List<? super String> 你只能调⽤接受 String 作为参数的⽅法(例如,你可以调⽤ add(String) 或者 set(int, String))
当然 如果调⽤函数返回 List<T> 中的 T,你得到的并⾮⼀个 String ⽽是⼀个 Object。 

只能从中 读取 的对象为 ⽣产者:-extends
只能 写⼊ 的对象为 消费者:-super

声明处型变 out:仅生产 不消费

声明处型变:我们可以标注 Source 的 类型参数 T 来确保它仅从 Source<T> 成员中返回(⽣产),并从不被消费。为此,我们提供 out 修饰符:

abstract class Source<out T> {    
    abstract fun nextT(): T 
}

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs     //这个没问题,因为 T 是⼀个 out-参数
    // …… 
}

⼀般原则是:当⼀个类 C 的类型参数 T 被声明为 out 时,它就只能出现在 C 的成员的 输出-位置,但回报是 C<Base> 可以安全地作为  C<Derived>的超类。

简⽽⾔之,他们说类 C 是在参数 T 上是 协变的,或者说 T 是⼀个 协变的类型参数。 你可以认为 C 是 T 的 ⽣产者,⽽不是 T 的 消费者

out修饰符称为 型变注解,并且由于它在类型参数声明处提供,所以我们讲 声明处型变。 这与 Java 的 使⽤处型变相反,其类型⽤途通配符使得类型协变。

逆变 in:只可以被消费⽽不可以被生产

另外除了 out, Kotlin ⼜补充了⼀个型变注释:in。 它使得⼀个类型参数 逆变:只可以被消费,⽽不可以 被⽣产。

逆变类的⼀个很好的例⼦是 Comparable:

abstract class Comparable<in T> {    
     abstract fun compareTo(other: T): Int 
}

fun demo(x: Comparable<Number>) {    
    x.compareTo(1.0)        //1.0 拥有类型 Double,它是 Number 的⼦类型
    // 因此,我们可以将 x 赋给类型为 Comparable<Double> 的变量    
    val y: Comparable<Double> = x     //OK!
}

 in 和 out 两词是⾃解释的(因为它们已经在 C# 中成功使⽤很⻓时间了):消费者 in, ⽣产者 out

  • 支持协变(out)的类型参数只能用在输出位置:函数返回值、属性的get访问器以及委托参数的某些位置。协变的原理是把子类指向父类的关系,拿到泛型中。
  • 支持逆变(in)的类型参数只能用在输入位置:方法参数或委托参数的某些位置中出现。逆变的原理是把父类指向子类的关系,拿到泛型中。

2.类型投影

使⽤处型变:类型投影 (??不是很懂)

fun copy(from: Array<out Any>, to: Array<Any>) { 
    // …… 
} 
这⾥称为 类型投影:我们说from不仅仅是⼀个数组,⽽是⼀个受限制的(投影的)数组:我们只可以调⽤返回类型为参数 T 的⽅法,
如我们只能调⽤ get()。
      这就是我们的 使⽤处型变 的⽤法,并且是对应于 Java 的 Array<? extends Object>
      
fun fill(dest: Array<in String>, value: String) {    
    // …… 
}
Array<in String> 对应于 Java 的 Array<? super String>,

星投影 ??

Kotlin 提供了所谓的 星投影语法:

  • 对于 Foo <out T>,其中 T 是⼀个具有上界 TUpper 的协变类型参数,Foo <*>  等价于 Foo <out TUpper>。 这意味着当 T 未知时,你可以安全地从 Foo <*> 读 取 TUpper 的值。
  • 对于 Foo <in T>,其中 T 是⼀个逆变类型参数,Foo <*> 等价于 Foo <in Nothing>。 这意味着当 T 未知时,没有什么可以以安全的⽅式写⼊ Foo <*>。
  • 对于 Foo <T>,其中 T 是⼀个具有上界 TUpper 的不型变类型参数,Foo<*> 对于读取值时等价于 Foo<out TUpper> ⽽对于写值时等价于 Foo<in Nothing>。

如果泛型类型具有多个类型参数,则每个类型参数都可以单独投影。 例如,如果类型被声明为 interface Function <in T, out U> ,我们可以想 象以下星投影:

  • Function<*, String> 表⽰ Function<in Nothing, String>;
  • Function<Int, *> 表⽰ Function<Int, out Any?>; 
  • Function<*, *> 表⽰ Function<in Nothing, out Any?>。

注意 :星投影⾮常像 Java 的原始类型,但是安全。其中 Nothing 见:Kotlin简介 5

3.泛型函数

不仅类可以有类型参数。 函数也可以有。 类型参数要放在函数名称之前:

fun <T> singletonList(item: T): List<T> {    
    // …… 
} 

fun <T> T.basicToString() : String {  // 扩展函数
    // ……
}

要调⽤泛型函数,在调⽤处函数名之后 指定类型参数即可:
val l = singletonList<Int>(1)

4.泛型约束

能够替换给定类型参数的所有可能类型的集合可以由 泛型约束限制。

上界

最常⻅的约束类型是与 Java 的 extends 关键字对应的 上界:

fun <T : Comparable<T>> sort(list: List<T>) {    
    // …… 
}
 
冒号之后指定的类型是 上界:只有 Comparable<T> 的⼦类型可以替代 T。 例如 
sort(listOf(1, 2, 3))                   //OK。 Int 是 Comparable<Int> 的⼦类型
sort(listOf(HashMap<Int, String>()))    //错误:HashMap<Int, String>不是Comparable<HashMap<Int, String>>的⼦类型

默认的上界(如果没有声明)是 Any?。 
在尖括号中只能指定⼀个上界。 如果同⼀类型参数需要多个上界,我们需要⼀个单独的 where-⼦句:
fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>    
    where T : Comparable, T : Cloneable {  
        return list.filter { it > threshold }.map { it.clone() } 
    }

获得泛型信息

1. 通过匿名内部类获得泛型信息
object Generic1 {
    open class InnerClass<T>
    
    @JvmStatic    //添加这个是为了让 main 函数能够执行,且需要带有参数
    fun main(args: Array<String>) {
        val innerClass = object : InnerClass<Int>() {
            // 匿名内部类的声明在编译时进行,实例化在运行时进行
        }
        
        //本类的类名
        val typeClass = innerClass.javaClass.genericSuperclass
        println(typeClass)        >>>//com.zxx.mykotlin.fanxing.Generic1$InnerClass<java.lang.Integer>

        //接口
        val interChildClass = innerClass.javaClass.genericInterfaces
        println(interChildClass)        >>>//[Ljava.lang.reflect.Type;@1f32e575

        if (typeClass is ParameterizedType) {
            //第一个泛型 的类型
            val actualType = typeClass.actualTypeArguments[0]
            println(actualType)        >>>//class java.lang.Integer
        }
    }
}

2.通过反射获得泛型信息
fun main(args: Array<String>) {
    val child = GenericChild()
    child.printType()
}

open class Father<T>

class GenericChild : Father<String>() {
    fun printType() {
        val genType = javaClass.genericSuperclass
        println(genType)            >>>//包名.fanxing.Father<java.lang.String>
        val params = (genType as ParameterizedType).actualTypeArguments[0]
        println(params)            >>>//class java.lang.String
    }
}

六、委托

1.类委托:使用 by 关键字

实现继承的⼀个很好的替代⽅式。

装饰器模式
class DelegatingCollection<T>: Collection<T> {
      private val innerList = arrayListOf<T>()
      
      //以下编译器自动生成
      override val size: Int get() = innerList.size
      override fun isEmpty(): Boolean = innerList.isEmpty()
      override fun contains(element: T): Boolean = innerList.contains(element)
      override fun iterator(): Iterator<T> = innerList.iterator()
      override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(elements)
}

每当你实现一个接口时,你可以说 你通过 by 关键字把接口的实现委托给另一个对象。

class DelegatingCollection<T> ( innerList: Collection<T> = ArrayList<T>()): Collection<T> by innerList { 
    //.....
}

这个类中所有方法的实现都消失了。编译器会生成这些实现。(若想修改,复写即可)

例子:执行某种去重操作,通过比较尝试次数 来添加一个带有结果集合大小的元素

//一、简单的例子
interface Base {    
    fun print() 
}

class BaseImpl(val x: Int) : Base {    
    override fun print() { print(x) }     //实现接口,需要实现其方法
}
class Derived(b: Base) : Base by b        //若是没有 by,则此类需要实现接口的方法

//使用
val b = BaseImpl(10)    
Derived(b).print() // 输出 10

【Derived 的超类型列表中的 by-⼦句表⽰ b 将会在 Derived 中内部存储。 并且编译器将⽣成转发给 b 的所有 Base 的⽅法。
】


//二、复写
class CountingSet<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : 
    MutableCollection<T> by innerSet {       //1 委托MutableCollection的实现给innerSet (否则就要写一堆接口方法的实现)
    
		var objectsAdded = 0
    
    override fun add(element: T): Boolean {    //2 没有委托,提供了一个不同的实现
        objectsAdded++
        return innerSet.add(element)
    }
    override fun addAll(c: Collection<T>): Boolean {
         objectsAdded += c.size
         return innerSet.addAll(c)
    }
}

>>> val cset = CountingSet<Int>()
>>> cset.addAll(listOf(1, 1, 2))
>>> println("${cset.objectsAdded} objects were added, ${cset.size} remain")
3 objects were added, 2 remain


2.委托属性

有⼀些常⻅的属性类型,虽然我们可以在每次需要的时候⼿动实现它们,但是如果能够把他们只实现⼀次并放⼊⼀个库会更好。

  • 延迟属性(lazy properties) : 其值只在⾸次访问时计算,
  • 可观察属性(observable properties) : 监听器会收到有关此属性变更的通知,
  • 把多个属性储存在⼀个映射(map)中,⽽不是每个存在单独的字段中

语法是: val / var <属性名>: <类型> by <表达式> 在 by 后⾯的表达式是该 委托,因为属性对应的 get() 和 set() 会被委托给它的 getValue() 和 setValue() ⽅法。

//1.对于 var 属性:属性的委托不必实现任何的接⼝,但是需要提供⼀个 getValue() 和 setValue() 函数
class Example {
    var p: String = by Delegate()
}

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
     
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name} in $thisRef.'")    
    }     
}    

当我们从委托到⼀个 Delegate 实例的 p 读取时,将调⽤ Delegate 中的 getValue() 函数, 所以它第⼀个参数是读出 p 的对象、
第⼆个参数保 存了对 p ⾃⾝的描述 (例如你可以取它的名字)。 例如:
val e = Example() 
println(e.p)            //输出结果:
Example@33a17727, thank you for delegating ‘p’ to me!   

类似地,当我们给 p 赋值时,将调⽤ setValue() 函数。 前两个参数相同,第三个参数保存将要被赋予的值:
e.p = "NEW"         //输出结果:NEW has been assigned to ‘p’ in Example@33a17727.

3.标准委托

Kotlin 标准库为⼏种有⽤的委托提供了⼯⼚⽅法。

延迟属性 Lazy

lazy() 是接受⼀个 lambda 并返回⼀个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托:

第⼀次调⽤ get() 会执⾏已传递给 lazy() 的 lamda 表达式并记录结果, 后续调⽤ get() 只是返回记录的结果。

val lazyValue: String by lazy {    
    println("computed!")    
    "Hello" 
}
fun main(args: Array<String>) {    
    println(lazyValue)
    println(lazyValue) 
}
//输出: 
computed! 
Hello 
Hello

  1. 默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在⼀个线程中计算,并且所有线程 会看到相同的值。
  2. 如果初始化委托的同步锁不是必需的,这样 多个线程 可以同时执⾏,那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数.
  3. ⽽如果你确定初始化将总是发⽣在 单个线程,那么你可以使⽤ LazyThreadSafetyMode.NONE 模式, 它不会有任何线程安全的保证和相关的开销。


可观察属性 Observable

Delegates.observable() 接受两个参数:初始值和修改时处理程序(handler)。每当我们给属性赋值时会调⽤该处理程序(在赋值后执⾏)。它有三个参数:被赋值的属性、旧值和新值:

import kotlin.properties.Delegates
class User {    
    var name: String by Delegates.observable("<no name>") {        
        prop, old, new -> println("$old -> $new")    
    }
}

fun main(args: Array<String>) {    
    val user = User()    
    user.name = "first"   
    user.name = "second" 
}

//输出: 
<no name> -> first 
first -> second

如果你想能够截获⼀个赋值并“否决”它,就使⽤ vetoable() 取代 observable()。 在属性被赋新值⽣效之前会调⽤传递给 vetoable 的处理程序。

把属性储存在映射中

像解析 JSON 或者做其他“动态”事情的应⽤中,你可以使⽤映射实例(如map)⾃⾝作为委托来实现委托属性。

class User(val map: Map<String, Any?>) { 
    //【这里属性 必须是 val】   
    val name: String by map    
    val age: Int     by map 
} 
//构造函数接受⼀个映射(map)参数:
val user = User( mapOf(    
    "name" to "John Doe",    
    "age"  to 25 
))
println(user.name) // Prints "John Doe" 
println(user.age)  // Prints 25


//这也适⽤于 var 属性,如果把只读的 Map 换成 MutableMap 的话
class MutableUser(val map: MutableMap<String, Any?>) {    
    var name: String by map    
    var age: Int     by map 
}

局部委托属性(⾃ 1.1 起)

你可以将局部变量声明为委托属性。 例如,你可以使⼀个局部变量惰性初始化:

fun example(computeFoo: () -> Foo) {    
   val memoizedFoo by lazy(computeFoo)
   if (someCondition && memoizedFoo.isValid()) {        
       memoizedFoo.doSomething()    
   }
}

memoizedFoo 变量只会在第⼀次访问时计算。 如果 someCondition 失败,那么该变量根本不会计算。

4.总结

对于 属性委托

  1. 如果是⼀个 只读属性(即 val 声明的),委托必须提供⼀个名为 getValue 的函数,该函数接受以下参数:
  • thisRef ⸺ 必须与 属性所有者 类型(对于扩展属性⸺指被扩展的类型)相同或者是它的超类型,
  • property ⸺ 必须是类型 KProperty<*> 或其超类型
  • 这个函数必须返回与属性相同的类型(或其⼦类型)。
  1. 如果是⼀个 可变属性(即 var 声明的),委托必须 提供 setValue 和 getValue 的函数,该函数接受以下参数:
  • thisRef ⸺ 同上,
  • property ⸺ 同上,
  • new value ⸺ 必须和属性同类型或者是它的超类型。
  • getValue() 或/和 setValue() 函数可以通过委托类的成员函数提供 或者由扩展函数提供。 当你需要委托属性到原本未提供的这些函数的对象时后者会更便利。 两函数都需要⽤ operator 关键字来进⾏标记。

七、高阶函数 与 lambda 表达式

1.高阶函数

⾼阶函数是将函数 ⽤作参数 或 返回值 的函数。

//⼀个很好的例⼦是 lock(),它接受⼀个锁对象和⼀个函数,获取锁,运⾏函数并释放锁:
fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body()
    } finally {
        lock.unlock()
    }
} 

// body 拥有函数类型:() -> T, 所以它应该是⼀个不带参数并且返回 T 类型值的函数。 
//它在 try-代码块内部调⽤、被 lock 保护,其结果由lock()函数返回。

//调用
fun toBeSynchronized() = sharedResource.operation()    //创建一个函数
val result = lock(lock, ::toBeSynchronized)            //调用lock函数,并把上述函数当做参数传递进去

//lambda形式
val result = lock(lock, { sharedResource.operation() }) 

//-------------------------------
//⾼阶函数的另⼀个例⼦是 map():
//map是扩展函数:这里transform是个参数为T,返回R的函数
fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
    val result = arrayListOf<R>()
    for (item in this) {
        result.add(transform(item)) //this是List,所以item是T,调用transform函数返回R,添加到result中
    }
    return result
}

//调用
val doubled = ints.map { value -> value * 2 }  // lambda 是该调⽤的唯⼀参数,则调⽤中的圆括号可以完全省略。
//函数作为最后一个参数
fun sum(a: Int, b: Int, term: (Int) -> Int): Int {
    var sum = 0
    for (i in a..b) {
        sum += term(i)
    }
    return sum
}

fun main(args: Array<String>) {
    val square = { x: Int -> x * x }
    println(sum(1, 10, square))   //  385
    println(sum(1,3){x->x+x})    //看下方约定1:返回12
}

//函数作为返回值
fun sum(type: String): (Int, Int) -> Int {
    val identity = { x: Int -> x }
    val square = { x: Int -> x * x }

    return when (type) {
        "square" -> return { a, b -> sum(a, b, square) }
        else -> { a, b -> sum(a, b, identity) }
    }
}

fun main(args: Array<String>) {
    var squareFunction = sum("square")
    println(squareFunction(1,3))
}

2.lambda表达式

  • lambda 表达式总是被⼤括号括着,
  • 其参数(如果有的话)在 -> 之前声明(参数类型可以省略)
  • 函数体(如果存在的话)在 -> 后⾯。
  • 如果不限定显示返回一个值,默认隐式返回最后一个表达式的值
val sum = { x: Int, y: Int -> x + y }		//其实就是下面的
《==》
val sum: (Int, Int) -> Int = { x, y -> x + y }	//参数Int,Int,返回值Int


ints.filter {    
    val shouldFilter = it > 0
    shouldFilter  			//默认隐式返回shouldFilter
} 
《==》
ints.filter {
    val shouldFilter = it > 0
    return@filter shouldFilter
}

约定1:lambda作为最后参数

在 Kotlin 中有⼀个约定,如果函数的最后⼀个参数是⼀个函数,并且你传递⼀个 lambda 表达式作为相应的参数,你可以在圆括号之外指定它:

//所以上述 lambda 形式,变为以下:
lock (lock) {    
	sharedResource.operation() 
}

约定2:it-单个参数的隐式名称

另⼀个有⽤的约定是,如果函数字⾯值只有⼀个参数, 那么它的声明可以省略(连同 ->) ,其名称是 it

ints.map { value -> value * 2 }
《==》
ints.map { it * 2 }

//------集合、数组的API 中
strings.filter { it.length == 5 } .sortBy { it } .map { it.toUpperCase() }

约定3:下划线 _ ⽤于未使⽤的变量

如果 lambda 表达式的参数未使⽤,那么可以⽤下划线取代其名称:

map.forEach { _, value -> println("$value!") }

3.内联函数 inline

使⽤内联函数有时能提⾼⾼阶函数的性能。(inline 1.Kotlin标准函数)

使⽤⾼阶函数会带来⼀些运⾏时的效率损失:每⼀个函数都是⼀个对象,并且会捕获⼀个闭包。 即那些在函数体内会访问到的变量。 内存分配(对于函数对象和类)和虚拟调⽤会引⼊运⾏时间开销。

inline 修饰符影响函数本⾝和传给它的 lambda 表达式:所有这些都将内联 到调⽤处。

内联可能导致⽣成的代码增加,但是如果我们使⽤得当(不内联⼤函数),它将在 性能上有所提升,尤其是在循环中“超多态”调⽤处。

禁⽤内联 noinline

如果你传给⼀个内联函数的参数 只想⼀些被内联,你可以⽤ noinline 修饰符标记 ⼀些函数参数:

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {    
  // …… 
}

可以内联的 lambda 表达式只能在内联函数内部调⽤或者作为可内联的参数传递, 但是 noinline 的可以以任何我们喜欢的⽅式操作:存储在字段中、 传送它等等。

⾮局部返回

一般通过return 退出一个命名函数 或 匿名函数,要退出 lambda 需要使用标签,并且在 lambda 内禁止使用 裸return 。因为lambda 不能包含他的函数返回。

但是如果 lambda 表达式传给的函数是内联的,该 return 也可以内联,所以它是允许的。但是这种返回(lambda 表达式中的return)是退出包含它的函数:称为 ⾮局部返回

fun foo51() {
    println("11111")
    foo_in {
        println("22222")        
        //return    //(不使用内联)这是不允许的,需要添加标签
        return      //(使用内联)退出整个函数--⾮局部返回,所以输出 11111 22222
        
        //return@foo_in    //(内联与非内联都可)这是退出当前foo_in函数,所以输出 11111 22222 44444 33333
    }
    println("33333")
}

//非内联
fun foo_in(function: () -> Unit) {
    function()
    println("44444")
}

//内联,就是直接替换调用处的代码
inline fun foo_in(function: () -> Unit) {
    function()
    println("44444")
}

请注意,⼀些内联函数可能调⽤传给它们的不是直接来⾃函数体、⽽是来⾃另⼀个执⾏ 上下⽂的 lambda 表达式参数,例如来⾃局部对象或嵌套函数。 在这种情况下,该 lambda 表达式中 也不允许⾮局部控制流。 为了标识这种情况,该 lambda 表达式参数需要 ⽤ crossinline 修饰符标记:

具体化的类型参数 reified

(就像 findviewById<..>(..))

//需要访问⼀个作为参数传给我们的⼀个类型
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {  
    var p = parent
    ....
    @Suppress("UNCHECKED_CAST")
    return p as T? 
} 
//所以调用处
treeNode.findParentOfType(MyTreeNode::class.java)

//而我们只是想 传⼀个类型给该函数,即像这样调⽤它:
treeNode.findParentOfType<MyTreeNode>() 

//内联函数⽀持 具体化的类型参数,于是我们可以这样写:(所以这是就不需要反射了:MyTreeNode::class.java
)
inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    ....
    return p as T? 
} 

//但我们仍然可以对⼀个具体化的类型参数 使用反射
inline fun <reified T> membersOf() = T::class.members    //反射
println(membersOf<StringBuilder>().joinToString("\n"))

普通的函数(未标记为内联函数的)不能有具体化参数。

不具有运⾏时表⽰的类型(例如⾮具体化的类型参数或者类似于Nothing的虚构类型) 不能⽤作 具体化的类型参数的实参。

内联属性(⾃ 1.1 起)

inline 修饰符可⽤于没有幕后字段的属性的访问器。 你可以标注独⽴的属性访问器:

val foo: Foo    
    inline get() = Foo()
var bar: Bar
    get() = ……
    inline set(v) { …… }

你也可以标注整个属性,将它的两个访问器都标记为内联:

inline var bar: Bar
    get() = ……
    set(v) { …… }

在调⽤处,内联访问器如同内联函数⼀样内联。

4.匿名函数 与 Lambda 表达式

⼀个 lambda 表达式或匿名函数是⼀个“函数字⾯值” ,即⼀个未声明的函数, 但⽴即做为表达式传递。

max(strings, { a, b -> a.length < b.length })

函数 max 是⼀个⾼阶函数,换句话说它接受⼀个函数作为第⼆个参数。其第⼆个参数是⼀个表达式,它本⾝是⼀个函数,即函数字⾯值。

写成函数的话,它相当于:

fun compare(a: String, b: String): Boolean = a.length < b.length

对于接受另⼀个函数作为参数的函数,我们必须为 该参数 指定函数类型。 例如上述函数 max 定义如下:

fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
    var max: T? = null
    for (it in collection)
        if (max == null || less(max, it))    //less 作为⼀个函数使⽤:通过传⼊两个 T 类型的参数来调⽤。 
             max = it
    return max
} 
//参数 less 的类型是 (T, T) -> Boolean,即⼀个接受两个类型T的参数并返回⼀个布尔值的函数: 
//    如果第⼀个参数⼩于第⼆个那么该函数返回 true。 

匿名函数的返回类型推断机制与正常函数⼀样:对于具有表达式函数体的匿名函数将⾃动 推断返回类型,⽽具有代码块函数体的返回类型必须显式 指定 (或者已假定为 Unit)。

请注意,匿名函数参数总是在括号内传递。 允许将函数 留在圆括号外的简写语法仅适⽤于 lambda 表达式。

Lambda表达式和匿名函数之间的另⼀个区别是 ⾮局部返回的⾏为。 ⼀个不带限定标签的 return 语句 总是在⽤ fun 关键字声明的函数中返回。 这意味着 lambda 表达式中的 return 将从包含它的函数返回,⽽匿名函数中的 return 将从匿名函数⾃⾝返回。

5.闭包

Lambda 表达式 或者 匿名函数(以及 局部函数 和 对象表达式) 可以访问其 闭包 ,即在外部作⽤域中声明的变量。 与 Java 不同的是可以修改闭包中捕获的 变量:

var sum = 0 
ints.filter { it > 0 }.forEach {    
    sum += it
} 
print(sum)

6.带接收者的函数字⾯值

使⽤指定的 接收者对象 调⽤函数字⾯值,在函数字⾯值的函数体中,可以调⽤该接收者对象上的⽅法⽽⽆需任何额外的限定符。 这类似于扩展函数,它允你在函数体内访问接收者对象的成员。

//很像 扩展函数
val sum = fun Int.(other: Int): Int = this + other     //接收者对象:Int,this:访问接收者对象的成员
val result = 2.sum(5)
println("结果:$result")    //结果:7

7.解构声明

集合是setOf,listOf,映射是有key-value值得mapOf(只读),mutableMapOf(读写)

能够解构说明的具有 component 函数( component1()...componentN() )。如映射。可以返回 2至 n 个变量(数据类是kotlin中简洁的实现方式,因为其自动声明 component() 函数。),当然1个也行(普通函数)

//---------------实例-----------------
fun main() {
    for ((a, b) in mapOf("a" to 2)) {
        println("Key:$a, Value:$b")
    }

    val (name, age) = function()
    println("Name:$name, Age:$age")
}

//一般类也不行
data class Result(
    var name: String? = null,
    var age: Int? = 0,
)

fun function(): Result {
    val name = "zzz"
    val age = 12
    //各种计算
    return Result(name, age)
}

//--------------为了能够解构:需要以下:-----------------
1. 通过提供⼀个 iterator() 函数将映射表⽰为⼀个值的序列
2. 通过提供函数 component1() 和 component2() 来将每个元素呈现为⼀对

//对于 map 。标准库提供了扩展:请注意,componentN() 函数需要⽤ operator 关键字标记,以允许在解构声明中使⽤它们。 
operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator() 
operator fun <K, V> Map.Entry<K, V>.component1() = getKey() 
operator fun <K, V> Map.Entry<K, V>.component2() = getValue()


//-----------lambda 参数的解构---------------------
如果lambda表达式具有 Pair 类型(或者Map.Entry 或者其他具有 componentN 函数的类型)参数,那么可以对那一个单参数进行解构
map.mapValues { entry -> "${entry.value}!" } 
map.mapValues { (key, value) -> "$value!" }         //解构

注意声明两个参数和声明⼀个(解构对)来取代单个参数之间的区别:
 { a //-> …… }           //⼀个参数
 { a, b //-> …… }        //两个参数
 { (a, b) //-> …… }      //⼀个解构对
 { (a, b), c //-> …… }   //⼀个解构对以及其他参数 
 
如果解构的参数中的⼀个组件未使⽤,那么可以将其替换为下划线,以避免编造其名称:
map.mapValues { (_, value) -> "$value!" } 

你可以指定整个解构的参数的类型或者分别指定特定组件的类型:
map.mapValues { (_, value): Map.Entry<Int, String> -> "$value!" }
map.mapValues { (_, value: String) -> "$value!" }


从 Kotlin 中的函数返回多个值

1.使用数据类
//从函数返回多个值的惯用方式是定义数据类并从函数返回其实例。然后,您可以使用调用函数内的解构声明解压值。
data class Person(var name: String, var age: Int, var gender: Char)
 
fun fetchPersonDetails(): Person {
    val name = "Riya"
    val age = 18
    val gender = 'M' 
    return Person(name, age, gender)
}
 
fun main() {
    val (name, age, gender) = fetchPersonDetails()    // 解构声明 
    println("($name, $age, $gender)")                // (Riya, 18, M)
}

2.使用 Pair/Triple 类
//Kotlin 具有通用的 Pair 和 Triple 类型,它们可以从函数返回两个或三个值。
//请注意,最好为您的数据正确命名,并且我们应该使用数据类来返回更多值。
fun fetchPersonDetails(): Triple<String, Int, Char> {
    val name = "Riya"
    val age = 18
    val gender = 'M' 
    return Triple(name, age, gender)
}
 
fun main() {
    val (name, age, gender) = fetchPersonDetails() 
    println("($name, $age, $gender)")        // (Riya, 18, M)
}

3.返回一个数组
//将值累积在数组中并从函数中返回。请注意,这不会为对象数组提供类型安全性或将字段信息传递给调用者。
fun fetchPersonDetails(): Array<Any> {
    val name = "Riya"
    val age = 18
    val gender = 'M' 
    return arrayOf(name, age, gender)
}
 
fun main() {
    val (name, age, gender) = fetchPersonDetails() 
    println("($name, $age, $gender)")        // (Riya, 18, M)
}