反射规则

前面介绍了Value和Type的基本概念。本文学习反射对象Value、Type和类型实例之间的相互转化。实例、Value、Type三者之间的转换关系如下:

反射对象关系

1. 1 反射API

反射API的分类总结如下:

  1. 从实例到Value
    通过实例获取Value对象,直接使用reflect.ValueOf()函数,例如:
func ValueOf(i interface{}) Value
  1. 从实例到Type
    通过实例获取反射对象的Type,直接使用reflect.TypeOf()函数,例如
func TypeOf(i interface{}) Type
  1. 从Type到Value
    Type里边只有类型信息,所以直接从一个Type接口变量里面是无法获得实例的Value的,但可以通过该Type构建一个新实例的Value,reflect包提供了两种方法,示例如下:
// New returns a Value representing a pointer to a new zero value for the specified type. That is, the returned Value's Type is PtrTo(typ).
func New(typ Type) Value {
	if typ == nil {
		panic("reflect: New(nil)")
	}
	t := typ.(*rtype)
	ptr := unsafe_New(t)
	fl := flag(Ptr)
	return Value{t.ptrTo(), ptr, fl}
}

// Zero returns a Value representing the zero value for the specified type. The result is different from the zero value of the Value struct, which represents no value at all. For example, Zero(TypeOf(42)) returns a Value with Kind Int and value 0. The returned value is neither addressable nor settable.
func Zero(typ Type) Value {
	if typ == nil {
		panic("reflect: Zero(nil)")
	}
	t := typ.(*rtype)
	fl := flag(t.Kind())
	if ifaceIndir(t) {
		return Value{t, unsafe_New(t), fl | flagIndir}
	}
	return Value{t, nil, fl}
}

如果知道一个类型值的底层存放地址,则还可以又一个函数是可以依据type和该地址值恢复出Value的,例如:

// NewAt returns a Value representing a pointer to a value of the specified type, using p as that pointer.
func NewAt(typ Type, p unsafe.Pointer) Value {
	fl := flag(Ptr)
	t := typ.(*rtype)
	return Value{t.ptrTo(), p, fl}
}
  1. 从Value到Type
    从反射对象Value到Type可以直接调用Value方法,因为Value内部存放着到Type类型的指针。例如:
func (v Value) Type() Type
  1. 从Value到实例
    Value本身就包含类型和值信息,reflect提供了丰富的方法来实现从Value到实例的转换。例如:
// packEface converts v to the empty interface.
func packEface(v Value) interface{} {
	t := v.typ
	var i interface{}
	e := (*emptyInterface)(unsafe.Pointer(&i))
	// First, fill in the data portion of the interface.
	switch {
	case ifaceIndir(t):
		if v.flag&flagIndir == 0 {
			panic("bad indir")
		}
		// Value is indirect, and so is the interface we're making.
		ptr := v.ptr
		if v.flag&flagAddr != 0 {
			// TODO: pass safe boolean from valueInterface so
			// we don't need to copy if safe==true?
			c := unsafe_New(t)
			typedmemmove(t, c, ptr)
			ptr = c
		}
		e.word = ptr
	case v.flag&flagIndir != 0:
		// Value is indirect, but interface is direct. We need
		// to load the data at v.ptr into the interface data word.
		e.word = *(*unsafe.Pointer)(v.ptr)
	default:
		// Value is direct, and so is the interface.
		e.word = v.ptr
	}
	// Now, fill in the type portion. We're very careful here not
	// to have any operation between the e.word and e.typ assignments
	// that would let the garbage collector observe the partially-built
	// interface value.
	e.typ = t
	return i
}
// Bool returns v's underlying value. It panics if v's kind is not Bool.
func (v Value) Bool() bool {
	v.mustBe(Bool)
	return *(*bool)(v.ptr)
}

// Float returns v's underlying value, as a float64. It panics if v's Kind is not Float32 or Float64
func (v Value) Float() float64 {
	k := v.kind()
	switch k {
	case Float32:
		return float64(*(*float32)(v.ptr))
	case Float64:
		return *(*float64)(v.ptr)
	}
	panic(&ValueError{"reflect.Value.Float", v.kind()})
}

// Int returns v's underlying value, as an int64. It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64.
func (v Value) Int() int64 {
	k := v.kind()
	p := v.ptr
	switch k {
	case Int:
		return int64(*(*int)(p))
	case Int8:
		return int64(*(*int8)(p))
	case Int16:
		return int64(*(*int16)(p))
	case Int32:
		return int64(*(*int32)(p))
	case Int64:
		return *(*int64)(p)
	}
	panic(&ValueError{"reflect.Value.Int", v.kind()})
}

// Uint returns v's underlying value, as a uint64. It panics if v's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64.
func (v Value) Uint() uint64 {
	k := v.kind()
	p := v.ptr
	switch k {
	case Uint:
		return uint64(*(*uint)(p))
	case Uint8:
		return uint64(*(*uint8)(p))
	case Uint16:
		return uint64(*(*uint16)(p))
	case Uint32:
		return uint64(*(*uint32)(p))
	case Uint64:
		return *(*uint64)(p)
	case Uintptr:
		return uint64(*(*uintptr)(p))
	}
	panic(&ValueError{"reflect.Value.Uint", v.kind()})
}
  1. 从Value的指针到值
    从一个指针类型的Value获得值类型Value有两种方法,如下:
// Elem returns the value that the interface v contains or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr. It returns the zero Value if v is nil.
//如果v类型是接口,则Elem()返回接口绑定的实例的Value,如果v类型是指针,则返回指针值的Value,否则引起panic
func (v Value) Elem() Value

// Indirect returns the value that v points to.
// If v is a nil pointer, Indirect returns a zero Value. If v is not a pointer, Indirect returns v.
// 如果v是指针,则返回指针值的Value,否则返回v自身,该函数不会引起panic
func Indirect(v Value) Value 
  1. Type指针和值的相互转换
    (1)指针类型Type到值类型Type:
//t必须是Array、Chan、Map、Ptr、Slice,否则会引起panic
//Elem返回的是其内部元素的Type
t.Elem() Type

(2)值类型Type到指针类型Type:

PtrTo返回的是指向t的指针型Type
func PtrTo(t Type) Type
  1. Value值的可修改性
    Value值的修改涉及两个方法:
//通过CanSet判断是否能修改
func (v Value) CanSet() bool

//通过Set进行修改
func (v Value) Set(x Value)

Value值在什么情况下可以修改?

  • 值可修改条件之一:可被寻址。通过反射修改变量值的前提条件之一:这个值必须可以被寻址。简单地说就是这个变量必须能被修改。
  • 值可修改条件之一:被导出。结构体成员中,如果字段没有被导出,即便不使用反射也可以被访问,但不能通过反射修改。

实例对象传递给接口的是一个完全的值拷贝,如果调用反射的方法reflect.ValueOf()传进去的是一个值类型变量,则获得的Value实际上是对原对象的一个副本,因此这个Value不能被修改。如果传进去的是一个指针,但是通过指针还是可以访问到最原始的对象,因此这种情况的Value可以被修改。

值的修改从表面意义上叫可寻址,换一种说法就是值必须“可被设置”。那么,想修改变量值,一般的步骤是

  1. 取这个变量的地址或者这个变量所在的结构体已经是指针类型。
  2. 使用 reflect.ValueOf 进行值包装。
  3. 通过 Value.Elem() 获得指针值指向的元素值对象(Value),因为值对象(Value)内部对象为指针时,使用 set 设置时会报出宕机错误。
  4. 使用 Value.SetXXX 设置值。

示例代码:

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Id   int
	Name string
	Age  int
}

func main() {
	u := User{1, "Tom", 20}
	va := reflect.ValueOf(u)
	vb := reflect.ValueOf(&u)

	//值类型是不可修改的
	fmt.Println(va.CanSet(), va.FieldByName("Name").CanSet())

	//指针类型是可修改的
	fmt.Println(vb.CanSet(), vb.Elem().FieldByName("Name").CanSet())

	fmt.Printf("%v\n", vb)
	name := "Jerry"
	vc := reflect.ValueOf(name)

	//通过Set函数修改变量的值
	vb.Elem().FieldByName("Name").Set(vc)
	fmt.Printf("%v\n", vb)
}
//代码输出
false false
false true
&{1 Tom 20}
&{1 Jerry 20}

1.2 反射三定律

(1)反射可以从接口值得到反射对象;
(2)反射可以从反射对象获得接口值;
(3)若要修改一个反射对象,则其值必须可以修改。

参考资料

  1. 《Go语言核心编程》
  2. https://books.studygolang.com/gopl-zh/ch12/ch12-05.html
  3. https://zhuanlan.zhihu.com/p/64884660
  4. https://www.cnblogs.com/itbsl/p/10551880.html
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐