1 前言
还记得在数据结构书中的 <T>
类型吗?本文带你看看了解这个泛型编程。
泛型编程中,代码可以被处理成任何类型的对象。泛型编程与非泛型编程的区别是,数据的“类型”(通常标记为 T
)没有被明确说明。
泛型编程范式的掌握具有挑战性,因为它需要高度的抽象性(完全忽略数据的类型)。目前,C++(模板)、Java、C#,以及 Golang 都有对泛型编程的内置支持,并包括一个大型的通用集合和数据结构的标准库。
2 为什么选择泛型
Object 是所有其他类的超类,对象引用可以引用任何对象。这些功能缺乏类型安全性。泛型添加了这种类型的安全功能。
Java 中的泛型类似于 C++中的模板。例如,像 HashSet、ArrayList, HashMap 等类很好地使用了泛型。泛型类型的两种方法之间存在一些根本差异。
class Test<T> { // An object of type T is declared T obj; Test(T obj) { this.obj = obj; } // 构造器 public T getObject() { return this.obj; } } class Main { public static void main(String[] args) { // 整数类型实例 Test<Integer> iObj = new Test<Integer>(15); System.out.println(iObj.getObject()); // 字符串类型实例 Test<String> sObj = new Test<String>("宇宙之一粟"); System.out.println(sObj.getObject()); } }
输出:
15 宇宙之一粟
对于泛型类型(通常表示为 T
,你也可以选择其他的),不能对正在处理的实例类型做出假设。由于在泛型代码中不能做任何类型的假设,所以相同的逻辑有可能以一致和可重复的方式对许多类型起作用。类型不可知的性质使得泛型成为一个强大的语言特性,但它也使得不习惯的工程师难以阅读或理解泛型代码。
严格来说,有些语言如 C# 和 Java 有一个基础的 Object 类,所有的类都继承自它。在这些情况下,你可以对类型进行假设,只要你把它当作基础对象。
泛型编程实际上是将类型从代码中抽象出来。类型与代码逻辑的分离可以通过使用类型参数来完成,但也可以通过使用编程语言中的其他抽象机制(如接口或反射)来完成。
虽然在大多数语言中,接口是由类明确定义和实现的,但情况并非总是如此。例如,Go 和 TypeScript 都使用隐式接口,不直接在类型上预先声明,而是通过类型判断过程推断出来。隐式接口类型化是一个强大的工具。Go 的隐式接口:
// Example of an interface in Go type Reader interface { Read(p []byte) (n int, err error) } type myReader struct {} // Implement the Reader interface func (r *myReader) Read(p []byte) (n int, err error) { return 0, nil }
3 数据结构和泛型
数据结构是许多编程语言中使用的数据组织和操作的模式。最常见的数据结构是一类元素的集合。因为列表只是一类元素的集合,所以很容易创建一个泛型列表。大多数计算机科学学生学习的第一个数据结构是一个链表。链表的数据结构是一类元素的列表,但每个项目都包含一个指向链表中下一个元素的引用(或指针)。
4 泛型编程的优点
- 更少的模板代码;更多的业务逻辑
- 通用代码可以适用于任何数据类型
- 可以大大减少代码的重复性
- 错误修复只需要在一个地方发生
- 代码的可重用性可以大大增加
- 代码的稳定性可以大大增加
- 理论上,泛型代码应该能够达到 100% 的测试覆盖率,将回归的机会降到最低。
5 总结
编写泛型代码是一件非常有趣的事情,也是学习纯算法设计的好方法,不需要打字那么麻烦。