什么是interface{}
简单的来说, interface{} 是指两个内容:
- 一堆方法 (a set of methods)
- 同时也是一个类型
对于Go中的interface{}
, 通常指一个空的接口(empty interface), 也就是说这个interface并没有任何方法.
不像Java, 可以使用implement
这种关键词来手动继承接口, 所以在Go中, 所有的类型(type), 即便一个不包含任何方法的类型(type), 都会自动的继承空的接口(也就是interface{}
, 上一段中说的空的接口).
所以, 如果一个函数用interface{}
作为参数, 那么意味着这个参数可以接受任何类型的值.
困扰么?
假设我们有一个函数, 定义如下
func DoSomething(v interface{}) {
// ...
}
那么在DoSomething
的函数内部, v
的类型是什么?
v
是任何类型 (v
is of any type)
但这种观点显然是错误的, v
并不是任何类型, 而是interface{}
类型.
在调用DoSomething
, 并且传递一个参数给DoSomething
的时候, Go运行时会在必要的阶段执行一次类型转换(type conversion), 并且把这个传递的值转换为一个interface{}
类型的值. 所以从运行时的角度看, 任何值都是一个类型的, 而且interface{}
是v
的一个静态类型(static type).
一个interface类型的值的结构是什么样的?
为了更进一步了解, 参考了一下Go Data Structures: Interfaces的部分内容
假设一个interface
定义如下
type Stringer interface {
String() string
}
Interface values are represented as a two-word pair giving a pointer to information about the type stored in the interface and a pointer to the associated data.
Assigning b to an interface value of type Stringer sets both words of the interface value.
interface
类型的值通常包含两部分内容
- 一个指向该类型的方法表的指针
- 一个指向该类型的内容的指针
The first word in the interface value points at what I call an interface table or itable.
那么第一个word指向的是一个接口表itable(an interface table), 这段数据开头拥有一些关于需要的类的元信息(metadata), 之后便是一个储存各个方法的指针的列表.
Note that the itable corresponds to the interface type, not the dynamic type.
所以说itable只包含和interface{}
类型有关的方法, 而不包含一个动态类型的所有方法.
换句话说, Stringer
这个类型的itable虽然申明了需要Binary
这个类, 但是itable的方法列表中却只有String
这个方法, 而Binary
类中的其他方法(比如Get
)并没有出现在itable中.
The second word in the interface value points at the actual data.
interface
结构中的第二个word是一个指向值的内存空间的指针, 也就是说Go运行时会开辟一段新的内存储存具体的值的内容. 所以当我们使用var s Stringer = b
来申明s
的时候, 其实是复制了b
的内容, 而不是直接将指针指向b
的内存地址. 所以当我们修改b
的内容的时候, s
的内容不会被改变.
通常情况下, 我们储存在interface
中的值可能非常大, 但对于interface
类型来说, 运行时只利用了1个word的大小来储存指针, 所以Go在堆中开辟了一大块内存, 并且用1个word大小的内存来记录这块内存的指针.