Golang接口
Golang接口声明了操作而不透露任何与数据相关的信息。
# 接口类型
接口类型是一种抽象的类型。它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合;它们只会表现出它们自己的方法。也就是说当你有看到一个接口类型的值时,你不知道它是什么,唯一知道的就是可以通过它的方法来做什么。
接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。 --《go语言圣经 (opens new window)》
# 接口值
概念上讲一个接口的值,接口值,由两个部分组成,一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值。....在我们的概念模型中,一些提供每个类型信息的值被称为类型描述符,比如类型的名称和方法。在一个接口值中,类型部分代表与之相关类型的描述符。
[!NOTE] Interface{} 空接口类型不会对数据进行任何方法实现上的要求,因此空接口值可以存任何类型的数据
# 接口值的可比性
接口值可以使用==和!=来进行比较。两个接口值相等仅当它们都是nil值,或者它们的动态类型相同并且动态值也根据这个动态类型的==操作相等。
# Golang八股:一个包含nil指针的接口不是nil接口
const debug = true
func main() {
var buf *bytes.Buffer
if debug {
buf = new(bytes.Buffer) // enable collection of output
}
f(buf) // NOTE: subtly incorrect!
if debug {
// ...use buf...
}
}
// If out is non-nil, output will be written to it.
func f(out io.Writer) {
// ...do something...
if out != nil {
out.Write([]byte("done!\n"))
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
该程序在debug=false是会发生panic,原因是
var buf *bytes.Buffer
这一行,buf
被赋值一个*bytes.Buffer
的空指针,值概念如下:
而方法f以
io.Writer
的类型传入参数,因此,buf
会被视为一个具有动态类型*bytes.Buffer
,动态值为nil
的接口,与nil接口的比较为false,进入条件导致nil指针解引用
只有当接口的动态方法与动态值同时为nil时,接口才是nil接口
[!NOTE] 这段程序的迷惑之处在于,我在阅读代码时,很少关注能够运行代码的传参类型,以为能传入就是类型可用,就是行为正确,只能说被rust养得太好了。
# 类型断言
有了接口值的概念模型,类型断言就会变得非常直觉 考虑这一段程序
var w io.Writer
w = os.Stdout
f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
2
3
4
后两行所做的赋值操作,其实就是在赋动态值的基础上,检查动态类型,值的类型是不会改变的,在上例中,都是os.File
不会panic的写法:
var w io.Writer = os.Stdout
f, ok := w.(*os.File) // success: ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
2
3
# 类型分支
知道了类型断言本质上是判断类型是否符合接口,那么就可以使用常规bool判断的思路,让类型断言更进一步
接口被以两种不同的方式使用。在第一个方式中,以io.Reader,io.Writer,fmt.Stringer,sort.Interface,http.Handler和error为典型,一个接口的方法表达了实现这个接口的具体类型间的相似性,但是隐藏了代码的细节和这些具体类型本身的操作。重点在于方法上,而不是具体的类型上。
第二个方式是利用一个接口值可以持有各种具体类型值的能力,将这个接口认为是这些类型的联合。类型断言用来动态地区别这些类型,使得对每一种情况都不一样。在这个方式中,重点在于具体的类型满足这个接口,而不在于接口的方法(如果它确实有一些的话),并且没有任何的信息隐藏。我们将以这种方式使用的接口描述为discriminated unions(可辨识联合)。
例子
func sqlQuote(x interface{}) string {
switch x := x.(type) {
case nil:
return "NULL"
case int, uint:
return fmt.Sprintf("%d", x) // x has type interface{} here.
case bool:
if x {
return "TRUE"
}
return "FALSE"
case string:
return sqlQuoteString(x) // (not shown)
default:
panic(fmt.Sprintf("unexpected type %T: %v", x, x))
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17