泛型
go的泛型是go 1.18 之后的产物。
泛型主要是为了解决不同类型的代码重复的问题
引用一张网络梗图:

标志
[T any]
相关概念
简单了解下即可,未接触过golang的any的话,直接阅读起来比较干巴且抽象,可以带着这些概念阅读下文中代码。
类型约束
虽然使用了泛型,但是并不能对一个变量随意赋值,比如一个变量先赋值为string,再赋值为int,这一般不会被允许(any、interface是例外)
比如:
我定义了一个泛型函数Echo(),希望可以接受 int , string 类型的参数,
但是不能接受其他类型,比如float64,因为float64不是以上类型中的子类型。
func Echo[T int | string](arg T) T {
return arg
}
func TestEcho2(t *testing.T) {
/*
此时约束为 string ,得到的 domain 也为 string 类型
所以此处不能使用 int 类型为 domain 赋值
*/
domain := Echo[string]("fanfine.cn")
fmt.Println(domain, reflect.TypeOf(domain)) // fanfine.cn string
// domain = 1 // 不可以
/*
此时约束为 int ,得到的 num 为 int 类型
所以此处不能使用 string 类型为 num 赋值
*/
num := Echo[int](1)
fmt.Println(num, reflect.TypeOf(num)) // 1 int
// num = "fanfine.cn" // 不可以
/*
如果使用了一个函数未被允许的类型作为约束?
报错:
float64 does not satisfy int | string (float64 missing in int | string)
float64不满足int|string(int|string中缺少float64)
*/
floatData := Echo[float64](1.2)
fmt.Println(floatData)
}
当 约束设置为 [T any] 或 [T interface{}] 时,float64 以及其他类型都可以作为参数传入。
因为 any 和 interface{} 都是所有类型的超集,所以它们都可以作为约束,
func Echo[T any](arg T) T {
return arg
}
func TestEcho(t *testing.T) {
// but 如果使用 any / interface{} 作为约束呢?
everything := Echo[any](1)
fmt.Println(everything, reflect.TypeOf(everything)) // 1 int
fmt.Printf("everything 指向: %p \n", &everything) // 0x1400002a310
// 因为约束为 any 所以可以使用其他类型赋值,如:
everything = "fanfine.cn"
fmt.Println(everything, reflect.TypeOf(everything)) // fanfine.cn string
fmt.Printf("everything 指向: %p \n", &everything) // 0x1400002a310
// interface{} 同理
everything2 := Echo[interface{}]("Fan的小破站")
fmt.Println(everything2) // Fan的小破站
everything2 = 1 // 赋值由 string 的转为 int 的 1
fmt.Println(everything2) // 1
}
内置约束
anyany表示可以是任何类型。- 当你使用
any作为类型参数时,编译器不会强制类型检查。 - 在运行时,变量的实际类型取决于最后一次赋值的类型。
comparablecomparable表示可以比较的类型。- 在map中可作为key的类型。
// 内置约束 comparable
func EqualComparable[T comparable](a, b T) bool {
return a == b
}
func TestEqualComparable(t *testing.T) {
fmt.Println(EqualComparable(1, 2)) // false
fmt.Println(EqualComparable("1", "1")) // true
fmt.Println(EqualComparable(1.1, 2.2)) // false
fmt.Println(EqualComparable(true, false)) // false
}
自定义约束
语法如下:
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
func EqualSigned[T Signed](a, b T) bool {
return a == b
}
公开库的约束
"golang.org/x/exp/constraints"
使用详情见下文。
类型推断
当使用泛型时,如果没有显式指定类型,编译器会自动推断类型。例如:
// 推断
func Echo[T any](arg T) T {
return arg
}
func TestEcho(t *testing.T) {
fmt.Println("typeof:", reflect.TypeOf(Echo[int](1))) // typeof: int
fmt.Println("typeof:", reflect.TypeOf(Echo(1))) // typeof: int
}
在第二个 TestEcho() 调用时,虽然没有显式标示约束的类型,但是编译器时可以推断出其类型为 int。
小建议:
建议使用显式约束,这样泛型代码的阅读性会更好。
能一眼看出来调用了泛型函数,且明确约束的类型是什么。
解决梗图
在需要约束的数量很少的时候,可以这样写:
// 此处约束的 Ordered 也可写作 `int | float32 | uint | 更多类型`
func MaxNum[T int | float64 | uint](a, b T) T {
if a > b {
return a
}
return b
}
func TestMaxNum(t *testing.T) {
fmt.Println(MaxNum(int(1), int(2))) // 2
fmt.Println(MaxNum(uint(1), uint(2))) // 2
}
如此,便使用了泛型解决了梗图中代码大量重复的问题.
当需要约束的类型比较多的时候,可以这样写:
// 解决比大小的网络梗图,得出小的值
type Ordered interface {
int | float32 | float64 | uint // 省略其他类型
}
// 得出最小值
func MinNum[T Ordered](a, b T) T {
if a < b {
return a
}
return b
}
func TestMinNum(t *testing.T) {
fmt.Println(MinNum(int(1), int(2))) // 1
fmt.Println(MinNum(uint(1), uint(2))) // 1
}
同时,也有公开库 "golang.org/x/exp/constraints" 提供了 Ordered 类型,可以这样写:
import "golang.org/x/exp/constraints"
func EqualOrdered[T constraints.Ordered](a, b T) bool {
return a == b
}
func TestEqualOrdered(t *testing.T) {
fmt.Println(EqualOrdered(int(1), int(1))) // true
fmt.Println(EqualOrdered(uint(1), uint(2))) // false
fmt.Println(EqualOrdered("domain", "fanfine.cn")) // false
}
constraints还提供了其他约束:
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
type Integer interface {
Signed | Unsigned
}
type Float interface {
~float32 | ~float64
}
type Complex interface {
~complex64 | ~complex128
}
type Ordered interface {
Integer | Float | ~string
}
类型篇
泛型的基础使用
// 泛型的基础使用
func TestT1(t *testing.T) {
var t1 any
fmt.Println(t1) // nil 零值
t1 = 1
fmt.Println(t1) // 1
t1 = "fanfine.cn"
fmt.Println(t1) // fanfine.cn
t1 = map[string]any{
"domain": "fanfine.cn",
}
fmt.Println(t1) // map[domain:fanfine.cn]
t1 = func(a string) string { return a }
fmt.Println(t1)
}
泛型结构体
// 结构体泛型
type User[T any] struct {
Data T
Age int
}
func NewUser[T any](data T, age int) *User[T] {
return &User[T]{
Data: data,
Age: age,
}
}
func TestAnyStruct(t *testing.T) {
u1 := NewUser[string]("fanfine.cn", 18)
fmt.Println(u1) // &{fanfine.cn 18}
u2 := NewUser[bool](true, 18)
fmt.Println(u2) // &{true 18}
}
泛型切片
// 泛型 Slice
type AnySlice[T any] []T
func TestAnySlice(t *testing.T) {
// make
list := make(AnySlice[int], 0)
list = append(list, 1)
fmt.Println(list, reflect.TypeOf(list)) // [1] 包名.AnySlice[int]
// :=
list2 := AnySlice[string]{"1", "2"}
fmt.Println(list2, reflect.TypeOf(list2)) // [1 2] 包名.AnySlice[string]
}
泛型 Map
此处涉及多个约束的使用
// 泛型 Map
type AnyMap[k comparable, v any] map[k]v
// map
func TestAnyMap(t *testing.T) {
// make
m := make(AnyMap[string, int])
m["age"] = 1
fmt.Println(m, reflect.TypeOf(m)) // map[age:1] 包名.AnyMap[string,int]
// :=
m2 := AnyMap[string, string]{
"domain": "fanfine.cn",
}
fmt.Println(m2, reflect.TypeOf(m2)) // map[domain:fanfine.cn] 包名.AnyMap[string,string]
}
泛型 chan
// 泛型 chan
type AnyChan[T any] chan T
func TestAnyChan(t *testing.T) {
// 约束为 int
ch := make(AnyChan[int], 1)
defer close(ch)
ch <- 1
fmt.Println(<-ch) // 1
fmt.Println(reflect.TypeOf(ch)) // 包名.AnyChan[int]
// 其他约束,如:string
ch2 := make(AnyChan[string], 1)
defer close(ch2)
ch2 <- "fanfine.cn"
fmt.Println(<-ch2) // fanfine.cn
fmt.Println(reflect.TypeOf(ch2)) // 包名.AnyChan[string]
/*
指定约束后,不可写入其他类型,如:
ch <- "wrong" // cannot use "wrong" (untyped string constant) as int value in sendcompiler IncompatibleAssign
*/
// any
ch3 := make(AnyChan[any], 1)
go func() {
ch3 <- 1
ch3 <- "fanfine.cn"
ch3 <- 3.14
ch3 <- true
type Person struct {
Name string
}
ch3 <- &Person{Name: "fanfine"}
close(ch3)
}()
for v := range ch3 {
fmt.Println("type:", reflect.TypeOf(v), "value:", v)
}
/*
type: int value: 1
type: string value: fanfine.cn
type: float64 value: 3.14
type: bool value: true
type: *包名.Person value: &{fanfine}
*/
}
泛型接口
// 泛型 接口
type AnyInterface[T any] interface {
Job1(data T) T
}
type AnyStruct[T any] struct {
Data T
}
func NewAnyStruct[T any](data T) *AnyStruct[T] {
return &AnyStruct[T]{
Data: data,
}
}
func (s *AnyStruct[T]) Job1(data T) T {
return data
}
func TestAnyInterface(t *testing.T) {
// 约束为int
var anyInterface AnyInterface[int] = NewAnyStruct[int](111)
fmt.Println(anyInterface.Job1(111)) // 111
// 约束为string
var anyInterface2 AnyInterface[string] = NewAnyStruct[string]("fan")
fmt.Println(anyInterface2.Job1("fanfine.cn")) // fanfine.cn
// 约束为any
var anyInterface3 AnyInterface[any] = NewAnyStruct[any]("fan")
fmt.Println(anyInterface3.Job1("fanfine.cn")) // fanfine.cn
fmt.Println(anyInterface3.Job1(123)) // 123
}
参数篇
泛型作为函数参数
// 泛型作为函数的参数
func echo[T any](a T) T {
return a
}
func TestEcho(t *testing.T) {
// 约束参数类型为 int
num := echo[int](12)
/*
已经约束为 int 后,num 就是 int 类型,后续变量 num 只能传入 int 类型 , 否则会报错,例如:
num = "wrong" // cannot use "wrong" (untyped string constant) as int value in assignment
*/
num2 := echo(12)
/*
当然,如果约束为 any 、 interface{}
则 a 的类型就是 any、interface{},所以可以传入任意类型
*/
a := echo[any]("www.fanfine.cn")
a = 123
a = map[string]int{
"a": 1,
}
// 传入其他类型
str := echo("www.fanfine.cn")
bo := echo(true)
ma := echo(map[string]string{
"domain": "www.fanfine.cn",
})
fmt.Println("num 的类型", reflect.TypeOf(num)) // int
fmt.Println("num2 的类型", reflect.TypeOf(num2)) // int
fmt.Println("a 的类型", reflect.TypeOf(a)) // any
fmt.Println("str 的类型", reflect.TypeOf(str)) // string
fmt.Println("bo 的类型", reflect.TypeOf(bo)) // bool
fmt.Println("ma 的类型", reflect.TypeOf(ma)) // map[string]int
}
泛型作为方法参数
// 泛型作为方法参数
type Animal[T any] struct {
data T
}
// New 一个Animal,泛型作为函数参数
func NewAnimal[T any](data T) *Animal[T] {
return &Animal[T]{
data: data,
}
}
// 泛型作为方法参数
func (a *Animal[T]) GetData() T {
return a.data
}
func (a *Animal[T]) SetData(data T) {
a.data = data
}
func (a *Animal[T]) Job1(data T) T {
return data
}
// 测试方法的使用
func TestMethod(t *testing.T) {
a := NewAnimal[any]("fanfine.cn")
fmt.Println(a.GetData(), reflect.TypeOf(a.GetData())) // fanfine.cn string
a.SetData(1)
fmt.Println(a.GetData(), reflect.TypeOf(a.GetData())) // 1 int
// 也可约束为其他类型,比如 string
a2 := NewAnimal[string]("fanfine.cn")
fmt.Println(a2.GetData(), reflect.TypeOf(a2.GetData())) // fanfine.cn string
/*
此时约束为 string ,所以不能使用其他类型赋值
a2.SetData(1) // cannot use 1 (untyped int constant) as string value in argument to a2.SetData
*/
// a2 的初始化也可写作:编译器会根据参数的类型,推断出约束为 string
a2 = NewAnimal("fanfine.cn")
fmt.Println(reflect.TypeOf(a2.GetData())) // string
a3 := NewAnimal[int](1) // 此时约束为 int
fmt.Println(reflect.TypeOf(a3.GetData())) // int
}

