Post

Go中想“重载”方法,用泛型还是any?

Go中想“重载”方法,用泛型还是any?

众所周知,Go不支持方法重载。我们如果想写一个 n -> n*n 的平方方法,最简单粗暴的方法就是针对不同的类型编写不同方法名但具有类似方法体的方法。

1
2
3
4
5
6
7
func SquareInt(n int) int{
    return n*n
}

func SquareFloat(n float64) float64{
    return n*n
}

如果该场景只针对int\float64两种数据类型,那么这种直接的写法也较为容易被人接收。但实际情况下,我们要处理的数据类型很多,为每种数据类型都去写一套方法冗杂且低效。

行为相同时

行为相同时:多种数据类型针对同一行为的算法流程大致相同时,如int和float64平方的操作,算法流程相同,都是 n-> n*n

我们可以使用泛型来处理类型爆炸的问题。

  • 泛型实现
1
2
3
4
5
6
7
type Number interface{
    int|float64
}

func Square[T Number](n T) T{
    return n*n 
}

一行代码搞定多种类型。接下来我们来看any实现。

any是空接口,任意类型都实现了空接口,固然any可以接收任意类型

  • any实现

    由于入参类型是any ,我们不得不去做参数合法性校验(校验参数类型),必须switch type+default分支

1
2
3
4
5
6
7
8
9
10
func Square(n any) any{
    switch v := n.(type){
        case int:
        	return v * v
        case float64:
        	return v * v
        default:
        	return nil
    }
}

由此可得出结论,多种数据类型针对同一行为的算法流程大致相同时,最好使用泛型,因为他简单,入参类型不匹配直接在编译类型报错,若使用any进入default分支return nil还需要额外的判断。

行为不同时

但如果我们现在想为string类型编写Square函数呢? “n” ->”n”+”n”

  • any实现
1
2
3
4
5
6
7
8
9
10
func Square(n any) any{
    switch v := n.(type){
        case int:
        	return v * v
        case string:
        	return v + v //String类型与其他类型处理方式不同
        default:
        	return nil
    }
}
  • 泛型实现

    由于不同类型的行为不同,不能再用同一套流程了,需要分类型情况处理,怎么把泛型T确定为具体的类型?用反射!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type SquareType interface{
    int|string
}

func Square[T SquareType](n T) T{
    v := reflect.ValueOf(n)
    switch v.Kind(){
        case reflect.Int:
        	return any(int(v.Int())*int(v.Int())).(T)
        case reflect.String:
        	return any(v.String()+v.String()).(T)
        default:
        	//reutrn nil  这里直接返回nil是错的!!!!因为出参类型是T
        	var r T
        	return r
    }
    
}

可以观察到,用泛型处理不同行为的类型,不可避免的需要把确定的类型如整形,转化为泛型T返回。

需要先包装成any空接口,再.(T)把空接口转成泛型。好像有点麻烦。

结论

虽然针对不同数据类型处理行为不同的场景下,泛型需要先反射获取原类型,再原类型计算,再包装any,再.(T)变回泛型,实现麻烦。但是绝大多数情况下,建议使用泛型,来确保函数接收的参数在预期之内。你说你担心反射运行时性能太低?那就为每一个类型写一个方法吧!处理三个以内的类型可以被接受。

This post is licensed under CC BY 4.0 by the author.