go反射详解

目录

前言

思考题

反射的三大法则及go中对应的实现

go反射概述

Type详解

Method

Kind

StructField

Value详解

比较通用的函数 

函数

数组、切片、字符串

通道

映射

结构体

从反射类型得到接口数据

Setter函数

Overflow一类的函数

其他函数

go反射最佳实践

从reflect.ValueOf开始

如果想修改数据

Elem()的替代函数

最初的思考

参考文章


前言

书上对go反射的讲解太浅显,网上的博客也太零散,想着自己整理一篇博客吧,为解决当前的一个问题,也为以后复习参考。

思考题

在读这篇博客之前可以先想象这样一个情景:自己设计一个类似 json.Marshal(v interface{})的函数,按顺序递归打印出入参的每一个字段的名称和值。

这也是我当前遇到的需求的一个环节。

反射的三大法则及go中对应的实现

  1. 能根据接口数据获得反射对象。对应go中reflect.TypeOf(i interface{})和reflect.ValueOf(i interface{})两个方法。
  2. 能根据反射对象获得接口数据。对应go中Value.Interface()、Value.[基本数据类型]()等方法。
  3. 如果数据可修改,则能通过反射对象修改。对应go中Value.CanSet()和Value的Set系列方法。

go反射概述

  1. go不支持解析string然后执行。golang的反射机制只能存在于已经存在的对象上面。
  2. go的反射主要由Type和Value组成。可分别由reflect.TypeOf(i interface{})和reflect.ValueOf(i interface{})获得。Type是一个接口,有若干函数,reflect.TypeOf(i interface{})返回的是实现了这个接口的具体实现。Value是一个结构体,有若干字段和函数。
  3. Type也可由Value.Type()获得,Value不可由Type获得。
  4. Type存储接口的类型信息,例如方法数量、方法名、字段数量、字段名等。当使用Type时应当只读取信息,不能试图通过Type进行修改、调用之类的操作。
  5. Value存储接口的数据信息,并且包含对应的Type,可以通过Value.Type()获得对应的Type。

Type详解

通过go中Type的定义,可以看出Type及其具体实现有哪些方法可以用。下面给几个常用方法做了注释

type Type interface {
	Name() string    //返回类型名
	Kind() Kind        //返回该类型的种类,Array、Slice、Struct等
	Elem() Type    //当type当kind是Array, Chan, Map, Ptr, 或 Slice使用,返回类型的元素类型。
	Method(int) Method    // 按下标返回Method,超出范围Panic
	MethodByName(string) (Method, bool)   // 按方法名称返回Method和表示方法是否存在的bool
	NumMethod() int    // 返回方法数量

	NumIn() int       //当kind是Func时,返回入参数量
	In(i int) Type    //当Kind是Func时,返回第i个入参类型
	NumOut() int      //当kind是Func时,返回出参数量
	Out(i int) Type   //当kind是Func时,返回第i个出参类型

	NumField() int    //当Kind是Struct时,返回字段数量,内嵌结构体算一个。
	Field(i int) StructField    //当Kind是Struct时,通过下标返回字段
	FieldByIndex(index []int) StructField    //当Kind是Struct时,返回内嵌结构体字段
	FieldByName(name string) (StructField, bool) //当Kind是Struct时,通过名称返回字段
	FieldByNameFunc(match func(string) bool) (StructField, bool) //当Kind是Struct时,按自定义字段名匹配函数返回字段
	
        Key() Type        //当Kind时map时,返回键的类型,值的类型可由Elem()返回

	Len() int         //当类型是Array时,返回长度


	PkgPath() string //返回定义类型的包的路径
	Size() uintptr    //返回存储一个该类型的值所需要的字节数
	Align() int
	FieldAlign() int
	String() string
	Implements(u Type) bool
	AssignableTo(u Type) bool
	ConvertibleTo(u Type) bool
	Comparable() bool
	Bits() int
	ChanDir() ChanDir
	IsVariadic() bool
	common() *rtype
	uncommon() *uncommonType
}

Method

Type中Method(int) Method、MethodByName(string) (Method, bool)、NumMethod() int三个方法与Method有关,Method是一个结构体,存储了方法名、方法在Type方法集中的下标等信息。

type Method struct {
	Name    string //方法名
	PkgPath string

	Type  Type
	Func  Value
	Index int // 在Type方法集中的下标
}

示例代码:

func (user User) GetId() int {
	log.Println("[User] GetId begin")
	log.Printf("[User] age:%d", user.age)
	log.Println("[User] GetId end")
	return user.age
}

func main() {
	user := User{
		Name: "张三",
		age:  18,
	}
	userType := reflect.TypeOf(user)
	log.Printf("userType.NumMethod():%d", userType.NumMethod()) //1
	method, exist := userType.MethodByName("GetId")
	if exist {
		log.Printf("method.Name:%+v", method.Name)       //GetId
		log.Printf("method.PkgPath:%+v", method.PkgPath) //
		log.Printf("method.Type:%+v", method.Type)       //func(main.User) int
		log.Printf("method.Func:%+v", method.Func)       //0x10bd9f0
		log.Printf("method.Index:%+v", method.Index)     //0
	}
	for i := 0; i < userType.NumMethod(); i++ {
		method := userType.Method(i)
		log.Printf("method.Name:%+v", method.Name)       //GetId
		log.Printf("method.PkgPath:%+v", method.PkgPath) //
		log.Printf("method.Type:%+v", method.Type)       //func(main.User) int
		log.Printf("method.Func:%+v", method.Func)       //0x10bd9f0
		log.Printf("method.Index:%+v", method.Index)     //i
	}

}

Kind

Type的Kind()方法返回该类型的种类,可根据不同的种类进行不同的操作。reflect中枚举的种类有以下几种:

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
)

StructField

先通过一个例子说明一下FieldByIndex(index []int) StructField函数

type Point struct {
	X int
	Y int
	Z int
}
type User struct {
	Name string
	Age  int
	Point
}

func main() {
	user := User{
		Name: "张三",
		Age:  18,
	}
	userType := reflect.TypeOf(user)
	index := []int{2, 1} //代表下标为2的字段是一个内嵌结构体,取出这个内嵌结构体中下标为1的字段
	field := userType.FieldByIndex(index)
	log.Printf("userType.FieldByIndex():%+v", field)
}

StructField记录结构体的字段信息,包括字段名称、字段标签、字段是否为内嵌结构体、字段在结构体中的偏移量等。

type StructField struct {
	Name string         //名称
	PkgPath string
	Type      Type      //类型
	Tag       StructTag //标签
	Offset    uintptr   //如果是内嵌结构体里的字段,则从内嵌结构体里面重新计算偏移量
	Index     []int     //如果是内嵌结构体里的字段,则从内嵌结构体里面重新计数下标
	Anonymous bool      //是否是内嵌结构体
}

Value详解

先看一下Value的定义:

type Value struct {
	typ *rtype
	ptr unsafe.Pointer
	flag
}

可以看到Value只有三个非导出字段,这表面我们对Value的一切操作都要通过调用函数。值得注意的是typ *rtype字段,这是Value对应的Type实例。我们可以通过Value.Type()获得这个实例。

Value有61个可用函数。下面分类看看这些函数

比较通用的函数 

func (v Value) CanAddr() bool //判断v是否可寻址。如果v是切片元素、可寻址数组元素、可寻址结构体字段、或ptr执行Elem()后返回的结果,那么v是可寻址的。
func (v Value) CanSet() bool //判断v是否可修改。可当v寻址且是导出字段时可修改。
func (v Value) Elem() Value    //当v的kind是Interface 或 Ptr,返回v指向的实例。
func (v Value) Addr() Value    //当v可寻址是返回v当指针Value,和Elem()互为逆操作。
func (v Value) Kind() Kind    //返回v的Kind。
func (v Value) IsNil() bool   //当v的kind是Chan, Func, Map, Ptr, UnsafePointer时,判断v是否是nil
func (v Value) IsValid() bool //判断v是否有效
func (v Value) IsZero() bool  //判断v是否是它所属类型的零值
func (v Value) Pointer() uintptr //当v的 Kind is not Chan, Func, Map, Ptr, Slice, or UnsafePointer时使用。
func (v Value) UnsafeAddr() uintptr //返回指向v的数据的指针

函数

func (v Value) NumMethod() int    //返回v的函数数量。
func (v Value) Method(i int) Value     //按下标返回函数。
func (v Value) MethodByName(name string) Value   //按名字返回函数。


func (v Value) Call(in []Value) []Value //当v的Kind是Func时,执行函数v。in是入参,返回是出参。
func (v Value) CallSlice(in []Value) []Value //当vKind是Func且是变参函数时,执行函数。in是入参,返回是出参。

数组、切片、字符串

func (v Value) Cap() int //当v的Kind是Array、Slice、Chan时返回v的容量。
func (v Value) Len() int //当v的Kind是Array、Slice、String、Chan、Map时返回v的长度。
func (v Value) Index(i int) Value    //当v的Kind是Array、Slice、String时按下标返回元素。
func (v Value) Slice(i, j int) Value    //当v的Kind是Array、Slice、String时,返回v[i:j]
func (v Value) Slice3(i, j, k int) Value    //当v的Kind是Array、Slice时,返回v[i:j:k]

通道

func (v Value) Len() int
func (v Value) Cap() int
func (v Value) Recv() (x Value, ok bool) 
func (v Value) Send(x Value)
func (v Value) TryRecv() (x Value, ok bool)
func (v Value) TrySend(x Value) bool
func (v Value) Close()    //当v的kind是Chan时,关闭v。

映射

func (v Value) Len() int
func (v Value) MapKeys() []Value    //当v的Kind是Map时,返回键数组
func (v Value) MapIndex(key Value) Value    //当v的Kind是Map时,输入键返回值。
func (v Value) MapRange() *MapIter //当v的Kind是Map时,返回MapIter实例。

 MapIter是遍历Map的迭代器,它的字段都是非导出字段,我们只需要关心MapIter的方法:

func (it *MapIter) Key() Value
func (it *MapIter) Value() Value
func (it *MapIter) Next() bool

结构体

func (v Value) Field(i int) Value    //当v的kind是Struct时,按下标返回字段
func (v Value) FieldByIndex(index []int) Value    //当v的kind是Struct时,返回内嵌字段
func (v Value) FieldByName(name string) Value    //当v的kind是Struct时,按名字返回字段
func (v Value) FieldByNameFunc(match func(string) bool) Value    //当v的kind是Struct时,按自定义字段名匹配规则返回字段。

从反射类型得到接口数据

从反射类型转化接口数据的方法,对应反射的第二法则。结构体可先转化为接口再进行断言。基本数据类型和[]byte、[]rune有一步到位的方法。

func (v Value) Interface() (i interface{})

func (v Value) Bool() bool
func (v Value) Int() int64
func (v Value) Uint() uint64
func (v Value) String()
func (v Value) Float() float64
func (v Value) Complex() complex128

func (v Value) Bytes() []byte
func (v Value) runes() []rune

Setter函数

func (v Value) Set(x Value) 
func (v Value) SetBool(x bool)
func (v Value) SetBytes(x []byte) 
func (v Value) setRunes(x []rune) 
func (v Value) SetComplex(x complex128)
func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64)
func (v Value) SetLen(n int)
func (v Value) SetCap(n int)
func (v Value) SetMapIndex(key, elem Value) 
func (v Value) SetUint(x uint64)
func (v Value) SetPointer(x unsafe.Pointer)
func (v Value) SetString(x string)

Overflow一类的函数

func (v Value) OverflowComplex(x complex128) bool
func (v Value) OverflowFloat(x float64) bool
func (v Value) OverflowInt(x int64) bool
func (v Value) OverflowUint(x uint64) bool

其他函数


// CanInterface reports whether Interface can be used without panicking.
func (v Value) CanInterface() bool
// InterfaceData returns the interface v's value as a uintptr pair.
// It panics if v's Kind is not Interface.
func (v Value) InterfaceData() [2]uintptr
// Convert returns the value v converted to type t.
// If the usual Go conversion rules do not allow conversion
// of the value v to type t, Convert panics.
func (v Value) Convert(t Type) Value

go反射最佳实践

从reflect.ValueOf开始

从上文中可以看出,Type能做的事Value基本都能做,Value不能做的事可以通过Value.Type()获得对应的Type再做,所以一般反射可以从reflect.ValueOf开始。如果只需要读取结构信息,可以从reflect.TypeOf开始。

如果想修改数据

在go语言中可以被修改的数据必须满足以下两个条件:

  1. 可以被寻址
  2. 被导出

第一个条件很好理解,毕竟要修改数据的值,就得找到数据所在的地址。那什么样的变量才叫可寻址呢?分两步走。

第一步:reflect.ValueOf(i interface{})函数要传入指针类型变量。

第二步:对reflect.ValueOf获得的对象执行Elem()函数。Elem()函数返回的Value就是可寻址的。示例代码:

func main() {
	i := 1
	v1 := reflect.ValueOf(&i)
	v2 := v1.Elem()

	//可用Value.CanAddr()判断是不是可以被寻址。
	fmt.Println(v1.CanAddr()) // false
	fmt.Println(v2.CanAddr()) // true
}

所以如果想通过反射修改数据,就向reflect.ValueOf传入一个指针。

如果只想通过反射读取一些信息,为了避免误改,就向reflect.ValueOf传入一份拷贝。当传入非指针实例时go会自动拷贝数据。

Elem()的替代函数

func (v Value) Elem() Value函数不是任何Value都能调用的,如果v的 Kind 不是 Interface 或 Ptr就会导致Panic。我们遇到的大多数需要执行Elem()的时候都是v的 Kind是Ptr的时候。为了避免每次都手动判断v的Kind是不是Ptr,可以借助reflect包里的func Indirect(v Value) Value函数。这个函数会自行判断v的是不是Kind是不是Ptr,如果是则返回v.Elem(),否则返回v本身。

最初的思考

现在再看文章刚开始的思考题,似乎已经没那么难了。下面贴出来代码,有兴趣的可以看看。

package main

import (
	"log"
	"reflect"
)

type People struct {
	Name string
	Age  int
}
type User struct {
	Id int64
	People
	friends []*People
	ext     map[string]interface{}
}

func main() {

	user := User{
		Id: 1,
		People: People{
			Name: "张三",
			Age:  18,
		},
		friends: []*People{
			{
				Name: "李四",
				Age:  18,
			},
			{
				Name: "朱五",
				Age:  18,
			},
		},
		ext: map[string]interface{}{
			"teacher": People{
				Name: "杨六",
				Age:  36,
			},
		},
	}
	Handle(user)
}
func Handle(i interface{}) {
	val := reflect.ValueOf(i)
	route(val)
}

func route(val reflect.Value) {
	if !val.IsValid() {
		return
	}
	switch val.Kind() {
	case reflect.Struct:
		handleStruct(val)
	case reflect.Slice:
		handleSlice(val)
	case reflect.Map:
		handleMap(val)
	case reflect.Ptr, reflect.Interface:
		route(val.Elem())
	default:
		log.Printf("处理能力之外的Kind,Kind:%+v", val.Kind())
	}
}
func handleStruct(obj reflect.Value) {
	objType := obj.Type()
	for i := 0; i < obj.NumField(); i++ {
		field := obj.Field(i)

		switch field.Kind() {
		case reflect.Array, reflect.Slice, reflect.Chan, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Struct:
			route(field)
		default:
			log.Printf("%s:%v", objType.Field(i).Name, field)
		}

	}
}
func handleSlice(list reflect.Value) {
	for i := 0; i < list.Len(); i++ {
		element := list.Index(i)
		route(element)
	}
}
func handleMap(m reflect.Value) {
	for iter := m.MapRange(); iter.Next(); {
		route(iter.Value())
	}
}

参考文章

go源码

图文详解go语言反射实现原理_Golang_脚本之家

Go语言通过反射修改变量的值

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值