Golang: 泛型容器的安全问题 (2)

本节不讲如何去实现泛型容器,主要关注:如何不用 类型断言 确认 generic container 的安全,且编译时就能确认。

专栏的介绍可以参考 《GotchaGolang专栏》,代码可以看《宝库-Gotcha》

如果编译时能够检查泛型容器的安全那最好不过了。

案例

话不多说,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
"fmt"
)

//创建一个 generic container, 底层用 slice
type Container []interface{}

func (c *Container) Put(elem interface{}) {
*c = append(*c, elem) //append 方法可能比较危险(重新分配底层数组问题),所以重新赋值一下
}
func (c *Container) Get() interface{} {
elem := (*c)[0]
*c = (*c)[1:]
return elem //返回顶元素
}
func (c *Container) IsEmpty() bool {
return len(*c) == 0
}

func main() {
intContainer := &Container{}
intContainer.Put(1)
intContainer.Put(3)

for !intContainer.IsEmpty() {
elem, ok := intContainer.Get().(int)
if !ok {
fmt.Println("Get() did not return an integer.")
continue
}
fmt.Printf("Elem: %d (%[1]T)\n", elem)
}

}

虽然用了 type assertion,但是一旦你放进去了一个异类对象,立即 error 退出了:

1
2
3
4
intContainer := &Container{}
intContainer.Put(1)
intContainer.Put("5") //Get() did not return an integer.
intContainer.Put(3)

这种问题,编译时就应该检查出来的,而不是等到运行时报错,走异常流程。

————通用容器,一定要丢失类型检查么?

这个时候可以 借助内嵌 来搞定这个问题,即显示的把 generic container 需要确定类型的部分,具体化

即不直接再使用通用容器了,使用具体化的某个容器: (内嵌的话,原来的方法还是照样当做自己的使用)

1
2
3
4
5
6
7
8
9
10
11
12
type IntContainer struct {
Container
}

func (ic *IntContainer) Put(elem int) { //入参改成了 int,编译时进行检查
(*ic).Container.Put(elem)
}

intContainer := &IntContainer{Container{}}
intContainer.Put(1)
//intContainer.Put("5") //只要你这么写,编译时就报错了,不用等到运行时了
intContainer.Put(3)

完整的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main

import (
"fmt"
)

//创建一个 generic container, 底层用 slice
type Container []interface{}

func (c *Container) Put(elem interface{}) {
*c = append(*c, elem) //append 方法可能比较危险(重新分配底层数组问题),所以重新赋值一下
}
func (c *Container) Get() interface{} {
elem := (*c)[0]
*c = (*c)[1:]
return elem //返回顶元素
}
func (c *Container) IsEmpty() bool {
return len(*c) == 0
}

type IntContainer struct {
Container
}

func (ic *IntContainer) Put(elem int) { //入参 具体化
(*ic).Container.Put(elem)
}

func main() {
/* intContainer := &Container{}
intContainer.Put(1)
//intContainer.Put("5") //Get() did not return an integer.
intContainer.Put(3) */

intContainer := &IntContainer{Container{}}
intContainer.Put(1)
//intContainer.Put("5") //只要你这么写,编译时就报错了,不用等到运行时了
intContainer.Put(3)



for !intContainer.IsEmpty() {
elem, ok := intContainer.Get().(int)
if !ok {
fmt.Println("Get() did not return an integer.")
continue
}
fmt.Printf("Elem: %d (%[1]T)\n", elem)
}

}

总结

泛型容器肯定会失去一部分编译时的安全检查,这是哪种拥有泛型 feature 的语言都避免不了的。
但是如果针对某具体的类型,可以尝试实现以下具体的类型,继承也好,组合也好,部分具体化产生具体容器。


Merlin 2018.3 泛型容器的坑

文章目录
  1. 1. 案例
  2. 2. 总结
|