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方法 , 然而我们不能在类型检查里一一列举他们 , 从而导致了我们的通用算法变为了限定算法 。
作为一门静态强类型语言 , 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}
- 看不上|为什么还有用户看不上华为Mate40系列来看看内行人怎么说
- 制药领域|为什么AI制药这么火,为什么是现在?
- 手机壳里头|为什么要在手机壳里面夹钱?10个有9个不懂,我才知道大有讲究
- 短视频|全球最火APP?抖音爆火背后离不开这几剂“猛药”为什么抖音能够这么火?
- 电商快递|包邮不香吗,为什么还有人加49元让小哥穿西装专车送快递?
- 团队|为什么项目管理非常重要?
- 猫腻|为什么拼多多上商品价格那么便宜还包邮?有什么猫腻?看完明白了
- 刷机|前几年满大街的“刷机”服务去哪里了,为什么大家都不爱刷机了?
- 手机|便宜没好货!为什么二手iPhone很便宜,这些手机都来自哪儿?
- 中国|相对论Vol.48丨一个“歪果仁”,为什么要在海外电商平台直播带中国货
