golang拾遗:为什么我们需要泛型( 三 )


def transform(self):for i in range(len(self.data)):self.data[i] = self.data[i].upper()我们提供了一个方法 , 可以将队列中的字符串从小写转换为大写 。 问题发生了 , 我们的队列不仅可以接受字符串 , 它还可以接受数字 , 这时候如果我们调用transform方法就会发生运行时异常:AttributeError: 'int' object has no attribute 'upper' 。 那么怎么避免问题呢?添加运行时的类型检测就可以了 , 然而这样做有两个无法绕开的弊端:

  • 写出了类型相关的代码 , 和我们本意上想要实现类型无关的代码结构相冲突
  • 限定了算法只能由几种数据类型使用 , 但事实上有无限多的类型可以实现upper方法 , 然而我们不能在类型检查里一一列举他们 , 从而导致了我们的通用算法变为了限定算法 。
动静结合没有泛型的世界实在是充满了煎熬 , 不是在违反DRY原则的边缘反复试探 , 就是冒着类型安全的风险激流勇进 。 有什么能脱离苦海的办法吗?
作为一门静态强类型语言 , golang提供了一个不是太完美的答案——interface 。
使用interface模拟泛型interface可以接受任何满足要求的类型的数据 , 并且具有运行时的类型检查 。 双保险很大程度上提升了代码的安全性 。
一个典型的例子就是标准库里的containers:
package list // import "container/list"Package list implements a doubly linked list.To iterate over a list (where l is a *List):for e := l.Front(); e != nil; e = e.Next() {// do something with e.Value}type Element struct{ ... }type List struct{ ... }func New() *Listtype Element struct {// The value stored with this element.Value interface{}// Has unexported fields.}Element is an element of a linked list.func (e *Element) Next() *Elementfunc (e *Element) Prev() *Elementtype List struct {// Has unexported fields.}List represents a doubly linked list. The zero value for List is an emptylist ready to use.func New() *Listfunc (l *List) Back() *Elementfunc (l *List) Front() *Elementfunc (l *List) Init() *Listfunc (l *List) InsertAfter(v interface{}, mark *Element) *Elementfunc (l *List) InsertBefore(v interface{}, mark *Element) *Elementfunc (l *List) Len() intfunc (l *List) MoveAfter(e, mark *Element)func (l *List) MoveBefore(e, mark *Element)func (l *List) MoveToBack(e *Element)func (l *List) MoveToFront(e *Element)func (l *List) PushBack(v interface{}) *Elementfunc (l *List) PushBackList(other *List)...这就是在上一大节中的方案2的类型安全强化版 。 接口的工作原理本文不会详述 。
但事情远没有结束 , 假设我们要对一个数组实现indexOf的通用算法呢?你的第一反应大概是下面这段代码:
func IndexOfInterface(arr []interface{}, value interface{}) int { for i, v := range arr {if v == value {return i} } return -1}这里你会接触到interface的第一个坑 。
interface会进行严格的类型检查看看下面代码的输出 , 你能解释为什么吗?
func ExampleIndexOfInterface() {arr := []interface{}{uint(1),uint(2),uint(3),uint(4),uint(5)} fmt.Println(IndexOfInterface(arr, 5))fmt.Println(IndexOfInterface(arr, uint(5)))// Output:// -1// 4}会出现这种结果是因为interface的相等需要类型和值都相等 , 字面量5的值是int , 所以没有搜索到相等的值 。
想要避免这种情况也不难 , 创建一个Comparable接口即可:
type Comparator interface { Compare(v interface{}) bool}func IndexOfComparator(arr []Comparator, value Comparator) int { for i,v := range arr {if v.Compare(value) {return i} } return -1}这回我们不会出错了 , 因为字面量根本不能传入函数 , 因为内置类型都没实现Comparator接口 。
内置类型何去何从然而这是接口的第二个坑 , 我们不得不为内置类型创建包装类和包装方法 。
假设我们还想把前文的arr直接传入IndexOfComparator , 那必定得到编译器的抱怨:
cannot use arr (type []interface {}) as type []Comparator in argument to IndexOfComparator为了使用这个函数我们不得不对代码进行修改:
type MyUint uintfunc (u MyUint) Compare(v interface{}) bool { value := v.(MyUint) return u == value}arr2 := []Comparator{MyUint(1),MyUint(2),MyUint(3),MyUint(4),MyUint(5)}fmt.Println(IndexOfComparator(arr2, MyUint(5)))我们希望泛型能简化代码 , 但现在却反其道而行之了 。
性能陷阱第三个 , 也是被人诟病最多的 , 是接口带来的性能下降 。
我们对如下几个函数做个简单的性能测试:
func IndexOfByReflect(arr interface{}, value interface{}) int { arrValue := reflect.ValueOf(arr) length := arrValue.Len() for i := 0; i < length; i++ {if arrValue.Index(i).Interface() == value {return i} } return -1}func IndexOfInterface(arr []interface{}, value interface{}) int { for i, v := range arr {if v == value {return i} } return -1}func IndexOfInterfacePacking(value interface{}, arr ...interface{}) int { for i, v := range arr {if v == value {return i} } return -1}