Kotlin入门(七)

本文介绍了Kotlin中的函数式接口,包括如何使用fun接口声明和SAM转换,使得代码更加简洁。同时,讲解了Kotlin的可见性修饰符及其在包、类和接口中的应用。此外,还详细阐述了Kotlin的扩展功能,如扩展函数和扩展属性,以及它们的使用场景和规则,强调了扩展函数的静态分发特性。最后,讨论了伴生对象的扩展以及声明为成员的扩展。
摘要由CSDN通过智能技术生成

Kotlin入门(八).

函数式(SAM)接口

只有一个抽象方法的接口称为函数式接口或 SAM(单一抽象方法)接口。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。
可以用 fun 修饰符在 Kotlin 中声明一个函数式接口。

fun interface KRunnable {
   fun invoke()
}

SAM 转换

对于函数式接口,可以通过 lambda 表达式实现 SAM 转换,从而使代码更简洁、更有可读性。
使用 lambda 表达式可以替代手动创建实现函数式接口的类。 通过 SAM 转换, Kotlin 可以将其签名与接口的单个抽象方法的签名匹配的任何 lambda 表达式转换为实现该接口的类的实例。
例如,有这样一个 Kotlin 函数式接口:

fun interface IntPredicate {
   fun accept(i: Int): Boolean
}

如果不使用 SAM 转换,那么你需要像这样编写代码:

// 创建一个类的实例
val isEven = object : IntPredicate {
   override fun accept(i: Int): Boolean {
       return i % 2 == 0
   }
}

通过利用 Kotlin 的 SAM 转换,可以改为以下等效代码:

// 通过 lambda 表达式创建一个实例
val isEven = IntPredicate { it % 2 == 0 }
fun main() {
    val isE = IntPred { i -> i % 2 == 0 }
    println("输出结果:${isE.accept(3)}")
}

fun interface IntPred {
    fun accept(i: Int): Boolean
}
//输出结果:false

可见性修饰符

类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有 可见性修饰符。 (getter 总是与属性有着相同的可见性。) 在 Kotlin 中有这四个可见性修饰符:private、 protected、 internal 和 public。 如果没有显式指定修饰符的话,默认可见性是 public。

函数、属性和类、对象和接口可以在顶层声明,即直接在包内:

// 文件名:example.kt
package foo

fun baz() { …… }
class Bar { …… }
  • 如果你不指定任何可见性修饰符,默认为 public,这意味着你的声明将随处可见;
  • 如果你声明为 private,它只会在声明它的文件内可见;
  • 如果你声明为 internal,它会在相同模块内随处可见;
  • protected 不适用于顶层声明。

注意:要使用另一包中可见的顶层声明,仍需将其导入进来。
例如:

// 文件名:example.kt
package foo

private fun foo() { …… } // 在 example.kt 内可见

public var bar: Int = 5 // 该属性随处可见
    private set         // setter 只在 example.kt 内可见
    
internal val baz = 6    // 相同模块内可见

类和接口

对于类内部声明的成员:

  • private 意味着只在这个类内部(包含其所有成员)可见;
  • protected—— 和 private一样 + 在子类中可见。
  • internal —— 能见到类声明的 本模块内 的任何客户端都可见其 internal 成员;
  • public —— 能见到类声明的任何客户端都可见其 public 成员。
    请注意在 Kotlin 中,外部类不能访问内部类的 private 成员。
    如果你覆盖一个 protected 成员并且没有显式指定其可见性,该成员还会是 protected 可见性。
    例子:
open class Outer {
    private val a = 1
    protected open val b = 2
    internal val c = 3
    val d = 4  // 默认 public
    
    protected class Nested {
        public val e: Int = 5
    }
}

class Subclass : Outer() {
    // a 不可见
    // b、c、d 可见
    // Nested 和 e 可见

    override val b = 5   // “b”为 protected
}

class Unrelated(o: Outer) {
    // o.a、o.b 不可见
    // o.c 和 o.d 可见(相同模块)
    // Outer.Nested 不可见,Nested::e 也不可见
}

构造函数

要指定一个类的的主构造函数的可见性,使用以下语法(注意你需要添加一个显式 constructor 关键字):

class C private constructor(a: Int) { …… }

这里的构造函数是私有的。默认情况下,所有构造函数都是 public,这实际上等于类可见的地方它就可见(即 一个 internal 类的构造函数只能在相同模块内可见).

局部声明

局部变量、函数和类不能有可见性修饰符

模块

可见性修饰符 internal 意味着该成员只在相同模块内可见。更具体地说, 一个模块是编译在一起的一套 Kotlin 文件

  • 一个 IntelliJ IDEA 模块;
  • 一个 Maven 项目;
  • 一个 Gradle 源集(例外是 test 源集可以访问 main 的 internal 声明);
  • 一次 Ant 任务执行所编译的一套文件。

扩展

Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。 这通过叫做 扩展 的特殊声明完成。 例如,你可以为一个你不能修改的、来自第三方库中的类编写一个新的函数。 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用。 这种机制称为 扩展函数 。此外,也有 扩展属性 , 允许你为一个已经存在的类添加新的属性。

扩展函数

声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。 下面代码为 MutableList 添加一个swap 函数:

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // “this”对应该列表
    this[index1] = this[index2]
    this[index2] = tmp
}

这个 this 关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象) 现在,我们对任意 MutableList 调用该函数了:

fun main() {
    val list = mutableListOf(1, 2, 3)
    list.swap(1, 2)
    println("输出结果是:$list")
}

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1]
    this[index1] = this[index2]
    this[index2] = tmp
} //输出结果是:[1, 3, 2]

当然,这个函数对任何 MutableList 起作用,我们可以泛化它:

fun <T>MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1]
    this[index1] = this[index2]
    this[index2] = tmp
}

扩展是静态解析的

扩展不能真正的修改他们所扩展的类。通过定义一个扩展,你并没有在一个类中插入新成员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数。

我们想强调的是扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。例如:

fun main() {
    open class Shape
    class Rectangle : Shape()

    fun Shape.getName() = "Shape"
    fun Rectangle.getName() = "Rectangle"
    fun printClassName(s: Shape) {
        println(s.getName())
    }
    printClassName(Rectangle())
}

这个例子会输出 “Shape”,因为调用的扩展函数只取决于参数 s 的声明类型,该类型是 Shape 类。
如果一个类定义有一个成员函数与一个扩展函数,而这两个函数又有相同的接收者类型、 相同的名字,并且都适用给定的参数,这种情况总是取成员函数。 例如:

fun main() {
    class Example {
        fun printFunctionType() { println("Class method") }
    }
    fun Example.printFunctionType() { println("Extension function") }
    Example().printFunctionType()
}

这段代码输出“Class method”。
当然,扩展函数重载同样名字但不同签名成员函数也完全可以:

fun main() {
    class Example {
        fun printFunctionType() { println("Class method") }
    }

    fun Example.printFunctionType(i: Int) { println("Extension function") }

    Example().printFunctionType(1)
}

可空接收者

注意可以为可空的接收者类型定义扩展。这样的扩展可以在对象变量上调用, 即使其值为 null,并且可以在函数体内检测 this == null,这能让你在没有检测 null 的时候调用 Kotlin 中的toString():检测发生在扩展函数的内部。

fun Any?.toString(): String {
    if (this == null) return "null"
    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
    // 解析为 Any 类的成员函数
    return toString()
}

扩展属性

与函数类似,Kotlin 支持扩展属性:

val <T> List<T>.lastIndex: Int
    get() = size - 1

注意:由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getters/setters 定义。
例如:

val House.number = 1 // 错误:扩展属性不能有初始化器

伴生对象的扩展

如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数与属性。就像伴生对象的常规成员一样, 可以只使用类名作为限定符来调用伴生对象的扩展成员:

fun main() {
    MyClass.printCompanion()
}
fun MyClass.Companion.printCompanion() { println("伴侣") }
class MyClass {
    companion object { }  // 将被称为 "Companion"
}//伴侣

扩展的作用域

大多数时候我们在顶层定义扩展——直接在包里:

package org.example.declarations
 
fun List<String>.getLongestString() { /*……*/}

要使用所定义包之外的一个扩展,我们需要在调用方导入它:

package org.example.usage

import org.example.declarations.getLongestString

fun main() {
    val list = listOf("red", "green", "blue")
    list.getLongestString()
}

扩展声明为成员

在一个类内部你可以为另一个类声明扩展。在这样的扩展内部,有多个 隐式接收者 —— 其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例称为 分发接收者,扩展方法调用所在的接收者类型的实例称为 扩展接收者

fun main() {
    Connection(Host("我是奇偶"),43).connect()
}
class Host(val hostname: String) {
    fun printHostname() { print(hostname) }
}
class Connection(val host: Host, val port: Int) {
    fun printPort() { print(port) }

    fun Host.printConnectionString() {
        printHostname()   // 调用 Host.printHostname()
        print(":")
        printPort()   // 调用 Connection.printPort()
    }

    fun connect() {
        /*……*/
        host.printConnectionString()   // 调用扩展函数
    }
}//我是奇偶:43

对于分发接收者与扩展接收者的成员名字冲突的情况,扩展接收者优先。要引用分发接收者的成员你可以使用 限定的 this 语法。

fun main() {
    Connection(Host("我是奇偶"), 43).connect()
}

class Host(val hostname: String) {
    fun printHostname() {
        println("输出结果:Host$hostname")

    }
}

class Connection(val host: Host, val port: Int) {

    fun Host.printConnectionString() {
        printHostname()
        this@Connection.printHostname()
    }

    fun connect() {
        /*……*/
        host.printConnectionString()   // 调用扩展函数
    }

    fun printHostname() {
        println("输出结果:我是Connection")
    }
}
//输出结果:Host我是奇偶
//输出结果:我是Connection

声明为成员的扩展可以声明为 open 并在子类中覆盖。这意味着这些函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值