前言
在上一篇文章中介绍了 golang中泛型的定义,泛型结构体,部分错误的泛型写法。
type MyStruct[T int | string] struct {
Data []T
}
注意:匿名结构体不支持泛型
。
原因(个人理解):定义泛型,相当于定义新的类型,然后对其进行初始化。使用匿名结构体时,定义好匿名结构体之后直接初始化,相当于每次定义一个新的结构体类型进行初始化。不太符合泛型的要求。
泛型receiver
定义了新的普通类型之后可以给类型添加方法。那么可以给泛型类型添加方法吗?答案是可以的,如下:
type MySlice[T int | float32] []T
func (s MySlice[T]) Sum() T {
var sum T
for _, value := range s {
sum += value
}
return sum
}
func TestSlice(t *testing.T) {
var mySlice = MySlice[int]{}
mySlice = append(mySlice, []int{1, 2, 3, 4, 5, 6, 7, 8, 9}...)
fmt.Println(mySlice.Sum())
}
泛型receiver
的实用性。在没有泛型之前如果想实现通用的数据结构,诸如:堆、栈、队列、链表之类的话,我们的选择只有两个:
- 为每种类型写一个实现(每个类型写一次,代码量大)
- 使用 接口+反射(反射效率低)
基于泛型的队列
//基于泛型的队列
type MyQueue[T interface{}] struct {
elements []T
}
func (q *MyQueue[T]) Push(value T) {
q.elements = append(q.elements, value)
}
func (q *MyQueue[T]) Pop() (value T, isEmtry bool) {
if len(q.elements) == 0 {
return value, true
}
value = q.elements[0]
q.elements = q.elements[1:]
return value, len(q.elements) == 0
}
// 队列大小
func (q MyQueue[T]) Size() int {
return len(q.elements)
}
基于泛型的栈
//基于泛型的栈
type MyStack[T interface{}] struct {
elements []T
}
func (q *MyStack[T]) Push(value T) {
q.elements = append(q.elements, value)
}
func (q *MyStack[T]) Pull() (value T, isEmtry bool) {
if len(q.elements) == 0 {
return value, true
}
value = q.elements[len(q.elements)-1]
q.elements = q.elements[:len(q.elements)-1]
return value, len(q.elements) == 0
}
// 队列大小
func (q MyStack[T]) Size() int {
return len(q.elements)
}
泛型不能进行类型断言
对于interface
类型的参数,可以对其进行类型断言,然后不同类型做出不同处理呢。对于T
这样通过类型形参定义的变量,我们不能判断具体类型。
func (q *Queue[T]) Put(value T) {
value.(int) // 错误。泛型类型定义的变量不能使用类型断言
// 错误。不允许使用type switch 来判断 value 的具体类型
switch value.(type) {
case int:
// do something
case string:
// do something
default:
// do something
}
}
虽然泛型
和类型断言
一起不能用,但我们可通过反射机制达到目的:
func (receiver Queue[T]) Put(value T) {
// Printf() 可输出变量value的类型(底层就是通过反射实现的)
fmt.Printf("%T", value)
// 通过反射可以动态获得变量value的类型从而分情况处理
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Int:
// do something
case reflect.String:
// do something
}
}
当你写出上面这样的代码时候就出现了一个问题:
你为了避免使用反射而选择了泛型,结果到头来又为了一些功能在在泛型中使用反射。当出现这种情况的时候你可能需要重新思考一下,自己的需求是不是真的需要用泛型。
泛型函数
泛型函数在第一篇介绍过写法
func Add[T int | float32 | float64](a T, b T) T {
return a + b
}
func TestADD(t *testing.T) {
//写法1
fmt.Println(Add[](10, 20))
//写法2
fmt.Println(Add[int](100, 200))
fmt.Println(Add[float64](10.5, 24.6))
}
和泛型类型一样,泛型函数也是不能直接调用的,要使用泛型函数的话必须传入类型实参之后才能调用。
匿名函数不支持泛型:匿名函数不能自己定义类型形参。
// 错误,匿名函数不能自己定义类型实参
fnGeneric := func[T int | float32](a, b T) T {
return a + b
}
fmt.Println(fnGeneric(1, 2))
泛型方法
go里面不止有函数还有方法。有泛型函数,有没有泛型方法呢?结果:目前Go的方法并不支持泛型。但是因为receiver
支持泛型, 所以如果想在方法中使用泛型的话,我们可以这样。
type A[T int | float32 | float64] struct {
}
// 方法可以使用类型定义中的形参 T
func (receiver A[T]) Add(a T, b T) T {
return a + b
}
// 用法:
var a A[int]
a.Add(1, 2)
var aa A[float32]
aa.Add(1.0, 2.0)
个人理解:函数主要针对传参进行操作,方法可以针对receiver
进行操作。如果传入一个泛型的参数赋值给一个非泛型receiver
的属性,可能会报错( 如:int value in variable)。所以不支持泛型方法。
总结
Go的泛型(或者或类型形参)目前可使用在3个地方
- 泛型类型 - 类型定义中带类型形参的类型
- 泛型
receiver
- 泛型类型的receiver
- 泛型函数 - 带类型形参的函数
- 没有匿名的泛型类型(匿名结构体,匿名方法)
- 没有泛型的方法,但是泛型方法可以通过泛型
receiver
+泛型参数来实现。 - 泛型类型不能直接使用,要使用的话必须传入类型实参进行实例化