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语言的常量计数器,只能在常量的表达式中使用,有这样的规则:
- 每当const出现时, 都会使iota初始化为0
- const中每新增一行常量声明,iota的值就会加1
因为有这样的规则,所以下面的代码在const代码块中,第一次出现iota,所以Ok的值就是 1 << 0结果是1,下面的Failed因为没有赋值,所以它的值是和上面的Ok一样的,也是 1 << iota,但是因为每增加一行常量声明,iota会加1,所以Failed实际上等于 1 << 1,自然结果就是2,依次类推
- Running实际上是 1 << 2,结果就是二进制的0100,换算为十进制就是4
- Finished实际上是 1 << 3,结果就是二进制的1000,换算为十进制就是8
- 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
)
核心的几个函数:
- TypeOf
- 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
- func ChanOf(dir ChanDir, t Type) Type 如果是一个通道,那么返回的Type对象是用于描述一个golang的Chan的
- 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 { }
- FuncOf 这些都差不多的,直接看源码即可
- SliceOf
- StructOf
- ArrayOf
这些函数都是返回Type,所以这个Type很重要,看看究竟有什么功能?
- Type.Align 返回这个数据类型在内存中字节对齐以后,占用的字节数,这玩意和C语言中的结构体对齐是一回事的概念,只不过在golang中Struct字段对齐概念已经被抹去了,不需要像C语言那样了
- Type.FieldAlign
- Type.NumMethod() 获得这个类型的方法数量
- Type.Method(int) 根据索引返回指定的方法
- Type.Size() uintptr 返回存储这个对象所需要的字节数
- Type.Implements(u Type) bool 判断当前类型是否实现了某个父接口类型
- Type.AssignableTo(u Type) bool 判断当前类型是否可以转换为u类型
- Type.ChanDir() ChanDir 返回一个通道类型的方向
- Type.Elem() Type 如果当前类型是Array, Chan, Map, Ptr, or Slice,那么就返回里面包含的元素的类型,否则就会panic,比如你不能对reflect.Typeof(int32).Elem(),这样会panic,因为int32是一个基本数据类型,并不是一个数组
- Type.In(i int) Type 如果当前是一个Func类型的话,可以返回它的第i个参数的类型
- Type.Out(i int) Type 如果当前是一个Func类型的话,返回它的第i个返回值的类型
- Type.Key() Type 如果当前类型是Map,返回它的Key的类型
- Type.Len() int 如果当前类型是数组,返回数组的长度
- Type.NumField() int 如果当前类型是一个Struct,那么NumField返回包含的字段个数
- Type.NumIn() int 如果当前类型是一个Func函数,返回函数入参的个数
- Type.NumOut() int 如果当前类型是一个Func函数,返回函数返回值的个数
- Type.Field(i int) StructField 如果当前类型是一个Struct结构体,返回它的第i个字段
- 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结构体包含的方法可以看得出来,它可以:
- 如果reflect.valueOf(xxx)返回的Value对应的类型是一个golang的Func的话,就直接可以通过Value.call调用这个函数,这个和java蛮相似的
- Value.Bool、Value.Bytes()、Value.Float()、Value.int()、Value.String()都可以直接得到内存中变量的具体值
- Value结构体还有一些SetXXX方法,可以直接修改这个变量对应的内存值
- 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
未完待续......