Go语言入门-反射

Go语言入门-反射

概述

定义

Reflection in computing is the ability of a program to examine its own structure, particularly through types; it’s a form of metaprogramming. It’s also a great source of confusion.

反射(Reflection )在运行时程序检查其自身结构的能力,特别是常用于类型中,能让程序在运行时检查和获取对象的类型信息和内存信息。从而实现一种类似于动态类型的功能,同时反射也是实现元编程的 形式。因为其的灵活性,也会带来一些额外的问题。

实现

go语言中反射是通过reflect包来获取程序执行过程中的反射信息。
反射建立在类型系统之上,每个变量都有具体的静态类型和对应的值,同时类型也关联了对应的接口,一个变量赋值给接口变量是接口变量也会存放变量的具体的值和变量的类型描述,因此go语言的反射与类型和接口有紧密的联系。

入口函数

go语言的中反射和其他语言反射有一定的差异,本文只探索一下go语言的反射。go语言提供了两个入口函数

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

以上两个函数的参数列表是 i interface{} 空接口,也就是可以传入所有类型的变量。

  • 示例1-简单演示reflect.TypeOfreflect.ValueOf方法
func main() {
    var a float64 = 1.2
    t := reflect.TypeOf(a)
    fmt.Printf("type = %v\n", t.Name())
    fmt.Printf("type = %v\n", t)
    v := reflect.ValueOf(a)
    fmt.Printf("type = %v\n", v)
}
/**
output:
type = float64
type = float64
type = 1.2
 */

应用

reflect.TypeOf-获取类型对象

类型和种类

在反射中类型是Type,除了Type以外还增加了一个粒度的划分Kind。Type表示实际的类型,Kind表示构建Type的基础的类别(底层类型)。当我们需要某些对象是不是指针、接口体类型是,我们就可以使用Kind来判断。

  1. 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
)

可以看出Kind本身也是一种类型,起底层的种类是unit8,而类型是Kind,Kind的范围是有限的由以上常量表示不同的类型,涵盖了go语言中的所有的内置类型。需要注意的是Invalid Kind = iota 不是一个有效类型。可以理解为边界值。

  • 示例2-简单演示Type和Kind
import (
    "fmt"
    "reflect"
)

type MyInt int
type MyFloat float64
type MyStruct struct {a,b int}
type MyFunc func(a, b int) error
type MyFunc2 func(a, b int) (int, error)
type MyMap map[int]string
type MyMap2 map[string]string
type MyInterface interface {hello()}
type MyInterface2 interface {}
// 别名
type MyStructAlias = MyStruct

func main() {
    var myInt MyInt
    var myFloat MyFloat
    var myStruct MyStruct
    var myFunc MyFunc
    var myFunc2 MyFunc2
    var myMap MyMap
    var myMap2 MyMap2
    var myStructAlias MyStructAlias
    //var myInterface MyInterface
    //var myInterface2 MyInterface2
    fmt.Printf("type=[%s] kind=[%s]\n", reflect.TypeOf(myInt).Name(), reflect.TypeOf(myInt).Kind())
    fmt.Printf("type=[%s] kind=[%s]\n", reflect.TypeOf(myFloat).Name(), reflect.TypeOf(myFloat).Kind())
    fmt.Printf("type=[%s] kind=[%s]\n", reflect.TypeOf(myStruct).Name(), reflect.TypeOf(myStruct).Kind())
    showReflectTypeAndKind(myFunc)
    showReflectTypeAndKind(myFunc2)
    showReflectTypeAndKind(myMap)
    showReflectTypeAndKind(myMap2)
    //类型别名的Type还是原来的Type
    showReflectTypeAndKind(myStructAlias)
    //showReflectTypeAndKind(myInterface)
    //showReflectTypeAndKind(myInterface2)
}
func showReflectTypeAndKind(i interface{}) {
    fmt.Printf("type=[%s] kind=[%s]\n", reflect.TypeOf(i).Name(), reflect.TypeOf(i).Kind())
}
/**
output:
type=[MyInt] kind=[int]
type=[MyFloat] kind=[float64]
type=[MyStruct] kind=[struct]
type=[MyFunc] kind=[func]
type=[MyFunc2] kind=[func]
type=[MyMap] kind=[map]
type=[MyMap2] kind=[map]
type=[MyStruct] kind=[struct]
 */
构建基础的复核类型

reflect包中提供了一下类型方法

type Type
    func ArrayOf(count int, elem Type) Type
    func ChanOf(dir ChanDir, t Type) Type
    func FuncOf(in, out []Type, variadic bool) Type
    func MapOf(key, elem Type) Type
    func PtrTo(t Type) Type
    func SliceOf(t Type) Type
    func StructOf(fields []StructField) Type
    func TypeOf(i interface{}) Type
  • 示例3-反射构建复合类型
//通过反射构建数组
func initAarryByX(i interface{}, n int)(result reflect.Type){
    //通过reflect.ArrayOf构建数组类型
    result = reflect.ArrayOf(n, reflect.TypeOf(i))
    return
}

func main() {
    i := 10
    //a是reflect.type类型是构建出的类型
    a := initAarryByX(i, 5)
    //[5]int
    fmt.Println(a)
}
/**
output:
[5]int
 */

反射构建出复合、引用类型

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 

// 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
  • 示例4 - 构建复合类型(数组)并创建对象
//通过反射构建数组
func initAarryByX(i interface{}, n int)(result reflect.Type){
    //通过reflect.ArrayOf构建数组类型
    result = reflect.ArrayOf(n, reflect.TypeOf(i))
    return
}
func main() {
    i := 10
    //a是reflect.type类型是构建出的类型
    a := initAarryByX(i, 5)
    //[5]int
    fmt.Println(a)
    //构建对象
    v1 := reflect.New(a)
    //
    fmt.Println(v1)
}
/**
output:
[5]int
&[0 0 0 0 0]
 */

反射构建引用类型的实例

reflect包中提供以下函数对反射构建的对象初始化

// MakeSlice creates a new zero-initialized slice value
// for the specified slice type, length, and capacity.
func MakeSlice(typ Type, len, cap int) Value 
// MakeChan creates a new channel with the specified type and buffer size.
func MakeChan(typ Type, buffer int) Value
// MakeMap creates a new map with the specified type.
func MakeMap(typ Type) Value
// MakeFunc returns a new function of the given Type
// that wraps the function fn. When called, that new function
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
  • 示例4 简单演示 reflect.MapOf reflect.MakeMap SetMapIndex的使用
func main() {
    //ty := reflect.MapOf(reflect.TypeOf("sd").(reflect.Type), reflect.TypeOf("sd").(reflect.Type))
    //生成一个map类型,key的类似通过"aa"获取到类型为string value的类型通过"bb"获取的类型为string
    ty := initReflectMap("aa", "bb")
    //map[string]string
    fmt.Println(ty)
    //创建map
    map1 := reflect.MakeMap(ty)
    //设置map的key和value
    map1.SetMapIndex(reflect.ValueOf("hello"), reflect.ValueOf("world"))
    //转换构建出的类型reflect.Value到map[string]string
    map2 := map1.Interface().(map[string]string)
    //打印
    fmt.Println(map1, reflect.TypeOf(map1))
    //打印
    fmt.Println(map2, reflect.TypeOf(map2))
}
/**
output:
map[string]string
map[hello:world] reflect.Value
map[hello:world] map[string]string
 */

reflect.ValueOf

reflect.ValueOf-获取原始值信息

reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值信息,主要用于对象数据值的读写。

  • 示例5-通过反射获取对象值的信息

func main() {
    a := 10
    //a的类型为int
    fmt.Printf("%T %#v\n", a, a)
    //获取反射对象v v的类型为reflect.Value
    v := reflect.ValueOf(a)
    fmt.Printf("%T %#v\n", v, v)
    //转换变量a的值以interfac{}返回
    vi := v.Interface()
    fmt.Printf("%T %#v\n", vi, v)
    //转换变量a的值以int类型返回
    vii := v.Int()
    fmt.Printf("%T %#v\n", vii, vii)
    //获取reflect value类型的变量种类
    vk := v.Kind()
    fmt.Printf("%T %#v\n", vk, vk)
    //获取变量v的类型
    fmt.Println(v.Type())
    //类型不符合无法转换
    //fmt.Println(v.Bytes())
    //IsNil只用于引用类型
    //fmt.Println(v.IsNil())
    //判断v是否有效
    fmt.Println(v.IsValid())
    //判断v是否为0
    fmt.Println(v.IsZero())
}
/**
int 10
reflect.Value 10
int 10
int64 10
reflect.Kind 0x2
int
true
false

 */

由于接口变量会复制对象,而ValueOf的签名是func ValueOf(i interface{}) Value。使用变量本身无法修改对象值因此需要修改指针。

reflect.ValueOf-通过反射修改对象值
  • 示例6 通过反射来修改变量值
func main() {
    //10 - 字面值常量地址不可达  无法获取变量的地址
    a := 10
    va := reflect.ValueOf(a)
    fmt.Println(va.CanAddr(), va.CanSet())
    //无法赋值
    //va.SetInt(11)
    vp := reflect.ValueOf(&a)
    fmt.Println(vp.CanAddr(), vp.CanSet())
    //指针本身是地址不可达, 也无法设置值的。
    //vp.SetInt(11)
    //但是可以通过存放a的地址副本通过a的地址来关联到a的内存地址,从而修改内存内容。
    //修改a的值为11
    vp.Elem().SetInt(11)
    fmt.Println(a)
    fmt.Println(vp.Elem().CanAddr(), vp.Elem().CanSet())
}
/**
output:
false false
false false
11
true true
 */
  1. 由于接口interface{}会赋值对象,并且是地址不可达,因此需要修改对象值需要传入对象指针。
  2. 传入的对象指针通过Elem来定位到对象实际的内存空间,从而修改对象值。
  3. 使用CanAddr、CanSet来判断是否可以寻址、可以设置值。
判断对象是否为空,或者有效

1. isNil
isNil的源码可以看出只要是针对引用类型的变量判空处理。如果传入非引用的变量就会panic。

// IsNil reports whether its argument v is nil. The argument must be
// a chan, func, interface, map, pointer, or slice value; if it is
// not, IsNil panics. Note that IsNil is not always equivalent to a
// regular comparison with nil in Go. For example, if v was created
// by calling ValueOf with an uninitialized interface variable i,
// i==nil will be true but v.IsNil will panic as v will be the zero
// Value.
func (v Value) IsNil() bool {
	k := v.kind()
	switch k {
	case Chan, Func, Map, Ptr, UnsafePointer:
		if v.flag&flagMethod != 0 {
			return false
		}
		ptr := v.ptr
		if v.flag&flagIndir != 0 {
			ptr = *(*unsafe.Pointer)(ptr)
		}
		return ptr == nil
	case Interface, Slice:
		// Both interface and slice are nil if first word is 0.
		// Both are always bigger than a word; assume flagIndir.
		return *(*unsafe.Pointer)(v.ptr) == nil
	}
	panic(&ValueError{"reflect.Value.IsNil", v.kind()})
}
  • 示例7 使用IsNil判断引用类型是否为空
func main() {
    var p *string
    var s []int
    //var i int
    //IsNil主要用来判断引用类型的变量的值是否为nil
    fmt.Println(reflect.ValueOf(p).IsNil())
    fmt.Println(p == nil)
    fmt.Println(reflect.ValueOf(s).IsNil())
    fmt.Println(s == nil)
    //panic: reflect: call of reflect.Value.IsNil on int Value
    //fmt.Println(reflect.ValueOf(i).IsNil())
}
/**
output:
true
true
true
true
 */

2. isValid()

//源码没看明白丫
type flag uintptr
// IsValid reports whether v represents a value.
// It returns false if v is the zero Value.
// If IsValid returns false, all other methods except String panic.
// Most functions and methods never return an invalid value.
// If one does, its documentation states the conditions explicitly.
func (v Value) IsValid() bool {
	return v.flag != 0
}
  • TODO

结构体反射

通过reflect.TypeOf()获得反射对象信息后,如果是struct类型,则可以通过反射对象获取方法和成员域。

  1. 通过以下方法成员域
	// NumField returns a struct type's field count.
	// It panics if the type's Kind is not Struct.
	NumField() int
	// Field returns a struct type's i'th field.
	// It panics if the type's Kind is not Struct.
	// It panics if i is not in the range [0, NumField()).
	Field(i int) StructField
	// FieldByIndex returns the nested field corresponding
	// to the index sequence. It is equivalent to calling Field
	// successively for each index i.
	// It panics if the type's Kind is not Struct.
	FieldByIndex(index []int) StructField

	// FieldByName returns the struct field with the given name
	// and a boolean indicating if the field was found.
	FieldByName(name string) (StructField, bool)
  1. 通过以下方法获取方法集的方法

	// NumMethod returns the number of exported methods in the type's method set.
	NumMethod() int
	// Method returns the i'th method in the type's method set.
	// It panics if i is not in the range [0, NumMethod()).
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	//
	// Only exported methods are accessible and they are sorted in
	// lexicographic order.
	Method(int) Method
	// MethodByName returns the method with that name in the type's
	// method set and a boolean indicating if the method was found.
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	MethodByName(string) (Method, bool)
  1. 关键的两个描述成员域和方法集的接口体
  • StructField-描述成员域
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
}
  • Method-描述方法的结构体
// Method represents a single method.
type Method struct {
	// Name is the method name.
	// PkgPath is the package path that qualifies a lower case (unexported)
	// method name. It is empty for upper case (exported) method names.
	// The combination of PkgPath and Name uniquely identifies a method
	// in a method set.
	// See https://golang.org/ref/spec#Uniqueness_of_identifiers
	Name    string
	PkgPath string

	Type  Type  // method type
	Func  Value // func with receiver as first argument
	Index int   // index for Type.Method
}
  • 示例8 结构体获取成员域和方法集
import (
    "fmt"
    "reflect"
)

type Animal struct {
    name string
    Age int
}


func (a *Animal) Name() string {
    return a.name
}

func (a *Animal) SetName(name string) {
    a.name = name
}

func (a Animal) GetAge() int {
    return a.Age
}

func main() {
    animal := Animal{
        name: "animal",
        Age:  0,
    }
    ty := reflect.TypeOf(animal)
    fmt.Println("==========================Field========================")
    //获取结构体成员域个数
    fmt.Println(ty.NumField())
    //获取成员域的个数
    for i := 0; i < ty.NumField(); i++ {
        //通过索引去遍历成员域
        f := ty.Field(i)
        fmt.Println(f.Name, f.Index, f.Type, f.Tag, f.Offset)
    }
    f, _ := ty.FieldByName("name")
    //通过FieldByName去尝试获取字段顺序
    fmt.Println("FieldByName->name", f.Name, f.Index, f.Type, f.Tag, f.Offset)
    f, _ = ty.FieldByName("Name")
    //找不到名字为Name的域
    fmt.Println("FieldByName->name", f.Name, f.Index, f.Type, f.Tag, f.Offset)
    
    fmt.Println("==========================Method========================")
    //获取方法集方法个数
    fmt.Println(ty.NumMethod())
    for i := 0; i < ty.NumMethod(); i++ {
        //通过索引去获取方法集中的方法
        m := ty.Method(i)
        fmt.Println(m.Type, m.Index, m.Name, m.Func, m.PkgPath)
    }
    //通过方法名尝试获取方法值
    m, _ := ty.MethodByName("GetAge")
    fmt.Println(m.Type, m.Index, m.Name, m.Func, m.PkgPath)
    //通过方法名尝试获取方法值
    m, _ = ty.MethodByName("Name")
    fmt.Println(m.Type, m.Index, m.Name, m.Func, m.PkgPath)
    
}
/**
output:
==========================Field========================
2
name [0] string  0
Age [1] int  16
FieldByName->name name [0] string  0
FieldByName->name  [] <nil>  0
==========================Method========================
1
func(main.Animal) int 0 GetAge 0x4cc0f0
func(main.Animal) int 0 GetAge 0x4cc0f0
<nil> 0  <invalid reflect.Value>

*/
  1. 反射到方法然后进行实例方法的调用
  • 示例9 通过结构体变量进行方法调用
type Animal struct {
    name string
    Age int
}

func (a *Animal) Name() string {
    fmt.Println("getName:", a.name)
    return a.name
}

func (a *Animal) SetName(name string) {
    a.name = name
    fmt.Println("setName")
}

func main() {
    //var in []reflect.Value
    animal := Animal{
        name: "",
        Age:  0,
    }
    //获取方法名
    ty := reflect.TypeOf(&animal)
    for i := 0; i < ty.NumMethod(); i++ {
        meth := ty.Method(i)
        fmt.Printf("TYPE index [%d] = [%s]\n", i, meth.Name)
    }
    va := reflect.ValueOf(&animal)
    for i := 0; i < va.NumMethod(); i++ {
        //meth := va.Method(i)
        fmt.Printf("TYPE index [%d] = [%s]\n", i, ty.Method(i).Name)
    }
    var in []reflect.Value
    //构造Name入参
    va.Method(0).Call(in)
    //构造setName--入参
    var in1 = []reflect.Value{reflect.ValueOf("rewr")}
    //通过实例值的方法进行方法调用,并修改对应的值
    va.Method(1).Call(in1)
    va.Method(0).Call(in)
}
/**
output:
TYPE index [0] = [Name]
TYPE index [1] = [SetName]
TYPE index [0] = [Name]
TYPE index [1] = [SetName]
getName:
setName
getName: rewr
 */

可以对结构体信息Valueof来获取方法集合,然后通过对应的索引值来获取方法对象,然后对方法对象进行.Call进行调用方法。
方法的参数是[]reflect.Value–切片。空参数送空切片。一个参数就是获取切片下标为0的元素。依次类推。。

需要注意的是调用方法一定是通过ValueOf而不是TypeOf
5. 反射操作tag

  • 示例10 通过结构体变量进行方法调用
import (
    "fmt"
    "reflect"
)

type Animal struct {
    name string `json:"Name" xml:"_name"`
    age int `json:"Age" xml:"_age"`
}

func (a *Animal) Age() int {
    return a.age
}

func (a *Animal) SetAge(age int) {
    a.age = age
}

func (a *Animal) Name() string {
    return a.name
}

func (a *Animal) SetName(name string) {
    a.name = name
}

func NewAnimal(name string, age int) *Animal {
    return &Animal{name: name, age: age}
}
func getTagFromStruct(s interface{})  {
    ty := reflect.TypeOf(s)
    for i := 0; i < ty.NumField(); i++ {
        field := ty.Field(i)
        //通过Tag标签的Get方法来获取tag-标签值 实际上就是一个字符串的处理
        fmt.Printf("name: %s|tag:[%s] subtag:xml->[%s] json->[%s] othr->[%s]\n",
            field.Name, field.Tag, field.Tag.Get("xml"), field.Tag.Get("json"), field.Tag.Get("othr"))
        
    }
}
func main() {
    a := NewAnimal("lisi", 10)
    getTagFromStruct(*a)
}
/**
output:
name: name|tag:[json:"Name" xml:"_name"] subtag:xml->[_name] json->[Name] othr->[]
name: age|tag:[json:"Age" xml:"_age"] subtag:xml->[_age] json->[Age] othr->[]
 */

实际上获取标签是比较容易的,通过索引获取的StructField对象,然后通过对象StructField的成员域获取Tag StructTag // field tag string就能拿到描述结构体的元数据,在通过Get方法对字符串json:"Name" xml:"_name" 遍历解析即可。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值