1. 概述
1.1 简介
接口是一种规范,描述了类的行为和功能,而不做具体实现
C++定义接口的方式称为“侵入式”,而Go采用的是“非侵入式”,不需要显式声明,只需要实现了接口定义的函数,编译器自动识别。
1.2 鸭子类型
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
Duck Typing: 鸭子类型,是动态编程语言的一种对象推断策略,它更关注对象能如何被使用,而不是对象的类型本身。Go语言作为一门静态语言,它通过接口方式完美支持鸭子类型。
1.3 接口特性
接口代表一种调用契约,是多个方法声明的集合。它把所有具有共性的方法定义在一起,任何其他类型只要全部实现了这些方法,就实现了该接口
- 一个或多个方法签名的集合
- 只要某类型拥有改接口的所有方法签名,即算实现该接口,无需显示声明实现了那些接口,此称为Structural Typing
- 接口中只有方法声明,没有实现
- 接口可匿名嵌入其他接口,或嵌入到结构中
- 将对象赋值给接口,会发生拷贝,而接口内部存储指向这个复制品的指针,即无法修改复制品的状态,也无法获取指针
- 只有当接口存储的类型和对象均为nil时,接口才能有nil
- 接口调用不会做receiver的自动转换
- 接口同样支持匿名字段方法
- 接口也可实现类似OOP中的多态
- 空接口可以作为任何类型数据的容器
2. 接口结构
接口src/runtime/runtime2.go
由两个指针构成:
- 指向变量的类型
- 指向变量的值
2.1 eface
空接口:不包含任何方法,即 interface{}
// src/runtime/runtime2.go
type eface struct {
_type *_type
data unsafe.Pointer
}
type _type struct {
size uintptr
ptrdata uintptr
hash uint32
tflag tflag
align uint8
fieldalign uint8
kind uint8
alg *typeA1g
gcdata *byte
str nameOff
ptrToThis typeOff
}
2.2 iface
非空接口:包含了一组方法集
// src/runtime/runtime2.go
type iface struct {
tab *itab
data unsafe.Pointer
}
// 非空接口的类型信息
type itab struct {
inter *interfacetype // 静态类型
_type *_type // 动态类型
link *itab
bad int32
inhash int32
unused [2]byte
fun [1]uintptr // 接口方法实现列表,即函数地址列表,按字典序排序
}
// runtime/type.go
// 非空接口类型,接口定义,包路径等。
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod // 接口方法声明列表,按字典序排序
}
// 接口的方法声明
type imethod struct {
name nameOff // 方法名
ityp typeOff // 描述方法参数返回值等细节
}
类型断言:
// src/rumtime/iface.go
func assertI2I(inter *interfacetype, tab *itab) *itab {
if tab == nil {
// explicit conversions require non-nil interface value.
panic(&TypeAssertionError{nil, nil, &inter.typ, ""})
}
if tab.inter == inter {
return tab
}
return getitab(inter, tab._type, false)
}
func assertI2I2(inter *interfacetype, i iface) (r iface) {
tab := i.tab
if tab == nil {
return
}
if tab.inter != inter {
tab = getitab(inter, tab._type, true)
if tab == nil {
return
}
}
r.tab = tab
r.data = i.data
return
}
func assertE2I(inter *interfacetype, t *_type) *itab {
if t == nil {
// explicit conversions require non-nil interface value.
panic(&TypeAssertionError{nil, nil, &inter.typ, ""})
}
return getitab(inter, t, false)
}
func assertE2I2(inter *interfacetype, e eface) (r iface) {
t := e._type
if t == nil {
return
}
tab := getitab(inter, t, true)
if tab == nil {
return
}
r.tab = tab
r.data = e.data
return
}
3. 获取类型
3.1 TypeOf
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i any) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
在调用 reflect.TypeOf
函数之前,已发送了一次隐式转换,将具体类型转换为 空接口(intreface{}
) ,该过程比较简单,只是 *rtype
和 unsafe.Pointer
两个指针
在反射包/src/reflect/value.go
中:
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
// nonEmptyInterface is the header for an interface value with methods.
type nonEmptyInterface struct {
// see ../runtime/iface.go:/Itab
itab *struct {
ityp *rtype // static interface type
typ *rtype // dynamic concrete type
hash uint32 // copy of typ.hash
_ [4]byte
fun [100000]unsafe.Pointer // method table
}
word unsafe.Pointer
}
3.2 类型断言
与类型转换的不同:
- 类型转换:转换前后两个类型要相互兼容才行
- 类型断言:对接口变量进行操作
func main() {
var a interface{}
var x float32 = 1.23
a = x
y, ok := a.(float32) // 类型断言
if ok {
fmt.Println(y)
} else {
fmt.Println("转换失败")
}
}
3.3 总结
// 方法一:本质使用反射,p.fmt.fmtS(reflect.TypeOf(arg).String())
func typeOf(v interface{}) string {
return fmt.Sprintf("%T\n", v)
}
// 方法二:反射
func typeOf(v interface{}) string {
return reflect.TypeOf(v).String()
}
// 方法三:类型断言
func typeOf(v interface{}) string {
switch v.(type) {
case int:
return "int"
case string:
return "string"
default:
return "unknown"
}
}
4. 静态和动态类型
- 静态类型: static type,变量声明时的类型。
- 动态类型: concrete type,程序运行时的具体类型
var i interface{} // 静态类型为interface
i = 8 // 动态类型为int
i = "abc" // 动态类型为string
4.1 零值接口
type iface struct {
tab *itab
data unsafe.Pointer
}
iface
比 eface
多了一层itab
结构。 itab
存储 _type类型信息
和[]fun方法集
,因此接口的 data
指向了nil 并不意味着 interface 等于 nil
零值接口:
- 动态类型为 nil
- 值为 nil
示例1:值为nil,但类型不为nil
type Stringer interface {
String() string
}
type Point struct {
x, y int
}
func (p *Point) String() string {
return fmt.Sprintf("(%d,%d)", p.x, p.y)
}
func main() {
var x Stringer
fmt.Printf("%T, %v, %t\n", x, x, x == nil) // <nil>, <nil>, true
x = &Point{1, 2}
fmt.Printf("%T, %v, %t\n", x, x, x == nil) // *main.Point, (1,2), false
x = (*Point)(nil)
fmt.Printf("%T, %v, %t\n", x, x, x == nil) // *main.Point, <nil>, false
fmt.Println(x.String()) // panic
}
示例2:非空接口,接收集体的实现
type Reader interface {
read()
}
type FileReader struct {
name string
}
func (r FileReader) read() {
fmt.Printf("read file %s\n", r.name)
}
func main() {
// 仅仅是一个未初始化的指针,即空指针
var r1 Reader
fmt.Println(r1 == nil) // true
fmt.Printf("%T, %v\n", r1, r1) // nil, nil
var r2 *FileReader
fmt.Println(r2 == nil) // true
fmt.Printf("%T, %v\n", r2, r2) // *main.FileReader, nil
// 非空接口iface,数据对象为空指针,但它还包含方法集等信息,不为nil
r1 = r2
fmt.Println(r1 == nil) // false
fmt.Printf("%T, %v\n", r1, r1) // *main.FileReader, nil
}
4.2 动态接口
调用fn函数
前,具体类型的 data
和 itab._type
两个指针被隐式转换成了 空接口interface {}
的 data
和 _type
指针,此时空接口的类型不为nil
func fn(v interface{}) string {
if v == nil {
return "empty-interface"
}
return "non-empty-interface"
}
func main() {
// i is nil
var i *int
// non-empty-interface
fmt.Println(fn(i))
// not work
if i != nil {
fmt.Printf("%v\n", i)
}
}
4.3 动态类型和值
func main() {
var a interface{} = nil
var b interface{} = (*int)(nil)
x := 1
var c interface{} = (*int)(&x)
fmt.Println(a == nil) // true
fmt.Println(b == nil) // false
fmt.Println(c == nil) // false
ia := *(*iface)(unsafe.Pointer(&a))
ib := *(*iface)(unsafe.Pointer(&b))
ic := *(*iface)(unsafe.Pointer(&c))
fmt.Println(ia) // {0 0}
fmt.Println(ib) // {17457856 0}
fmt.Println(ic) // {17457856 824634117808}
fmt.Println(*(*int)(unsafe.Pointer(ic.data))) // 1
}
5. 接口实现检查
type Animal interface {
eat()
}
type Dog struct{}
func (Dog) eat() {
fmt.Println("dog eat")
}
type Cat struct{}
func (Cat) walk() {
fmt.Println("cat walk")
}
func main() {
// 编译时检查
var _ Animal = new(Dog) // 编译通过,接口已实现
var _ Animal = new(Cat) // 编译失败,接口未实现
// 运行时检查
var _ Animal = (*Dog)(nil)
var _ Animal = (*Cat)(nil) // 失败
}