泛型函数和类

概述

泛型是编程语言中一个强大的特性,它允许我们在定义函数、类或接口时使用类型参数,从而使代码更加灵活和可重用。Kotlin 支持泛型,这使得我们可以在编写代码时不必预先指定具体的类型,而是在使用时再确定。

在本教程中,我们将深入探讨 Kotlin 中的泛型函数和类,包括它们的定义、使用场景以及如何通过泛型提高代码的可读性和可维护性。

泛型函数

定义泛型函数

泛型函数允许我们在函数定义中使用类型参数。类型参数通常用大写字母表示,例如 T、E 或 K。定义泛型函数时,类型参数放在函数名后面的尖括号 <> 中。

fun <T> printList(list: List<T>) {
    for (item in list) {
        println(item)
    }
}

在这个例子中,printList 函数可以接受任何类型的列表,并在控制台中打印每个元素。

使用泛型函数

使用泛型函数时,编译器会根据传入的参数类型推断出具体的类型参数。

fun main() {
    val intList = listOf(1, 2, 3, 4, 5)
    val stringList = listOf("a", "b", "c")

    printList(intList)    // 输出: 1 2 3 4 5
    printList(stringList) // 输出: a b c
}

泛型约束

有时我们希望限制泛型参数的类型范围。例如,我们可能希望泛型参数必须是某个类的子类或实现了某个接口。这可以通过泛型约束来实现。

fun <T : Number> doubleValue(value: T): Double {
    return value.toDouble() * 2
}

在这个例子中,T 必须是 Number 的子类,因此我们可以安全地调用 toDouble() 方法。

fun main() {
    val result = doubleValue(5) // 输出: 10.0
    println(result)
}

泛型类

定义泛型类

泛型类允许我们在类定义中使用类型参数。类型参数可以在类的属性和方法中使用。

class Box<T>(var item: T) {
    fun getItem(): T {
        return item
    }

    fun setItem(newItem: T) {
        item = newItem
    }
}

在这个例子中,Box 类可以存储任何类型的 item,并在需要时获取或设置它。

使用泛型类

使用泛型类时,我们需要在实例化时指定具体的类型参数。

fun main() {
    val intBox = Box(10)
    val stringBox = Box("Hello")

    println(intBox.getItem())    // 输出: 10
    println(stringBox.getItem()) // 输出: Hello

    intBox.setItem(20)
    stringBox.setItem("World")

    println(intBox.getItem())    // 输出: 20
    println(stringBox.getItem()) // 输出: World
}

泛型类的继承

泛型类也可以被继承,子类可以选择保留父类的泛型参数,或者指定具体的类型。

open class BaseBox<T>(var item: T)

class IntBox(item: Int) : BaseBox<Int>(item)

fun main() {
    val intBox = IntBox(100)
    println(intBox.item) // 输出: 100
}

型变(协变和逆变)

协变

协变允许我们使用比原始类型更具体的类型。在 Kotlin 中,我们可以通过在类型参数前加上 out 关键字来实现协变。

class Producer<out T>(val value: T) {
    fun produce(): T {
        return value
    }
}

fun main() {
    val producer: Producer<String> = Producer("Hello")
    val anyProducer: Producer<Any> = producer // 协变
    println(anyProducer.produce()) // 输出: Hello
}

逆变

逆变允许我们使用比原始类型更泛化的类型。在 Kotlin 中,我们可以通过在类型参数前加上 in 关键字来实现逆变。

class Consumer<in T> {
    fun consume(value: T) {
        println(value)
    }
}

fun main() {
    val consumer: Consumer<Any> = Consumer()
    val stringConsumer: Consumer<String> = consumer // 逆变
    stringConsumer.consume("Hello") // 输出: Hello
}

实践练习

练习 1:实现一个泛型栈

实现一个泛型栈类 Stack<T>,支持 push、pop 和 peek 操作。

class Stack<T> {
    private val elements: MutableList<T> = mutableListOf()

    fun push(item: T) {
        elements.add(item)
    }

    fun pop(): T? {
        if (elements.isEmpty()) {
            return null
        }
        return elements.removeAt(elements.size - 1)
    }

    fun peek(): T? {
        if (elements.isEmpty()) {
            return null
        }
        return elements.last()
    }

    fun isEmpty(): Boolean {
        return elements.isEmpty()
    }
}

fun main() {
    val stack = Stack<Int>()
    stack.push(1)
    stack.push(2)
    stack.push(3)

    println(stack.pop()) // 输出: 3
    println(stack.peek()) // 输出: 2
    println(stack.isEmpty()) // 输出: false
}

练习 2:实现一个泛型队列

实现一个泛型队列类 Queue<T>,支持 enqueue、dequeue 和 front 操作。

class Queue<T> {
    private val elements: MutableList<T> = mutableListOf()

    fun enqueue(item: T) {
        elements.add(item)
    }

    fun dequeue(): T? {
        if (elements.isEmpty()) {
            return null
        }
        return elements.removeAt(0)
    }

    fun front(): T? {
        if (elements.isEmpty()) {
            return null
        }
        return elements.first()
    }

    fun isEmpty(): Boolean {
        return elements.isEmpty()
    }
}

fun main() {
    val queue = Queue<String>()
    queue.enqueue("A")
    queue.enqueue("B")
    queue.enqueue("C")

    println(queue.dequeue()) // 输出: A
    println(queue.front()) // 输出: B
    println(queue.isEmpty()) // 输出: false
}

总结

泛型是 Kotlin 中一个非常强大的特性,它允许我们编写更加灵活和可重用的代码。通过泛型函数和类,我们可以减少代码重复,提高代码的可读性和可维护性。同时,通过协变和逆变,我们可以在类型安全的前提下,更加灵活地使用泛型。

希望本教程能够帮助你更好地理解和使用 Kotlin 中的泛型。继续探索和实践,你将能够编写出更加优雅和高效的 Kotlin 代码。