泛型編程

设计和编写程序,用参数类型编写算法,易于重用。

泛型程式設計英文:generic programming)是程式設計語言的一種風格或範式。泛型允許程式設計師在強型別程式設計語言中編寫代碼時使用一些以後才指定的類型,在實例化時作為參數指明這些類型。各種程式語言和其編譯器執行環境對泛型的支援均不同。AdaDelphiEiffelJavaC#F#SwiftVisual Basic .NET 稱之為泛型(generics);MLScalaHaskell 稱之為參數多型parametric polymorphism);C++D稱之為模板。具有廣泛影響的1994年版的《Design Patterns》一書稱之為參數化類型(parameterized type)。

泛型的定義及目的

泛型的定義主要有以下兩種:

  1. 在程式編碼中一些包含類型參數的類型,也就是說泛型的參數只可以代表類別,不能代表個別物件。(這是當今常見的定義)
  2. 在程式編碼中一些包含參數的類別。其參數可以代表類別或物件等等。(現在人們大多把這稱作模板)

不論使用哪個定義,泛型的參數在真正使用泛型時都必須作出指明。

一些強類型程式語言支援泛型,其主要目的是加強類型安全及減少類別轉換的次數,但一些支援泛型的程式語言只能達到部份目的。

虛擬碼例子

類 例泛類<T> {
  值 : T

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

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

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

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

在這例子中,例泛類是一個泛型,而T是一個類型參數。在例泛類中沒指明T的實際類型,只有例方法1()例方法2()在使用例泛類時才加以指明。

運行這例子的例方法1()將輸出整數5,而運行例方法2()將輸出浮點數5.5。

一些程式語言的泛型特性

.NET 的泛型

.NET 泛型的參數只可以代表类别,不能代表個別物件。由於 .NET 泛型的類型參數之實際類型在執行時均不會被消除,執行速度會因為類型轉換的次數減少而加快。另外,使用GetType()方法可於程式執行時得知泛型及其類型參數的實際類型,更可以運用反射式編程

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 允許對個別泛型的類型參數進行約束,包括以下幾種形式[1](假設T是泛型的類型參數,C是一般類別、泛類,或是泛型的類型參數):

  • T是一個類別。
  • T是一個值類型。
  • T具有無參數的公有建構方法。
  • T實現界面I
  • TC,或繼承自C

Java 的泛型

Java 泛型的參數只可以代表类别,不能代表個別物件。由於Java泛型的類型參數之實際類型在編譯時會被消除,所以無法在執行時得知其類型參數的類型,而且無法直接使用基本值類型作為泛型類型參數。Java編譯程式在編譯泛型時會自動加入類型轉換的編碼,故執行速度不會因為使用泛型而加快。

由於執行時會消除泛型的對象實例類型資訊等缺陷經常被人詬病,Java及JVM的開發方面也嘗試解決這個問題,例如:Java通過在生成位元組碼時添加類型推導輔助資訊,從而可以通過反射介面獲得部分泛型資訊;通過改進泛型在JVM的實現,使其支援基本值類型泛型和直接獲得泛型資訊等。

Java允許對個別泛型的類型參數進行約束,包括以下兩種形式[2](假設T是泛型的類型參數,C是一般類別、泛類,或是泛型的類型參數):

  • T實現介面I
  • TC,或繼承自C

C++的泛型(模板)

C++ 泛型的參數可以代表类别或個別物件。在一般意義上,C++ 缺乏對泛型的類型參數進行直接約束的手段,但可利用 SFINAE(模板代換失敗非錯誤,指在模板實例化過程中的錯誤僅意味此次代換失敗,並不一定產生編譯錯誤)規則及 C++11 的 static_assert 等實現相似功能。

#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<boolean, Type> 是 std::enable_if<boolean, Type>::type 的縮寫)利用 SFINAE 規則來實現模板類型參數約束的手段之一。其實現方式是若布林判斷為假,則把類型設為 void,而這將導致 const void & 這種不合法的類型出現,從而禁止這種類型參數的使用。

static_assert 則在布林判斷失敗時把後面的字串作為訊息內容報告為編譯錯誤。

在編譯時,每個被使用的封閉泛型類型(即是所有泛型參數的實際類型都已被指明的泛型)都會有獨立的編碼產生,編譯程式會在此時確保類型安全性。可是如果泛型要運用其泛型參數的某成員,而該泛型參數又不包含該成員的時候,編譯程式所產生的錯誤資訊或會看似與實際問題無關,增加除錯的難度。

資料來源

  1. ^ Standard ECMA-335 Common Language Infrastructure (CLI) 4th Edition. June 2006 [2007-08-03]. (原始內容存檔於2013-06-26). 
  2. ^ James Gosling, Bill Joy, Guy Steele, Gilad Bracha. The Java Language Specification Third Edition. 2005 [2007-08-03]. (原始內容存檔於2009-02-26). 

參考文獻

延伸閱讀

外部連結

C++/D
  • Walter Bright, Templates Revisited頁面存檔備份,存於網際網路檔案館).
  • David Vandevoorde, Nicolai M Josuttis, C++ Templates: The Complete Guide, 2003 Addison-Wesley. ISBN 0-201-73484-2
C#/.NET
Delphi/Object Pascal
Eiffel
Haskell
Java