五、泛型
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
- 默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在⼀个线程中计算,并且所有线程 会看到相同的值。
- 如果初始化委托的同步锁不是必需的,这样 多个线程 可以同时执⾏,那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数.
- ⽽如果你确定初始化将总是发⽣在 单个线程,那么你可以使⽤ 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.总结
对于 属性委托
- 如果是⼀个 只读属性(即 val 声明的),委托必须提供⼀个名为 getValue 的函数,该函数接受以下参数:
- thisRef ⸺ 必须与 属性所有者 类型(对于扩展属性⸺指被扩展的类型)相同或者是它的超类型,
- property ⸺ 必须是类型 KProperty<*> 或其超类型
- 这个函数必须返回与属性相同的类型(或其⼦类型)。
- 如果是⼀个 可变属性(即 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)
}