理解 interface
1、interface 是一种类型
准确来说,interface 是带有一组方法的一种类型,这些方法定义了 interface 的行为。如果一个类型实现了一个 interface 中所有方法,则该类型实现了该 interface。又因为 go 允许不带任何方法的interface存在,这种interface成为空interface。所以所有类型都实现了 empty interface,因为任何一种类型至少实现了 0 个方法。
go 没有显式的关键字用来实现 interface,只需要实现 interface 包含的方法即可。
2、空 interface
interface{}
是一个空的 interface 类型,前面说到基本上所有的类型都可被空 interface 接收,因此如果定义一个函数参数是 interface{}
类型,这个函数应该可以接受任何类型作为它的参数。
func AnyType(i interface{}) {
fmt.Println(i)
}
func TestInterface(t *testing.T) {
var a int64 = 1
var b int8 = 2
var c = 3.01
var s = "str"
AnyType(a)
AnyType(b)
AnyType(c)
AnyType(s)
}
2.1 底层结构
空接口的底层结构记录在反射包中 reflect/value.go
:
可以看到空interface的结构体中有一个类型描述字段和一个包含着值的字段:
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
- word指向值的地址
- typ保存类型信息
2.2 interface{} 在反射中的运用
我们知道在使用反射时最重要的两个API就是 reflect.TypeOf 和 reflect.ValueOf ,而这两个方法的入参类型都是使用 interface{} 来接收任意类型的参数的,附上源码:
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
因为 interface{} 的底层结构就是 emptyInterface ,并且在接收原有类型转化为 interface{} 时系统底层就会初始化 emptyInterface 结构体,所以通过强转就能够获取到它对应的 emptyInterface 结构体,以及 typ 或 word 成员变量。
rtype结构体定义:
// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
size uintptr
ptrdata uintptr // number of bytes in the type that can contain pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldAlign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type, may be zero
}
拿到类型信息后,反射就可以通过解析这些信息确定对象的类型、获取属性信息、并且可以修改相关的属性,以及调用包含的方法等等操作。
这些操作的方法都定义在 reflect.Type 和 reflect.Value 中。所有类型的相关方法都统一定义在了这两个接口当中(参数统一转为 interface{} 接收的缘故),因此在调用每个方法时都会对类型进行判断,若非法则会抛出 panic 异常。
Kind 的定义如下:
type Kind uint
const (
Invalid Kind = iota // 非法类型
Bool // 布尔型
Int // 有符号整型
Int8 // 有符号8位整型
Int16 // 有符号16位整型
Int32 // 有符号32位整型
Int64 // 有符号64位整型
Uint // 无符号整型
Uint8 // 无符号8位整型
Uint16 // 无符号16位整型
Uint32 // 无符号32位整型
Uint64 // 无符号64位整型
Uintptr // 指针
Float32 // 单精度浮点数
Float64 // 双精度浮点数
Complex64 // 64位复数类型
Complex128 // 128位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)
reflect.Type 接口的方法汇总如下(都是与类型相关的信息):
type Type interface {
// Kind返回该接口的具体分类
Kind() Kind
// Name返回该类型在自身包内的类型名,如果是未命名类型会返回""
Name() string
// PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64"
// 如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""
PkgPath() string
// 返回类型的字符串表示。该字符串可能会使用短包名(如用base64代替"encoding/base64")
// 也不保证每个类型的字符串表示不同。如果要比较两个类型是否相等,请直接用Type类型比较。
String() string
// 返回要保存一个该类型的值需要多少字节;类似unsafe.Sizeof
Size() uintptr
// 返回当从内存中申请一个该类型值时,会对齐的字节数
Align() int
// 返回当该类型作为结构体的字段时,会对齐的字节数
FieldAlign() int
// 如果该类型实现了u代表的接口,会返回真
Implements(u Type) bool
// 如果该类型的值可以直接赋值给u代表的类型,返回真
AssignableTo(u Type) bool
// 如该类型的值可以转换为u代表的类型,返回真
ConvertibleTo(u Type) bool
// 返回该类型的字位数。如果该类型的Kind不是Int、Uint、Float或Complex,会panic
Bits() int
// 返回array类型的长度,如非数组类型将panic
Len() int
// 返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic
Elem() Type
// 返回map类型的键的类型。如非映射类型将panic
Key() Type
// 返回一个channel类型的方向,如非通道类型将会panic
ChanDir() ChanDir
// 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
NumField() int
// 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
Field(i int) StructField
// 返回索引序列指定的嵌套字段的类型,
// 等价于用索引中每个值链式调用本方法,如非结构体将会panic
FieldByIndex(index []int) StructField
// 返回该类型名为name的字段(会查找匿名字段及其子字段),
// 布尔值说明是否找到,如非结构体将panic
FieldByName(name string) (StructField, bool)
// 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 如果函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真
// 如果这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片)
// 如非函数类型将panic
IsVariadic() bool
// 返回func类型的参数个数,如果不是函数,将会panic
NumIn() int
// 返回func类型的第i个参数的类型,如非函数或者i不在[0, NumIn())内将会panic
In(i int) Type
// 返回func类型的返回值个数,如果不是函数,将会panic
NumOut() int
// 返回func类型的第i个返回值的类型,如非函数或者i不在[0, NumOut())内将会panic
Out(i int) Type
// 返回该类型的方法集中方法的数目
// 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
// 匿名字段导致的歧义方法会滤除
NumMethod() int
// 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
Method(int) Method
// 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
MethodByName(string) (Method, bool)
// 内含隐藏或非导出方法
}
2.3 举例说明
使用 reflect.Type.NumField 方法的底层实现作为例子,源码如下:
func (t *rtype) NumField() int {
if t.Kind() != Struct {
panic("reflect: NumField of non-struct type " + t.String())
}
tt := (*structType)(unsafe.Pointer(t))
return len(tt.fields)
}
上文提过 interface{} 类型的数据结构是 emptyStruct , 其中包含了 rtype 和 word 两个字段,rtype 携带了所有类型的字段信息。所以 reflect.Type 接口的方法都是由 rtype 结构体实现的,然后通过 rtype 结构体中的字段信息进行解析。
步骤:
1. 通过 t.kind 字段得到 Kind 类型,判断是否为结构体类型
func (t *rtype) Kind() Kind { return Kind(t.kind & kindMask) }
2. 若是,将 rtype 强转为 structType 类型(底层会通过 tflag 进行映射转换)
// structType represents a struct type.
type structType struct {
rtype
pkgPath name
fields []structField // sorted by offset
}
a := reflect.TypeOf(i) //int
b := reflect.TypeOf(j) //int
a == b
&a != &b
3. 返回字段 fields 数量
3、判断 interface 的类型
3.1 判断 interface{} 所属类型
3.1.1 直接断言
val, ok := unknow.(string)
如果返回 ok 为true,则变量 unknown 为 string 类型,同时返回一个 val 存储 string 类型的值。
如果我们确定 unknown 为 string 类型,也可以不返回 ok 变量,直接强转获取其值:
val := unknow.(string)
但是使用这种方法有一定的风险,如果不是 string 类型,会发生 panic:
panic: interface conversion: interface {} is int, not string
3.1.2 使用反射
获取反射类型对象使用reflect.TypeOf,获取反射值对象使用reflect.ValueOf,具体使用方法:
typeRef = reflect.TypeOf(unknow)
valueRef = reflect.ValueOf(unknow)
3.1.3 type 关键字判断(只适用于switch)
switch unknow.(type){
case string:
//string类型
case int:
//int类型
default:
}
3.2 判断 interface 接口的实现者
3.2.1 类型断言
当一个接口有多个实现时,也可以通过类型断言的方式来判断:
type I interface {
GetA() int
SetA(int)
}
type S struct {
A int
}
func(s S) GetA()int {
return s.A
}
func(s *S) SetA(a int) {
s.A = a
}
func f(i I){
if t, ok := i.(*S); ok {
// t.GetA()
}
}
3.2.2 type 关键字判断
switch t := i.(type) {
case *S:
//
case *R:
//
default:
//
}
3.2.3 interface 接口的类型断言必须是实现者之一
前文说了 interface{} 可以接收任意类型的对象,是因为空接口没有方法,因此任意类型的对象都实现了空接口,故皆可赋给 interface{} 。但是 interface 接口类型有着确定的类型,所以只能接收实现了这个接口的对象,类型断言也是如此。
反例:
type I interface {
GetA() int
SetA(int)
}
func f(i I){
switch t := i.(type) {
case *S:
//
case *int:
//
}
}
将会发生编译错误:
jiaqi.com/reflectDemo [jiaqi.com/reflectDemo.test]
./main_test.go:34:2: impossible type switch case: i (type I) cannot have dynamic type *int (missing GetA method)
4、interface 实现者的 receiver
先来看一段代码:
type I interface {
GetA() int
SetA(int)
}
type S struct {
A int
}
func(s S) GetA()int {
return s.A
}
func(s *S) SetA(a int) {
s.A = a
}
func f(i I){
if t, ok := i.(*S); ok {
// t.GetA()
}
}
func main() {
s := S{}
f(s)
}
Q:这段代码是否能正常运行?
A:不能,并且编译都不通过。
原因:S 中 setA 方法的 receiver 是个 pointer *S 。传给 f 的只是 S 的一份值拷贝并非指针(go 中函数都是按值传递)。因此使用 f(s)
的形式调用,在进行 s 的拷贝到 I 的转换时,s 的拷贝不满足 SetA 方法的 receiver 是个 pointer。因此报错 does not implement 'I'
如何解决上述问题?
func(s *S) SetA(a int)
改为👇🏻
func(s S) SetA(a int)
这样可否?
可以发现编译的报错消失了,程序可以正常运行了!
我们来执行一下👇🏻
type I interface {
GetA() int
SetA(a int)
}
type S struct {
A int
}
func(s S) GetA() int {
return s.A
}
func(s S) SetA(a int) {
s.A = a
}
func f(i I) {
i.SetA(10)
fmt.Println(i.GetA())
}
func TestReflect(t *testing.T) {
s := S{}
f(s)
f(&s)
}
输出:
0
导致这个问题的原因是 I 的实现者 S 的方法都是 value receiver (值调用),对于 receiver 是 value 的 method,任何在 method 内部对 value 做出的改变都不影响调用者看到的 value,因为 value 只是一份拷贝,go 将无从得知 value 的原始值是什么,这就是按值传递。
如果是按 pointer 调用,go 会自动进行转换,因为指针指向的是原有对象的地址,go 会把指针进行隐式转换得到 value,但反过来则不行。
5、interface 类型的隐式转换
我们可以通过一个例子再理解一下 interface{} ,下面的代码在 main
函数中初始化了一个 *TestStruct
类型的变量,由于指针的零值是 nil
,所以变量 s
在初始化之后也是 nil
package main
import "fmt"
type TestStruct struct{}
func NilOrNot(v interface{}) bool {
return v == nil
}
func main() {
var s *TestStruct
fmt.Println(s == nil) // #=> true
fmt.Println(NilOrNot(s)) // #=> false
var i interface{}
fmt.Println(NilOrNot(i)) // #=> true
}
执行结果:
true
false
true
出现上述现象的原因是 —— 调用 NilOrNot
函数时发生了隐式的类型转换。在类型转换时,*TestStruct
类型会转换成 interface{}
类型,转换后的变量不仅包含变量的值信息,还包含变量的类型信息 TestStruct
,所以转换后的变量与 nil
不相等(还记得 interface{} 的底层数据结构是 emptyStruct 吗,包含两个成员变量,一个存储类型信息的 typ,另一个存储值信息)。
16行代码输出为 true 的原因是 interface{} -> interface{} 类型的传参,无需进行隐式转换,因此是一个 nil 的 interface{} 。
6、reflect.Value.Kind 何时为 Interface
var i I = S{}
var ii interface{} = &i
typeOfI := reflect.TypeOf(ii).Elem()
fmt.Println("typeOfI Name:", typeOfI.Name())
fmt.Printf("typeOfI kind is Interface %t\n", typeOfI.Kind() == reflect.Interface)
valueOfI := reflect.ValueOf(ii)
fmt.Println(valueOfI.Elem().Kind())
fmt.Println(valueOfI.Elem().NumMethod())
fmt.Println(valueOfI.Elem().Elem().Kind())
fmt.Println(valueOfI.Elem().Elem().NumMethod())
输出:
typeOfI Name: I
typeOfI kind is Interface true
interface
2
struct
3
另一种方式,直接对 nil 进行强转:
reflect.TypeOf((*<interface>)(nil)).Elem()
可以参考一下下面这篇文章: