Go 反射与interface拾遗

nino's blog · · 1771 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Go能实现接口的动态性和反射的基础是:编译期为运行时提供了类型信息。interface底层有两种结构,上一节讲了带方法的iface,这一节补充不带方法的eface结构。

interface之eface

Go中的任何对象都可以表示为interface{}。它扮演的角色与C中的void*差不多,区别在于interface{}中包含有类型信息,可以实现反射。

eface数据结构描述gcdata域用于垃圾回收,size描述类型的大小,hash表示数据的hash值,align是对齐,fieldalign是这个数据嵌入结构体时的对齐,kind是枚举值。alg是函数指针的数组,存储了hash/equal/print/copy四个函数操作。uncommentType指向这个类型的方法集。


type eface struct {
	_type *_type
	data unsafe.Pointer
}
type _type struct {
	size       uintptr
	ptrdata    uintptr // size of memory prefix holding all pointers
	hash       uint32
	_unused    uint8
	align      uint8
	fieldalign uint8
	kind       uint8
	alg        *typeAlg
	gcdata  *byte
	_string *string
	x       *uncommontype
	ptrto   *_type
	zero    *byte // ptr to the zero value for this type
}

运行时convT2E

T2E的转换 赋值给空interface{},运行时会调用conv类函数convT2E。与convT2I一样,向interface的类型转换var i interface{} = u都存在内存拷贝。看iface.go源码时发现convI2I等接口转换时没有分配内存和拷贝数据,原因可能是Go接口内部的data域,并不开放途径让外部修改,所以接口之间转换可以用同一块内存。


func convT2E(t *_type, elem unsafe.Pointer, x unsafe.Pointer) (e interface{}) {
	ep := (*eface)(unsafe.Pointer(&e))
  ...
	if x == nil {
		x = newobject(t)
	}
	typedmemmove(t, x, elem)
	ep._type = t
	ep.data = x
	}
	return
}

下面的例子中修改u并不影响i interface的内存数据,因为i在赋值时通过convT2E对u进行了拷贝。这也是反射非指针变量时无法直接改变变量数据的原因,因为反射会先把变量转成interface类型,拿到的是变量的副本。


u := User{1, "Tom"}
var i interface{} = u
u.id = 2
u.name = "Jack"
// u {2, "Jack"}
// i.(User) {1, "Tom"}

nil的理解

未初始化的interface类型,指针,函数,slice,cannel和map都是nil的。对于interface比较特殊,只有eface的type和data都是nil,或者iface的type和data都是nil时,interface{}才是nil。


type Duck interface{
    Walk()
}
var i interface{}   // nil
var d Duck   // nil
var v *T   // nil
i = v   // (*T)(nil) not nil

type assertion

严格来说Go并不支持泛型编程,但通过interface可实现泛型编程,后面reflect浅析中有个通过reflect实现泛型的例子。interface像其它类型转换的时候一般需要断言。下面只给出了eface的例子,当然也可以通过断言来判断某个类型是否实现了某个接口。


func do(v interface{}) {
	n, ok := v.(int)
	if !ok {...}
}
func doswitch(i interface{}) {
	switch v := i.(type) {
	case int: ...
	}
}

对应的go源码在iface.go当中。assertE2T过程判断了eface的type字段是否和目标type相等,相等则还需要拷贝数据。assertI2T也要拷贝数据,不过他比较的是iface.tab._type与目标type是否一致。


func assertE2T(t *_type, e interface{}, r unsafe.Pointer) {
	ep := (*eface)(unsafe.Pointer(&e))
	if ep._type == nil {
		panic(&TypeAssertionError{"", "", *t._string, ""})
	}
	if ep._type != t {
		panic(&TypeAssertionError{"", *ep._type._string, *t._string, ""})
	}
	if r != nil {
		if isDirectIface(t) {
			writebarrierptr((*uintptr)(r), uintptr(ep.data))
		} else {
			typedmemmove(t, r, ep.data)
		}
	}
}

reflect浅析

反射机制提供了检查存储在接口变量中的[类型 值]对的机制。根据Laws Of ReflectionGo的反射可以总结三点,即反射可以从interface中获取reflect对象;同时可以通过Interface()方法恢复reflect对象为一个interface;如果要修改反射对象,该对象必须是settable的。

TypeOf与ValueOf实现

获取反射对象的实现,是基于对interface底层数据的操作。首先对象经过了convT2E,然后emptyInterface直接指向了eface的type字段。typeOf返回的Type是接口,在类型_type上实现了很多操作。valueOf返回的就是Value结构体,它包含了数据域和type域信息。


func TypeOf(i interface{}) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}
func ValueOf(i interface{}) Value {
	if i == nil {
		return Value{}
	}
	escapes(i)
	return unpackEface(i)
}
func unpackEface(i interface{}) Value {
	e := (*emptyInterface)(unsafe.Pointer(&i))
	t := e.typ
	if t == nil {
		return Value{}
	}
	f := flag(t.Kind())
	if ifaceIndir(t) {
		f |= flagIndir
	}
	return Value{t, unsafe.Pointer(e.word), f}
}

type Type interface{
  ...
	Method(int) Method
	MethodByName(string) (Method, bool)
	NumMethod() int
	NumField()
	Field(i) StructField
	FieldByName(name string) (StructField, bool)
  ...
}
type Value struct {
	typ *rtype
	ptr unsafe.Pointer
	flag
}

通过Type获取Struct Field信息

可以通过reflect获取类型实例的结构体信息,比如每个field的名字类型或标签。


// A StructField describes a single field in a struct.
type StructField struct {
	Name string  // Name is the field name.
	PkgPath string
	Type      Type      // field type
	Tag       StructTag // field tag string
	Offset    uintptr   // offset within struct, in bytes
	Index     []int     // index sequence for Type.FieldByIndex
	Anonymous bool      // is an embedded field
}
type Person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}
func main() {
	nino := Person{"nino", 27}
	t := reflect.TypeOf(nino)
	n := t.NumField()
	for i := 0; i < n; i++ {
		fmt.Println(t.Field(i).Name, t.Field(i).Type, t.Field(i).Tag)
	}
}
// Name string json:"name"
// Age int json:"age"

通过Value实现泛型

为了解决method接受不同类型的slice为入参,可以用反射来完成。对于可记长度和可随机访问的类型,可以通过v.Len()v.Index(i)获取他们的第几个元素。

v.Index(i).Interface()将reflect.Value反射回了interface类型


func method(in interface{}) (ok bool) {
	v := reflect.ValueOf(in)
	if v.Kind() == reflect.Slice {
		ok = true
	} else {
		return false
	}
	num := v.Len()
	for i := 0; i < num; i++ {
		fmt.Println(v.Index(i).Interface())
	}
	return ok
}
func main() {
	s := []int{1, 3, 5, 7, 9}
	b := []float64{1.2, 3.4, 5.6, 7.8}
	method(s)
	method(b)
}

通过Elem修改reflect对象值

对LawsOfReflect第三点的理解 reflect.ValueOf 如果直接传入x,则v是x的一个副本的reflect对象。修改v的值并不会作用到x上。p是指向x的指针的reflect对象,修改p的值是在修改指针的指向,同样不会作用到x上,因此也是CanNotSet的。只有通过p.Elem()相当于获取了*p的reflect对象,这时才能使用v.SetFloat(7.2)来对原始的数据进行修改。


var x float64 = 3.4
v := reflect.ValueOf(x)   // can not set
p := reflect.ValueOf(&x)  // can not set
e := p.Elem()  // can set

有疑问加站长微信联系(非本文作者)

本文来自:nino's blog

感谢作者:nino's blog

查看原文:Go 反射与interface拾遗

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1771 次点击  
加入收藏 微博
被以下专栏收入,发现更多相似内容
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传