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还支持属性委托,允许你将属性的getset操作委托给另一个对象或表达式。这可以通过在属性声明中使用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时,它会调用LazyValuegetValue方法来计算并缓存值。之后的访问将直接返回缓存的值。

相关推荐
九鼎科技-Leo1 小时前
什么是 WPF 中的依赖属性?有什么作用?
windows·c#·.net·wpf
Yang.993 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
我不瘦但很逗4 小时前
Windows下使用DBeaver连接云数据库(MySQL)
数据库·windows
ashane13145 小时前
Java list
java·windows·list
万里沧海寄云帆5 小时前
Word 插入分节符页码更新问题
windows·microsoft·word
dot.Net安全矩阵6 小时前
.NET 通过模块和驱动收集本地EDR的工具
windows·安全·web安全·.net·交互
编程修仙8 小时前
Collections工具类
linux·windows·python
程序员小羊!9 小时前
高级 SQL 技巧讲解
windows
xiangshangdemayi11 小时前
Windows环境GeoServer打包Docker极速入门
windows·docker·容器·geoserver·打包·数据挂载
a_安徒生11 小时前
window系统改为Linux系统
linux·windows·centos·系统安全