kotlin基础之泛型和委托

Kotlin泛型的概念及使用

泛型概念

在Kotlin中,泛型(Generics)是一种允许在类、接口和方法中使用类型参数的技术。这些类型参数在实例化类、实现接口或调用方法时会被具体的类型所替代。泛型的主要目的是提高代码的复用性、类型安全性和可读性。

泛型使用
  1. 泛型类

定义一个泛型类,可以在类名后面加上尖括号< >,并在其中声明类型参数。

|---|----------------------------------------|
| | class Box<T>(val item: T) { |
| | fun getContent(): T { |
| | return item |
| | } |
| | } |
| | |
| | // 使用时指定类型参数 |
| | val intBox = Box<Int>(10) |
| | val stringBox = Box<String>("Hello") |

  1. 泛型函数

函数也可以有类型参数。

|---|-----------------------------------------|
| | fun <T> printItems(items: List<T>) { |
| | for (item in items) { |
| | print(item) |
| | print(", ") |
| | } |
| | println() |
| | } |
| | |
| | // 使用时,Kotlin会自动推断T的类型 |
| | printItems(listOf(1, 2, 3)) |
| | printItems(listOf("a", "b", "c")) |

  1. 泛型接口

与泛型类和泛型函数类似,接口也可以有类型参数。

|---|-------------------------------------------------|
| | interface Listener<T> { |
| | fun onItemClicked(item: T) |
| | } |
| | |
| | // 实现泛型接口 |
| | class ButtonClickListener<T> : Listener<T> { |
| | override fun onItemClicked(item: T) { |
| | // 处理点击事件 |
| | } |
| | } |

协变(Covariance)

协变是指在一个泛型类型中,如果类型参数是某个类的子类型,那么使用这个类型参数的泛型类型也应该是父类泛型类型的子类型。在Kotlin中,通过out修饰符实现协变。

|---|-----------------------------------------------------------------------------------------------------------------------|
| | interface Source<out T> { |
| | fun next(): T? |
| | } |
| | |
| | fun demo(strs: Source<String>) { |
| | // ... |
| | } |
| | |
| | val intSource: Source<Int> = ... |
| | // 因为Int是String的子类型(在Kotlin中String不是Int的子类,这里仅作示例),但Source<Int>不是Source<String>的子类型 |
| | // 所以不能直接传递intSource给demo函数,但可以通过协变实现 |
| | demo(intSource as Source<String>) // 错误:类型不匹配 |
| | |
| | // 正确的协变用法 |
| | val stringSource: Source<out String> = intSource as? Source<out String> // 这里假设intSource实际上可以转换为Source<out String> |
| | if (stringSource != null) { |
| | demo(stringSource) // 正确 |
| | } |

注意:在Kotlin中,String并不是Int的子类型,上面的例子仅用于说明协变的概念。

逆变(Contravariance)

逆变与协变相反,它指的是在一个泛型类型中,如果类型参数是某个类的父类型,那么使用这个类型参数的泛型类型也应该是子类泛型类型的父类型。在Kotlin中,通过in修饰符实现逆变。

|---|--------------------------------------------------------------------------------------------------------------|
| | interface Sink<in T> { |
| | fun put(item: T) |
| | } |
| | |
| | fun fill(sink: Sink<Number>) { |
| | // ... |
| | } |
| | |
| | val stringSink: Sink<String> = ... |
| | // 因为String是Number的子类型,但Sink<String>不是Sink<Number>的子类型 |
| | // 所以不能直接传递stringSink给fill函数,但可以通过逆变实现 |
| | fill(stringSink as Sink<Number>) // 错误:类型不匹配 |
| | |
| | // 正确的逆变用法 |
| | val numberSink: Sink<in Number> = stringSink as? Sink<in Number> // 这里假设stringSink实际上可以转换为Sink<in Number> |
| | if (numberSink != null) { |
| | fill(numberSink) // 正确 |
| | } |

同样,上面的例子仅用于说明逆变的概念,实际上String不是Number的子类型。

星号投射(Star Projection)

星号投射(*)在Kotlin中用于处理泛型类型的通配符情况。当你声明一个泛型类型但不想指定具体的类型参数时,可以使用星号投射。

使用方式:

  1. 协变星号投射 :List<out T*> 通常简化为 List<*>。这表示列表中的元素可以是任何类型,但当你从列表中取出元素时,它的类型会被视为Any?(因为任何类型都可以赋值给Any?)。

|---|-------------------------------------------------------|
| | val list: List<*> = ... // list可以是任何类型的List |
| | for (item in list) { |
| | if (item is String) { |
| | println(item.length) // 只有在确定item是String类型时才能调用其方法 |
| | } |
| | } |

  1. 逆变星号投射 :在Kotlin中,逆变星号投射不常用,因为Kotlin的泛型系统主要基于协变和不变。但在某些高级用法中,你可能会遇到类似于Sink<in T*>的逆变星号投射,这表示该接口或类可以接受任何类型的参数。

委托(Delegation)

概念:

委托(Delegation)是一种设计模式,它允许一个对象(委托对象)将其职责的一部分或全部委托给另一个对象(被委托对象)。委托模式可以提高代码的复用性和可维护性。

使用:

  1. 类委托 :在Kotlin中,可以使用by关键字来实现类委托。这允许一个类将某些方法的实现委托给另一个类的实例。

|---|-----------------------------------------------------|
| | class Base { |
| | fun printMessage() { |
| | println("Message from Base") |
| | } |
| | } |
| | |
| | class Derived(b: Base) : Base() by b { |
| | // Derived类将Base类的printMessage方法委托给b实例 |
| | } |
| | |
| | fun main() { |
| | val derived = Derived(Base()) |
| | derived.printMessage() // 输出 "Message from Base" |
| | } |

注意:在上面的例子中,Derived类继承了Base类,但实际上并没有重写printMessage方法。相反,它使用by关键字将该方法的调用委托给了b实例(即Base类的一个实例)。

  1. 属性委托 :Kotlin还支持属性委托,允许你将属性的get和set操作委托给另一个对象或表达式。这可以通过在属性声明中使用by关键字和相应的委托提供程序来实现。

|---|---------------------------------------------------------------------|
| | class LazyValue<T>(private val initializer: () -> T) { |
| | private var value: T? = null |
| | |
| | fun getValue(): T { |
| | if (value == null) { |
| | value = initializer() |
| | } |
| | return value!! |
| | } |
| | |
| | // 这里省略了setValue方法,因为我们只关心只读属性 |
| | } |
| | |
| | class Example { |
| | val lazyString: String by LazyValue { "Hello, World!" } |
| | } |
| | |
| | fun main() { |
| | val example = Example() |
| | println(example.lazyString) // 输出 "Hello, World!",并且只会在第一次访问时计算值 |
| | } |

在这个例子中,lazyString属性的get操作被委托给了LazyValue类的实例。当第一次访问lazyString时,它会调用LazyValue的getValue方法来计算并缓存值。之后的访问将直接返回缓存的值。

相关推荐
小羊没烦恼!4 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
rockmelodies4 天前
# Windows Server2008 R2 Standard(SP1镜像U盘)重置本地管理员密码【完整详细步骤】
windows·密码破解
微信开发api4 天前
微信iPad协议怎么用?基于协议的二次开发实践
微信·机器人·ipad
wechatbot8884 天前
极客互动企业微信 SCRM 自动运营平台效果实测
java·微信·企业微信·rpa
懂软件的胡子个哥4 天前
微信机器人为什么需要“回复候选”而不是所有 AI 内容直接发送
运维·微信·自动化·wechatapi·个人微信号二次开发
微信开发api4 天前
微信机器人接口:REST API vs WebSocket 选型建议
微信·机器人·ipad
kakakahahahaha4 天前
Windows C盘空间不足的安全清理与迁移流程
windows·电脑·笔记本电脑·软件需求·c盘清理
做咩啊~4 天前
远程连接提示:身份验证错误,要求的函数不受支持
windows
极客互动API4 天前
极客互动-企业微信基于外部API接口实现AI客服自动接管外部联系人消息收发
java·微信·企业微信·ai编程·rpa
百事牛科技4 天前
PPT只读怎么取消?四种情况分别处理
windows·powerpoint