泛型编程

泛型程序设计（英文：generic programming）是程序设计语言的一种风格或范型。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型，在实例化时作为参数指明这些类型。各种程序语言和其编译器、运行环境对泛型的支持均不同。Ada、Delphi、Eiffel、Java、C#、F#、Swift 和 Visual Basic .NET 称之为泛型（generics）；ML、Scala 和 Haskell 称之为参数多态（Parametric polymorphism）；C++ 和 D称之为模板。具有广泛影响的1994年版的《Design Patterns》一书称之为参数化类型（parameterized type）。

## 泛型的定义及目的

泛型的定义主要有以下两种：

- 在程序编码中一些包含类型参数的类型，也就是说泛型的参数只可以代表类，不能代表个别对象。（这是当今常见的定义）
- 在程序编码中一些包含参数的类。其参数可以代表类或对象等等。（现在人们大多把这称作模板）

不论使用哪个定义，泛型的参数在真正使用泛型时都必须作出指明。

一些强类型程序语言支持泛型，其主要目的是加强类型安全及减少类转换的次数，但一些支持泛型的程序语言只能达到部分目的。

## 伪代码例子

```text
類　例泛類<T> {
　　值 : T

　　設置值(新值 : T) {
　　　　值 := 新值
　　}

　　獲取值() : T {
　　　　返回　值
　　}
}

例方法1() {
　　例物件 : 例泛類<整數型>
　　例物件 := 新　例泛類<整數型>()
　　例物件.設置值(5)
　　輸出整數(例物件.獲取值())
}

例方法2() {
　　例物件 : 例泛類<浮點數型>
　　例物件 := 新　例泛類<浮點數型>()
　　例物件.設置值(5.5)
　　輸出浮點數(例物件.獲取值())
}
```

在这例子中，`例泛類`是一个泛型，而`T`是一个类型参数。在`例泛類`中没指明`T`的实际类型，只有`例方法1()`和`例方法2()`在使用`例泛類`时才加以指明。

运行这例子的`例方法1()`将输出整数5，而运行`例方法2()`将输出浮点数5.5。

## 一些程序语言的泛型特性

### .NET 的泛型

.NET 泛型的参数只可以代表类，不能代表个别对象。由于 .NET 泛型的类型参数之实际类型在运行时均不会被消除，运行速度会因为类型转换的次数减少而加快。另外，使用`GetType()`方法可于程序运行时得知泛型及其类型参数的实际类型，更可以运用反射式编程。

```c#
using System;

// 定義一個泛型列表類，T 表示類型參數
public class GenericList<T>
{
    private T[] _items; // 存儲列表items的數組
    private int _count; // 列表中items的計數

    // 構造函數，初始化列表的容量
    public GenericList(int capacity)
    {
        _items = new T[capacity];
        _count = 0;
    }

    // 添加item到列表中
    public void Add(T item)
    {
        if (_count < _items.Length)
        {
            _items[_count] = item;
            _count++;
        }
        else
        {
            Console.WriteLine("List capacity reached."); // 列表容量已滿
        }
    }

    // 根據索引獲取列表中的item
    public T GetItem(int index)
    {
        if (index >= 0 && index < _count)
        {
            return _items[index];
        }
        else
        {
            throw new IndexOutOfRangeException("Index out of range."); // 索引超出範圍
        }
    }
}

public class Program
{
    public static void Main()
    {
        // 創建一個存儲整數的泛型列表
        GenericList<int> intList = new GenericList<int>(3);
        intList.Add(1);
        intList.Add(2);
        intList.Add(3);
        Console.WriteLine(intList.GetItem(1));  // 輸出: 2

        // 創建一個存儲字符串的泛型列表
        GenericList<string> stringList = new GenericList<string>(2);
        stringList.Add("Hello");
        stringList.Add("World");
        Console.WriteLine(stringList.GetItem(0));  // 輸出: Hello
    }
}
```

在上面的例子中展示了一个简单的泛型列表类 `GenericList<T>`，它可以存储任何类型的数据（由 `T` 指定）。`Program` 类中的 `Main` 方法演示了如何使用这个泛型类来存储和检索整数和字符串。

.NET 允许对个别泛型的类型参数进行约束，包括以下几种形式（假设`T`是泛型的类型参数，`C`是一般类、泛类，或是泛型的类型参数）：

- `T`是一个类。
- `T`是一个值类型。
- `T`具有无参数的公有建构方法。
- `T`实现接口`I`。
- `T`是`C`，或继承自`C`。

### Java 的泛型

Java 泛型的参数只可以代表类，不能代表个别对象。由于Java泛型的类型参数之实际类型在编译时会被消除，所以无法在运行时得知其类型参数的类型，而且无法直接使用基本值类型作为泛型类型参数。Java编译程序在编译泛型时会自动加入类型转换的编码，故运行速度不会因为使用泛型而加快。

由于运行时会消除泛型的对象实例类型信息等缺陷经常被人诟病，Java及JVM的开发方面也尝试解决这个问题，例如：Java通过在生成字节码时添加类型推导辅助信息，从而可以通过反射接口获得部分泛型信息；通过改进泛型在JVM的实现，使其支持基本值类型泛型和直接获得泛型信息等。

Java允许对个别泛型的类型参数进行约束，包括以下两种形式（假设`T`是泛型的类型参数，`C`是一般类、泛类，或是泛型的类型参数）：

- `T`实现接口`I`。
- `T`是`C`，或继承自`C`。

### C++的泛型（模板）

C++ 泛型的参数可以代表类或个别对象。在一般意义上，C++ 缺乏对泛型的类型参数进行直接约束的手段，但可利用 SFINAE（模板代换失败非错误，指在模板实例化过程中的错误仅意味此次代换失败，并不一定产生编译错误）规则及 C++11 的 static_assert 等实现相似功能。

```c++
#include <type_traits>

class B{
...
};
class D: public B{
...
};
template<typename T>
void SFINAE(const std::enable_if_t<std::is_base_of<B, T>::value, T> &t);
template<typename T>
void STATIC_ASSERT(const T &t){
    static_assert(std::is_pod<T>::value, "Use with POD types only!");
}
```

如上所示，std::enable_if（std::enable_if_t 是 std::enable_if::type 的缩写）利用 SFINAE 规则来实现模板类型参数约束的手段之一。其实现方式是若布尔判断为假，则把类型设为 void，而这将导致 const void & 这种不合法的类型出现，从而禁止这种类型参数的使用。

static_assert 则在布尔判断失败时把后面的字符串作为消息内容报告为编译错误。

在编译时，每个被使用的封闭泛型类型（即是所有泛型参数的实际类型都已被指明的泛型）都会有独立的编码产生，编译程序会在此时确保类型安全性。可是如果泛型要运用其泛型参数的某成员，而该泛型参数又不包含该成员的时候，编译程序所产生的错误信息或会看似与实际问题无关，增加除错的难度。

## 外部链接

- generic-programming.org
- Alexander A. Stepanov, Collected Papers of Alexander A. Stepanov (creator of the STL)

C++/D

- Walter Bright, Templates Revisited .
- David Vandevoorde, Nicolai M Josuttis, C++ Templates: The Complete Guide, 2003 Addison-Wesley.

C#/.NET

- Jason Clark, " Introducing Generics in the Microsoft CLR ," September 2003, MSDN Magazine, Microsoft.
- Jason Clark, " More on Generics in the Microsoft CLR ," October 2003, MSDN Magazine, Microsoft.
- M. Aamir Maniar, Generics.Net . An open source generics library for C#.

Delphi/Object Pascal

- Nick Hodges, " Delphi 2009 Reviewers Guide ," October 2008, Embarcadero Developer Network, Embarcadero.
- Craig Stuntz, " Delphi 2009 Generics and Type Constraints," October 2008
- Dr. Bob, " Delphi 2009 Generics "
- Free Pascal: Free Pascal Reference guide Chapter 8: Generics , Michaël Van Canneyt, 2007
- Delphi for Win32: Generics with Delphi 2009 Win32 , Sébastien DOERAENE, 2008
- Delphi for .NET: Delphi Generics , Felix COLIBRI, 2008

Eiffel

- Eiffel ISO/ECMA specification document

Haskell

- Johan Jeuring, Sean Leather, José Pedro Magalhães, and Alexey Rodriguez Yakushev. Libraries for Generic Programming in Haskell. Utrecht University.
- Dæv Clarke, Johan Jeuring and Andres Löh, The Generic Haskell user's guide
- Ralf Hinze, " Generics for the Masses ," In Proceedings of the ACM SIGPLAN International Conference on Functional Programming (ICFP), 2004.
- Simon Peyton Jones, editor, The Haskell 98 Language Report , Revised 2002.
- Ralf Lämmel and Simon Peyton Jones, "Scrap Your Boilerplate: A Practical Design Pattern for Generic Programming," In Proceedings of the ACM SIGPLAN International Workshop on Types in Language Design and Implementation (TLDI'03), 2003. (Also see the website devoted to this research)
- Andres Löh, Exploring Generic Haskell, Ph.D. thesis, 2004 Utrecht University.
- Generic Haskell: a language for generic programming

Java

- Gilad Bracha, Generics in the Java Programming Language , 2004.
- Maurice Naftalin and Philip Wadler, Java Generics and Collections, 2006, O'Reilly Media, Inc.
- Peter Sestoft, Java Precisely, Second Edition, 2005 MIT Press.
- language/generics.html, 2004 Sun Microsystems, Inc.
- Angelika Langer, Java Generics FAQs