golang-泛型基础篇(二)

前言

在上一篇文章中介绍了 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+泛型参数来实现。
  • 泛型类型不能直接使用,要使用的话必须传入类型实参进行实例化

你可能感兴趣的:(golang,golang,java,数据结构)