golang的reflect包

1. 判断任意一个interface{}对象的类型

kind := reflect.TypeOf(nic).Kind()


// 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 interface{}) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}

这个reflect.Typeof传入任意数据类型,也就是说入参i在编译期间并不知道,所以i在这里是一个动态类型,所以它的数据类型是golang里面的interface{},方法的返回值是一个Type,这个Type是啥玩意?它是reflect/type.go文件中声明的一个接口,golang非的把任意数据类型用interface{}来表示,这搞的很繁琐,别的语言直接用object不是挺好的嘛

 可以看到Type这个interface里面的函数挺多的,可见Type描述了golang的数据类型,golang里面的数据类型那是多种多样,重点看它的Kind方法,而Kink是一个枚举

又看到了golang里面的老朋友,通过type关键字基于uinit搞出一个新的枚举类型,我们输出这些Kind常量看看它们取值是多少?

// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer
)

实际上打印出了它们的字符串表示 

fmt.Println(reflect.Invalid, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16)

invalid bool int int8 int16

但是让我颠覆认知的是,golang里面的数组竟然还可以这样声明,直接指定它的索引,我特么也是醉了,这帮golang开发团队的老家伙们。 

func (k Kind) String() string {
	if int(k) < len(kindNames) {
		return kindNames[k]
	}
	return "kind" + strconv.Itoa(int(k))
}

var kindNames = []string{
	Invalid:       "invalid",
	Bool:          "bool",
	Int:           "int",
	Int8:          "int8",
	Int16:         "int16",
	Int32:         "int32",
	Int64:         "int64",
	Uint:          "uint",
	Uint8:         "uint8",
	Uint16:        "uint16",
	Uint32:        "uint32",
	Uint64:        "uint64",
	Uintptr:       "uintptr",
	Float32:       "float32",
	Float64:       "float64",
	Complex64:     "complex64",
	Complex128:    "complex128",
	Array:         "array",
	Chan:          "chan",
	Func:          "func",
	Interface:     "interface",
	Map:           "map",
	Ptr:           "ptr",
	Slice:         "slice",
	String:        "string",
	Struct:        "struct",
	UnsafePointer: "unsafe.Pointer",
}

关于枚举的整数值,有点奇怪,我做了个测试,看看下面2种写法:

type MyEnum uint8

const (
	Ok MyEnum = iota
	Failed
	Running
	Finished
	Abort
)

func TestEnum1(t *testing.T) {
	fmt.Println(Ok, Failed, Running, Finished, Abort)
}

可以看到枚举值的值是从0开始按1为步长递增的

=== RUN   TestEnum1
0 1 2 3 4
--- PASS: TestEnum1 (0.00s)
PASS

如果我这样写,一旦换成了  1 << iota ,竟然变成了1、2、4、8、16,每个都是前面的2倍,这个是因为iota是golang语言的常量计数器,只能在常量的表达式中使用,有这样的规则:

  1. 每当const出现时, 都会使iota初始化为0
  2. const中每新增一行常量声明,iota的值就会加1

因为有这样的规则,所以下面的代码在const代码块中,第一次出现iota,所以Ok的值就是 1 << 0结果是1,下面的Failed因为没有赋值,所以它的值是和上面的Ok一样的,也是 1 << iota,但是因为每增加一行常量声明,iota会加1,所以Failed实际上等于 1 << 1,自然结果就是2,依次类推

  1. Running实际上是 1 << 2,结果就是二进制的0100,换算为十进制就是4
  2. Finished实际上是 1 << 3,结果就是二进制的1000,换算为十进制就是8
  3. Abort实际上是 1 << 4,结果就是二进制的0001 0000,换算为十进制就是16
type MyEnum uint8

const (
	Ok MyEnum =  1 << iota
	Failed
	Running
	Finished
	Abort
)

func TestEnum1(t *testing.T) {
	fmt.Println(Ok, Failed, Running, Finished, Abort)
}

=== RUN   TestEnum1
1 2 4 8 16
--- PASS: TestEnum1 (0.00s)
PASS

扯远了,回到正题golang的reflect包, type.go里面的结构如下

这里面挺有意思的,为了语义化表示出一个golang中的结构体字段、结构体标签、方法,在type.go里面有Method、StructField、StructTag,另外的ChanDir和Kind本质上都是int,Kind是指golang中的数据类型,这个ChanDir是golang里面Channel的方向

// ChanDir represents a channel type's direction.
type ChanDir int

const (
	RecvDir ChanDir             = 1 << iota // <-chan
	SendDir                                 // chan<-
	BothDir = RecvDir | SendDir             // chan
)

核心的几个函数:

  1. TypeOf
  2. PrtTo       这个比较特殊,它能得到指定元素的指针类型,我们知道,任何数据类型,都有它自己的指针类型,就好像C语言里面的一样,对于golang也是一样的,int就会有*int、float32就会有*float32、某个自己声明的结构体Foo,就会有*Foo
     
    //怎么理解分别输出[5]int、*[5]int?
    func Test1(t *testing.T) {
    	//这一行构造了一个数组,返回的是一个描述数组的类型变量,具体是啥呢,就是说这个数组里面的元素是整数,数组的长度是5
    	//数组里面的元素是整数,数组的长度是5,这2个重要信息就是数组的元数据,元数据是描述信息,它并不是数组里面的具体元素
    	arrayType:= reflect.ArrayOf(5, reflect.TypeOf(123))
    	fmt.Println(arrayType)	//[5]int
    	//下面这行比较关键了,PtrTo这个是啥玩意,它返回数据类型的指针
    	//我们知道,任何数据类型,都有它自己的指针类型,就好像C语言里面的一样,对于golang也是一样的
    	//int就会有*int、float32就会有*float32、某个自己声明的结构体Foo,就会有*Foo
    	//数组也是一种类型,既然上面的是[5]int, 那么自然加上*后就可以得到  *[5]int
    	var arrayTypePoint reflect.Type = reflect.PtrTo(arrayType)
    	fmt.Println(arrayTypePoint)		//*[5]int
    
    	//我们继续做一个测试,既然上面的解释中提到了一个是数组,一个是指向数组的指针,那么直接打印它们的Kind,必然可以推测输出应该是:array和ptr
    	fmt.Println(arrayType.Kind())
    	fmt.Println(arrayTypePoint.Kind())
    }
    
    
    === RUN   Test1
    [5]int
    *[5]int
    array
    ptr
    --- PASS: Test1 (0.00s)
    PASS
  3. func ChanOf(dir ChanDir, t Type) Type  如果是一个通道,那么返回的Type对象是用于描述一个golang的Chan的
  4. func MapOf(key, elem Type) Type    如果是一个Map,那么返回的Type对象就可以用于描述一个golang的Map,看注释:MapOf(k, e) represents map[int]string.​​​​
     
    // MapOf returns the map type with the given key and element types.
    // For example, if k represents int and e represents string,
    // MapOf(k, e) represents map[int]string.
    //
    // If the key type is not a valid map key type (that is, if it does
    // not implement Go's == operator), MapOf panics.
    func MapOf(key, elem Type) Type {
    }
  5. FuncOf           这些都差不多的,直接看源码即可
  6. SliceOf
  7. StructOf
  8. ArrayOf

这些函数都是返回Type,所以这个Type很重要,看看究竟有什么功能?

  1. Type.Align                      返回这个数据类型在内存中字节对齐以后,占用的字节数,这玩意和C语言中的结构体对齐是一回事的概念,只不过在golang中Struct字段对齐概念已经被抹去了,不需要像C语言那样了
  2. Type.FieldAlign
  3. Type.NumMethod()        获得这个类型的方法数量
  4. Type.Method(int)            根据索引返回指定的方法
  5. Type.Size() uintptr          返回存储这个对象所需要的字节数
  6. Type.Implements(u Type) bool   判断当前类型是否实现了某个父接口类型
  7. Type.AssignableTo(u Type) bool   判断当前类型是否可以转换为u类型
  8. Type.ChanDir() ChanDir    返回一个通道类型的方向
  9. Type.Elem() Type           如果当前类型是Array, Chan, Map, Ptr, or Slice,那么就返回里面包含的元素的类型,否则就会panic,比如你不能对reflect.Typeof(int32).Elem(),这样会panic,因为int32是一个基本数据类型,并不是一个数组
  10. Type.In(i int) Type       如果当前是一个Func类型的话,可以返回它的第i个参数的类型
  11. Type.Out(i int) Type    如果当前是一个Func类型的话,返回它的第i个返回值的类型
  12. Type.Key() Type         如果当前类型是Map,返回它的Key的类型
  13. Type.Len() int             如果当前类型是数组,返回数组的长度
  14. Type.NumField() int    如果当前类型是一个Struct,那么NumField返回包含的字段个数
  15. Type.NumIn() int         如果当前类型是一个Func函数,返回函数入参的个数
  16. Type.NumOut() int      如果当前类型是一个Func函数,返回函数返回值的个数
  17. Type.Field(i int) StructField   如果当前类型是一个Struct结构体,返回它的第i个字段
  18. Type.FieldByName(name string) (StructField, bool)   如果当前类型是一个Struct结构体,根据名称返回指定的字段

这个StructField表示结构体内的一个字段,从这些type.go中的设计看得出来,真的是在反射,这些其实都是属于元数据信息,程序员写程序的时候这些元数据信息自己是知道的,但是编译出来的二进制程序如果要想知道的话,就要通过reflect包来获得这些meta data


// A StructField describes a single field in a struct.
type StructField struct {
	// Name is the field name.
	Name string
	// PkgPath is the package path that qualifies a lower case (unexported)
	// field name. It is empty for upper case (exported) field names.
	// See https://golang.org/ref/spec#Uniqueness_of_identifiers
	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 StructTag string

func (tag StructTag) Get(key string) string {
	v, _ := tag.Lookup(key)
	return v
}

//这个函数很有意思,它可以返回一个Struct中字段上的tag,这些tag都是可以自己定义的,比如json tag
func (tag StructTag) Lookup(key string) (value string, ok bool) {
}

再看reflect/value.go源码

value.go里面不少Makexxxx的函数,它是在运行时动态生成特定的数据类型,支持生成:Slice、Chan、Map,这个reflect包有点不是特别好理解,因为如果是要创建一个Slice、Chan、Map,直接用golang的语法创建就行了,为什么要这么绕弯弯,通过reflect包来创建呢? 而且返回的还都是一个Value结构体,这不是没事找事吗?

func MakeSlice(typ Type, len, cap int) Value {
}

func MakeChan(typ Type, buffer int) Value {
}

func MakeMap(typ Type) Value {
	return MakeMapWithSize(typ, 0)
}

// MakeMapWithSize creates a new map with the specified type
// and initial space for approximately n elements.
func MakeMapWithSize(typ Type, n int) Value {
	if typ.Kind() != Map {
		panic("reflect.MakeMapWithSize of non-map type")
	}
	t := typ.(*rtype)
	m := makemap(t, n)
	return Value{t, m, flag(Map)}
}

value.go里面的结构体Value功能非常多,这个Value是一个抽象的概念,它是对某个变量的具体值的包装,要得到Value的引用也很简单,直接 reflect.valueOf即可

type Value struct {
}

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
	if i == nil {
		return Value{}
	}

	// TODO: Maybe allow contents of a Value to live on the stack.
	// For now we make the contents always escape to the heap. It
	// makes life easier in a few places (see chanrecv/mapassign
	// comment below).
	escapes(i)

	return unpackEface(i)
}

reflect.New方法,直接根据一个类型,可以创建出一个指向对应内存中对象的指针,当然了这个内存中对象全部被初始化为0,就是说假设参数tpy表示的是一个int64,那么内存中这8个字节全部被初始化为0,同时返回一个指向这段内存的指针,但是为什么返回的是Value结构体呢?从源码注释看,返回的Value的类型是PtrTo(typ),那么PtrTo(typ)又是什么呢?这里翻译一下源码注释,返回类型t的指针类型,比如如果有一个结构体Foo,t代表这个结构体的类型,那么PrtTo(t)就是 *Foo,*Foo很明显是一个指针,有了这个指针,就可以随意操作这个变量的内存内容了。

// 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}
}


// PtrTo returns the pointer type with element t.
// For example, if t represents type Foo, PtrTo(t) represents *Foo.
func PtrTo(t Type) Type {
	return t.(*rtype).ptrTo()
}

reflect.Value相比较于reflect.Type,有啥差异呢?

我的理解是对于任意一个golang数据类型,不管是内置的,还是自定义的结构体,或者是通过type xxx int这样声明的枚举类型,reflect.Type更多的是从类型信息这个角度去呈现meta信息,而reflect.Value更多的是从变量的具体内存值的角度去操作这个变量,从reflect.Value结构体包含的方法可以看得出来,它可以:

  1. 如果reflect.valueOf(xxx)返回的Value对应的类型是一个golang的Func的话,就直接可以通过Value.call调用这个函数,这个和java蛮相似的
  2. Value.Bool、Value.Bytes()、Value.Float()、Value.int()、Value.String()都可以直接得到内存中变量的具体值
  3. Value结构体还有一些SetXXX方法,可以直接修改这个变量对应的内存值
  4. func (v Value) MapKeys() []Value  可以返回一个Map类型变量中的所有Key,但是把Key以Value的形式返回

reflect.MakeFunc,这个还是蛮强大的,可以对一个函数进行包装,返回一个Value,这个最终返回的Value可以用来表示一个函数

func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value {
}

reflect.DeepEqual   func DeepEqual(x, y interface{}) bool 

未完待续......

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值