iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0
自我挑戰組

Go in 3o系列 第 20

[Day20] Go in 30 - 介面 - 泛型(generic)

  • 分享至 

  • xImage
  •  

一、本篇介紹

  • 泛型 generic
  • 型別斷言 type assertion 與 switch
  • 什麼時候該用泛型 ?

二、泛型 generic

其實interface{}就相當於某種形式的泛型,然而空介面能作的事還是有限,

Go語言在其1.18版本引入了泛型的支援。這是Go語言開發史上的一個重大進步,因為泛型在很多其他語言中已經是一個基本的功能,而Go社區也長期討論此特性。

泛型提供了一種方法,讓開發者能夠編寫更具一般性,但仍然具有型別安全性的程式碼。例如,以前要為不同的型別寫不同的函式或資料結構,但有了泛型後,你可以寫一個泛型函式或資料結構,並在使用時為其提供具體的型別。

泛型

在 Go 語言裡,任何型別只要實作一個介面中的所有方法,就可以視為符合該介面型別。
而 interface{}(空介面)沒有定義任何方法,這意味著任何型別(包括內建型別)都可以代入為空介面,
當你將一個值存放到空介面中時,其動態型別(即值的實際型別)在普通情況下是被隱藏的。

為了從interface{}中取出值,你需要知道該值的型別或至少猜測其可能的型別。
這可以通過以下兩種方法實現:

  1. 型別斷言(Type Assertion):型別斷言是一種檢查空介面值是否為某個已知型別的方法。
var value interface{} = 42
num, ok := value.(int)
if ok {
    fmt.Println("Value is of type int:", num)
} else {
    fmt.Println("Value is not of type int")
}

如果該值不是該型別,ok會是false,並且num將是int的零值(也就是0)。

  1. reflect套件:reflect套件提供了更多的工具來檢查、操作和比較運行時的值和型別。雖然reflect套件功能強大,但它的使用可能會使程式碼變得複雜,且會對效能產生影響。
var value interface{} = 42
t := reflect.TypeOf(value)
v := reflect.ValueOf(value)
fmt.Println("Type of value:", t)
fmt.Println("Value:", v.Int())

在設計Go程式時,建議盡量減少interface{}的使用,除非真的有需要。過度使用空介面可能會使程式碼失去型別的安全性和可讀性。當你需要用到多型時,定義具體的介面通常是更好的選擇。

泛型範例 :

假設我們有一個函式sum(),作int加總,一開始設計如下:

func sum(a int, b int) int {
    return a + b
}

如果我們想要計算int型別之外的和。 如計算浮點或字串? 解決方法之一就是像下面這樣為不同類型定義不同的函數:

func sumFloat32(a float32, b float32) float32 {
    return a + b
}

func sumString(a string, b string) string {
    return a + b
}

函式的參數parameter只是類似佔位符的東西並沒有具體的值,只有我們呼叫函數傳入引數(argument) 之後才有具體的值。此時我們可以針對型別也做類似的事 :

func Add(a T, b T) T {  
    return a + b
}

此時T就像是個型別的佔位符號,T 被稱為 type parameter, 它不是具體的型別,在定義函數時型別並不確定。 因為 T 的型別並不確定,所以我們需要像函數的 parameter 一樣,在呼叫函數的時候再傳入具體的型別。 這樣我們就能一個函數同時支援多個不同的型別了,這裡的具體型別被稱為 type argument。


//例子一
// [T=int]中的 int 是type argument,代表函數Add()定義中的型別 T 全都被 int 取代
Add[T=int](1020, 2050)

// 傳入型別int後,Add()函數的定義可近似看成下面這樣:
func Add( a int, b int) int {
     return a + b
}

// 例子2:如果是兩個字串總和的時候,就傳入string類型實參
Add[T=string]("Hello", "World")

// string傳入後,Add()函數的定義可近似視為如下
func Add( a string, b string) string {
     return a + b
}

為什麼不用reflect(反射)就好,理由整理如下 :

  1. 用起來麻煩:使用介面和反射來模擬泛型的效果確實需要寫更多的代碼,這會增加代碼的複雜性。例如,每次你需要檢查和轉換一個型別,你都需要寫相應的代碼。

  2. 失去了編譯時的型別檢查:這是一個非常重要的問題。Go的一個主要優點是它的強型別系統,它可以在編譯時捕捉很多錯誤。使用介面和反射繞過了這個系統,這意味著某些錯誤只能在執行時被檢測到,這顯然不是理想的情況。

  3. 性能不太理想:反射涉及很多執行時的檢查和轉換,這會降低代碼的性能。泛型,另一方面,是在編譯時進行的,所以它通常更快。

什麼時候該用泛型 ?

如果經常要分別為不同的類型寫完全相同邏輯的程式碼,那麼使用泛型將是最合適的選擇。

以上就是最簡單的介紹泛型,當然泛型的介紹不只這些,之後會開一個主題來講講~。


上一篇
[Day19] Go in 30 - 介面 - 在函式中活用介面
下一篇
[Day21] - Go in 30 - 介面 - 型別斷言(type assertion)
系列文
Go in 3o30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言