kotlin 第五天:类与对象(四)

泛型

类型参数约束

可以像javaT extends String一样,声明上界,T : String
还可以声明多个约束条件

fun <T> ensure(s:T) where T : CharSequence,T : Appenable{
}

没有声明上界的,其实默认上界是Any?,这样就导致了一个问题,可以传入一个可空的类型实参,如果想保证类型参数是非可空的,显示的加个上界T : Any

class Producer<T>{
}
var p:Producer<String?>//编译通过

class Consumer<T :Any>{}
var c:Consumer<String?>//编译失败

类、类型和子类型

kotlin和java的泛型有很多相似的地方,但是也有一些不一样的,但是要理解哪些概念,我觉得有类、类型和子类型的概念必须明确一下:
通常情况下,类的名称可以直接当成类型使用

var s:String

声明了一个可以保存String类实例的变量;
还可以声明一个可空的类型

var s:String

这就意味着任何一个kotlin类都可以有至少两种类型

对于泛型类,情况就会更加的多,因为泛型类引入了类型参数;对于泛型类要得到一个合法的***类型***,需要用一个作为类型实参的具体类型替换泛型类的类型参数。
例如:List不是一个类型,他是一个类的名称;List,List等是类型
每一个泛型类都可以生成无数的类型

子类型的定义:
类型A的值,你都能够使用类型B的值(当做A的值),类型B就称为类型A的子类型
超类是子类型的反义词,如果A是B的子类型,B就是A的超类型
所有类型都认为是 自己的子类型
非空类型是可空类的子类型,String是String?的子类型;反过来不是
这说明子类和子类型是两种不同的概念

对于变量,只有值的类型是变量类型的子类型时,才允许变量存储该值
只有当表达式的类型是函数参数的类型的子类型是,才允许把该表达式传递给函数

协变:保留子类型化关系

协变:如果A是B的子类型,那么Producer就是Producer的子类型。我们也说***子类型化被保留了***
声明:

interface Producer<out T>{
fun produce():T
}

声明就是在类型参数前加一个out
out有两层含义
1、子类型化被保留
2、T只能出现在out位置

声明out的要求
1、被声明为out的类型,它只能出现在out位置
out位置和in位置
函数返回值的位置是out位置,函数参数的位置是in位置

//out位置
fun testOut():T
//in 位置
fun testIn(t:T)

2、构造函数的参数既不在in位置也不在out位置
但是注意,在构造函数中,如果声明了var 或者val就会生成getter和setter;对于val变量,就在out位置使用了;对于var 变量在in和out位置都使用了
3、private方法的参数既不在in也不在out位置;对于(public,protected和internal)这些类外部可见的API,in和out规则才生效

逆变:反子类型化关系

可以简单说,逆变就是协变的镜像;当类型参数只出现在in位置的时候,就可以声明成协变
in关键字的意思就是,传递进来给这个类的,并且被这些方法消耗;

interface Consumer<in T>{
fun produce(t:T)
}

一个类可以同时声明一个协变类型参数和一个逆变类型参数

interface Function<in P,out R>{
    ...
}

可以表示为(P) -> R

使用点变型

协变和逆变实际是在类声明是使用的,这就是声明点变型***
像java的? super ? extends就是
使用时变型
*
类型投影:kotlin使用点变型,称作类型投影

val list :MutableList<out Number>

使用*投影

首先要注意的是* 不等于Any?:例如;

MutableList<*>
MutableList<Any?>

MutableList<>>是包含某种特定类型元素的列表,但是你不知道是哪个类型
MutableList<Any?> 这种列表包含的是任意类型的元素
MutableList<
> 投影成了 MutableList<out Any?> : 当你没有任何 元素类型信息 的时候,读取 Any?类型的元素仍然是安全的,但是向列表中写入元素 是不安全 的 。 谈到 Java 通配符, Kotlin 的 MyType<*>对应于 Java 的 MyType<?>

星号投影的语法很简沽 ,但只能用在对泛型类型实 参的确切值不感兴趣的地方: 只是使用生产值的方法,而且不关心那些值的类型。

对象

对象表达式

要创建一个继承自某个(或某些)类型的匿名类的对象,我们会这么写:

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { …… }override fun mouseEntered(e: MouseEvent) { …… }
})

如果超类型有一个构造函数,则必须传递适当的构造函数参数给它。 多个超类型可以由跟在冒号后面的逗号分隔的列表指定:

open class A(x: Int) {
    public open val y: Int = x
}interface B { …… }val ab: A = object : A(1), B {
    override val y = 15
}

任何时候,如果我们只需要“一个对象而已”,并不需要特殊超类型,那么我们可以简单地写:

fun foo() {
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}

请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是Any。在匿名对象中添加的成员将无法访问。

class C {
    // 私有函数,所以其返回类型是匿名对象类型
    private fun foo() = object {
        val x: String = "x"
    }

    // 公有函数,所以其返回类型是 Any
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x        // 没问题
        val x2 = publicFoo().x  // 错误:未能解析的引用“x”
    }
}

就像 Java 匿名内部类一样,对象表达式中的代码可以访问来自包含它的作用域的变量。 (与 Java 不同的是,这不仅限于 final 变量。)

fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0
​
    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }override fun mouseEntered(e: MouseEvent) {
            enterCount++
        }
    })
    // ……
}

对象声明
单例模式在一些场景中很有用, 而 Kotlin(继 Scala 之后)使单例声明变得很容易:

object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}

val allDataProviders: Collection
get() = // ……
}
这称为对象声明。并且它总是在 object 关键字后跟一个名称。 就像变量声明一样,对象声明不是一个表达式,不能用在赋值语句的右边。

对象声明的初始化过程是线程安全的。

如需引用该对象,我们直接使用其名称即可:

DataProviderManager.registerDataProvider(……)
这些对象可以有超类型:

object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { …… }

override fun mouseEntered(e: MouseEvent) { …… }
}
注意:对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中。

伴生对象
类内部的对象声明可以用 companion 关键字标记:

class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
该伴生对象的成员可通过只使用类名作为限定符来调用:

val instance = MyClass.create()
可以省略伴生对象的名称,在这种情况下将使用名称 Companion:

class MyClass {
companion object { }
}

val x = MyClass.Companion
其自身所用的类的名称(不是另一个名称的限定符)可用作对该类的伴生对象 (无论是否命名)的引用:

class MyClass1 {
companion object Named { }
}

val x = MyClass1

class MyClass2 {
companion object { }
}

val y = MyClass2
请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口:

interface Factory {
fun create(): T
}

class MyClass {
companion object : Factory {
override fun create(): MyClass = MyClass()
}
}

val f: Factory = MyClass
当然,在 JVM 平台,如果使用 @JvmStatic 注解,你可以将伴生对象的成员生成为真正的静态方法和字段。更详细信息请参见Java 互操作性一节 。

对象声明

单例

单例模式在一些场景中很有用, 而 Kotlin(继 Scala 之后)使单例声明变得很容易:

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ……
    }val allDataProviders: Collection<DataProvider>
        get() = // ……
}

kotlin 中直接再类声明前加入object关键字就可以;
这称为对象声明。并且它总是在 object 关键字后跟一个名称。 就像变量声明一样,对象声明不是一个表达式,不能用在赋值语句的右边

对象声明的初始化过程是线程安全的。

如需引用该对象,我们直接使用其名称即可:

DataProviderManager.registerDataProvider(……)

这些对象可以有超类型:

object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { …… }override fun mouseEntered(e: MouseEvent) { …… }
}

注意:对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中。

伴生对象

类内部的对象声明可以用companion关键字标记:

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

该伴生对象的成员可通过只使用类名作为限定符来调用:

val instance = MyClass.create()

可以省略伴生对象的名称,在这种情况下将使用名称 Companion:

class MyClass {
    companion object { }
}val x = MyClass.Companion

其自身所用的类的名称(不是另一个名称的限定符)可用作对该类的伴生对象 (无论是否命名)的引用:

class MyClass1 {
    companion object Named { }
}val x = MyClass1
​
class MyClass2 {
    companion object { }
}val y = MyClass2

请注意,即使伴生对象的成员看起来像其他语言的静态成员在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口:

interface Factory<T> {
    fun create(): T
}class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass = MyClass()
    }
}val f: Factory<MyClass> = MyClass

当然,在 JVM 平台,如果使用 @JvmStatic 注解,你可以将伴生对象的成员生成为真正的静态方法和字段。更详细信息请参见Java 互操作性一节 。

对象表达式和对象声明之间的语义差异

对象表达式和对象声明之间有一个重要的语义差别:

  • 对象表达式是在使用他们的地方立即执行(及初始化)的;
  • 对象声明是在第一次被访问到时延迟初始化的;
  • 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。

委托

委托实现

委托模式已经证明是实现继承的一个很好的替代方式, 而 Kotlin 可以零样板代码地原生支持它。 Derived 类可以通过将其所有公有成员都委托给指定对象来实现一个接口 Base:

interface Base {
    fun print()
}class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}class Derived(b: Base) : Base by b
​
fun main() {
    val b = BaseImpl(10)
    Derived(b).print()
}

Derived 的超类型列表中的 by-子句表示 b 将会在 Derived 中内部存储, 并且编译器将生成转发给 b 的所有 Base 的方法。

覆盖由委托实现的接口成员

覆盖符合预期:编译器会使用 override 覆盖的实现。如果将 override fun printMessage() { print(“abc”) } 添加到 Derived,那么当调用 printMessage 时程序会输出“abc”而不是“10”:

interface Base {
    fun printMessage()
    fun printMessageLine()
}class BaseImpl(val x: Int) : Base {
    override fun printMessage() { print(x) }
    override fun printMessageLine() { println(x) }
}class Derived(b: Base) : Base by b {
    override fun printMessage() { print("abc") }
}fun main() {
    val b = BaseImpl(10)
    Derived(b).printMessage()
    Derived(b).printMessageLine()
}
// 输出abc10

但请注意,以这种方式重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员实现

interface Base {
    val message: String
    fun print()
}class BaseImpl(val x: Int) : Base {
    override val message = "BaseImpl: x = $x"
    override fun print() { println(message) }
}class Derived(b: Base) : Base by b {
    // 在 b 的 `print` 实现中不会访问到这个属性
    override val message = "Message of Derived"
}fun main() {
    val b = BaseImpl(10)
    val derived = Derived(b)
    derived.print()
    println(derived.message)
}
// BaseImpl: x = 10
// Message of Derived

委托属性

常用的委托属性类型
— 延迟属性(lazyproperties):其值只在首次访问时计算;
— 可观察属性(observableproperties):监听器会收到有关此属性变更的通知;
— 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
这些属性委托类型,kotlin提供了相应的方法

标准属性委托

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.")
    }
}

语法:
val/var <属性名>: <类型> by <表达式>。
在 by 后面的表达式是该委托, 因为属性对应的 get()(与 set())会被委托给它的 getValue() 与 setValue() 方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(与 setValue()——对于 var 属性)。

val e = Example()
println(e.p)
//输出
// Example@33a17727, thank you for delegating ‘p’ to me!
e.p = "NEW"
//输出
//NEW has been assigned to ‘p’ in Example@33a17727.

属性委托要求:

对于一个只读属性(即 val 声明的),委托必须提供一个名为 getValue 的函数,该函数接受以下参数:

thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型;
property —— 必须是类型 KProperty<*> 或其超类型。
这个函数必须返回与属性相同的类型(或其子类型)。

对于一个可变属性(即 var 声明的),委托必须额外提供一个名为 setValue 的函数,该函数接受以下参数:

thisRef —— 同 getValue();
property —— 同 getValue();
new value —— 必须与属性同类型或者是它的超类型。

getValue() 或/与 setValue() 函数可以通过委托类的成员函数提供或者由扩展函数提供。 当你需要委托属性到原本未提供的这些函数的对象时后者会更便利。 两函数都需要用 operator 关键字来进行标记。

委托类可以实现包含所需 operator 方法的 ReadOnlyProperty 或 ReadWriteProperty 接口之一。 这俩接口是在 Kotlin 标准库中声明的:

interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

翻译规则
在每个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。 例如,对于属性 prop,生成隐藏属性 prop$delegate,而访问器的代码只是简单地委托给这个附加属性:

class C {
    var prop: Type by MyDelegate()
}// 这段是由编译器生成的相应代码:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

Kotlin 编译器在参数中提供了关于 prop 的所有必要信息:第一个参数 this 引用到外部类 C 的实例而 this::prop 是 KProperty 类型的反射对象,该对象描述 prop 自身。

请注意,直接在代码中引用绑定的可调用引用的语法 this::prop 自 Kotlin 1.1 起才可用。

延迟属性 Lazy

lazy() 是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}fun main() {
    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() {
    val user = User()
    user.name = "first"
    user.name = "second"
}

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

把属性储存在映射中

一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by 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 失败,那么该变量根本不会计算。

提供委托(自 1.1 起)

通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。 如果 by 右侧所使用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来创建属性委托实例。

provideDelegate 的一个可能的使用场景是在创建属性时(而不仅在其 getter 或 setter 中)检查属性一致性。

例如,如果要在绑定之前检查属性名称,可以这样写:

class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
    override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}
    
class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // 创建委托
        return ResourceDelegate()
    }private fun checkProperty(thisRef: MyUI, name: String) { …… }
}class MyUI {
    fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { …… }val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}

provideDelegate 的参数与 getValue 相同:

thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型;
property —— 必须是类型 KProperty<*> 或其超类型。
在创建 MyUI 实例期间,为每个属性调用 provideDelegate 方法,并立即执行必要的验证。

如果没有这种拦截属性与其委托之间的绑定的能力,为了实现相同的功能, 你必须显式传递属性名,这不是很方便:

// 检查属性名称而不使用“provideDelegate”功能
class MyUI {
    val image by bindResource(ResourceID.image_id, "image")
    val text by bindResource(ResourceID.text_id, "text")
}
​
fun <T> MyUI.bindResource(
        id: ResourceID<T>,
        propertyName: String
): ReadOnlyProperty<MyUI, T> {
   checkProperty(this, propertyName)
   // 创建委托
}

在生成的代码中,会调用 provideDelegate 方法来初始化辅助的 prop$delegate 属性。 比较对于属性声明 val prop: Type by MyDelegate() 生成的代码与上面(当 provideDelegate 方法不存在时)生成的代码:

class C {
    var prop: Type by MyDelegate()
}// 这段代码是当“provideDelegate”功能可用时
// 由编译器生成的代码:
class C {
    // 调用“provideDelegate”来创建额外的“delegate”属性
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

请注意,provideDelegate 方法只影响辅助属性的创建,并不会影响为 getter 或 setter 生成的代码。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值