文章

反射

反射

反射介绍

  反射就是在运行期间(不是编译期间)探知对象的类型信息和内存结构、更新变量、调用它们的方法。
  反射的使用场景:

  • 函数的参数类型是interface{},需要在运行时对原始类型进行判断,针对不同的类型采取不同的处理方式。比如json.Marshal(v interface{})。
  • 在运行时根据某些条件动态决定调用哪个函数,比如根据配置文件执行相应的算子函数。

  Go标准库里的json序列化就使用了反射。

type User struct {
    Name string
    Age int
    Sex byte `json:"gender"`
}
user := User{
    Name: "钱钟书",
    Age: 57,
    Sex: 1,
}
json.Marshal(user)  //返回 {"Name":"钱钟书","Age":57,"gender":1}

  反射的弊端:

  • 代码难以阅读,难以维护。
  • 编译期间不能发现类型错误,覆盖测试难度很大,有些bug需要到线上运行很长时间才能发现,可能会造成严重用后果。
  • 反射性能很差,通常比正常代码慢一到两个数量级。在对性能要求很高,或大量反复调用的代码块里建议不要使用反射。

反射的基础数据类型

  reflect.Type用于获取类型相关的信息。

type Type interface {
	Method(int) Method  //第i个方法
	MethodByName(string) (Method, bool) //根据名称获取方法
	NumMethod() int  //方法的个数
	Name() string   //获取结构体名称
	PkgPath() string //包路径
	Size() uintptr  //占用内存的大小
	String() string  //获取字符串表述
	Kind() Kind  //数据类型
	Implements(u Type) bool  //判断是否实现了某接口
	AssignableTo(u Type) bool  //能否赋给另外一种类型
	ConvertibleTo(u Type) bool  //能否转换为另外一种类型
	Elem() Type  //解析指针
	Field(i int) StructField  //第i个成员
	FieldByIndex(index []int) StructField  //根据index路径获取嵌套成员
	FieldByName(name string) (StructField, bool)  //根据名称获取成员
	FieldByNameFunc(match func(string) bool) (StructField, bool)  //
	Len() int  //容器的长度
	NumIn() int  //输出参数的个数
	NumOut() int  //返回参数的个数
}

  通过reflect.Value获取、修改原始数据类型里的值。

type Value struct {
	// 代表的数据类型
	typ *rtype
	// 指向原始数据的指针
	ptr unsafe.Pointer
}

反射API

reflect.Type

如何得到Type

通过TypeOf()得到Type类型。

typeI := reflect.TypeOf(1)       
typeS := reflect.TypeOf("hello") 
fmt.Println(typeI)               //int
fmt.Println(typeS)               //string

typeUser := reflect.TypeOf(&common.User{}) 
fmt.Println(typeUser)                     //*common.User
fmt.Println(typeUser.Kind())                 //ptr
fmt.Println(typeUser.Elem().Kind())    //struct

指针Type转为非指针Type

typeUser := reflect.TypeOf(&common.User{}) 
typeUser2 := reflect.TypeOf(common.User{})
assert.IsEqual(typeUser.Elem(), typeUser2)

获取struct成员变量的信息

typeUser := reflect.TypeOf(common.User{}) //需要用struct的Type,不能用指针的Type
fieldNum := typeUser.NumField()           //成员变量的个数
for i := 0; i < fieldNum; i++ {
	field := typeUser.Field(i)
	fmt.Printf("%d %s offset %d anonymous %t type %s exported %t json tag %s\n", i,
		field.Name,            //变量名称
		field.Offset,          //相对于结构体首地址的内存偏移量,string类型会占据16个字节
		field.Anonymous,       //是否为匿名成员
		field.Type,            //数据类型,reflect.Type类型
		field.IsExported(),    //包外是否可见(即是否以大写字母开头)
		field.Tag.Get("json")) //获取成员变量后面``里面定义的tag
}
fmt.Println()

//可以通过FieldByName获取Field
if nameField, ok := typeUser.FieldByName("Name"); ok {
	fmt.Printf("Name is exported %t\n", nameField.IsExported())
}
//也可以根据FieldByIndex获取Field
thirdField := typeUser.FieldByIndex([]int{2}) //参数是个slice,因为有struct嵌套的情况
fmt.Printf("third field name %s\n", thirdField.Name)

获取struct成员方法的信息

typeUser := reflect.TypeOf(common.User{})
methodNum := typeUser.NumMethod() //成员方法的个数。接收者为指针的方法【不】包含在内
for i := 0; i < methodNum; i++ {
	method := typeUser.Method(i)
	fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())
}
fmt.Println()

typeUser2 := reflect.TypeOf(&common.User{})
methodNum = typeUser2.NumMethod() //成员方法的个数。接收者为指针或值的方法【都】包含在内,也就是说值实现的方法指针也实现了(反之不成立)
for i := 0; i < methodNum; i++ {
	method := typeUser2.Method(i)
	fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())
}

获取函数的信息

func Add(a, b int) int {
	return a + b
}

typeFunc := reflect.TypeOf(Add) //获取函数类型
fmt.Printf("is function type %t\n", typeFunc.Kind() == reflect.Func)
argInNum := typeFunc.NumIn()   //输入参数的个数
argOutNum := typeFunc.NumOut() //输出参数的个数
for i := 0; i < argInNum; i++ {
	argTyp := typeFunc.In(i)
	fmt.Printf("第%d个输入参数的类型%s\n", i, argTyp)
}
for i := 0; i < argOutNum; i++ {
	argTyp := typeFunc.Out(i)
	fmt.Printf("第%d个输出参数的类型%s\n", i, argTyp)
}

判断类型是否实现了某接口

//通过reflect.TypeOf((*<interface>)(nil)).Elem()获得接口类型。因为People是个接口不能创建实例,所以把nil强制转为*common.People类型
typeOfPeople := reflect.TypeOf((*common.People)(nil)).Elem()
fmt.Printf("typeOfPeople kind is interface %t\n", typeOfPeople.Kind() == reflect.Interface)
t1 := reflect.TypeOf(common.User{})
t2 := reflect.TypeOf(&common.User{})
//如果值类型实现了接口,则指针类型也实现了接口;反之不成立
fmt.Printf("t1 implements People interface %t\n", t1.Implements(typeOfPeople))

reflect.Value

如果获得Value

通过ValueOf()得到Value。

iValue := reflect.ValueOf(1)
sValue := reflect.ValueOf("hello")
userPtrValue := reflect.ValueOf(&common.User{
	Id:     7,
	Name:   "杰克逊",
	Weight: 65,
	Height: 1.68,
})
fmt.Println(iValue)       //1
fmt.Println(sValue)       //hello
fmt.Println(userPtrValue) //&{7 杰克逊  65 1.68}

Value转为Type

iType := iValue.Type()
sType := sValue.Type()
userType := userPtrValue.Type()
//在Type和相应Value上调用Kind()结果一样的
fmt.Println(iType.Kind() == reflect.Int, iValue.Kind() == reflect.Int, iType.Kind() == iValue.Kind())  
fmt.Println(sType.Kind() == reflect.String, sValue.Kind() == reflect.String, sType.Kind() == sValue.Kind()) 
fmt.Println(userType.Kind() == reflect.Ptr, userPtrValue.Kind() == reflect.Ptr, userType.Kind() == userPtrValue.Kind())

指针Value和非指针Value互相转换

userValue := userPtrValue.Elem()                    //Elem() 指针Value转为非指针Value
fmt.Println(userValue.Kind(), userPtrValue.Kind())  //struct ptr
userPtrValue3 := userValue.Addr()                   //Addr() 非指针Value转为指针Value
fmt.Println(userValue.Kind(), userPtrValue3.Kind()) //struct ptr

得到Value对应的原始数据

通过Interface()函数把Value转为interface{},再从interface{}强制类型转换,转为原始数据类型。或者在Value上直接调用Int()、String()等一步到位。

fmt.Printf("origin value iValue is %d %d\n", iValue.Interface().(int), iValue.Int())
fmt.Printf("origin value sValue is %s %s\n", sValue.Interface().(string), sValue.String())
user := userValue.Interface().(common.User)
fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user.Id, user.Name, user.Weight, user.Height)
user2 := userPtrValue.Interface().(*common.User)
fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user2.Id, user2.Name, user2.Weight, user2.Height)

空Value的判断

var i interface{} //接口没有指向具体的值
v := reflect.ValueOf(i)
fmt.Printf("v持有值 %t, type of v is Invalid %t\n", v.IsValid(), v.Kind() == reflect.Invalid)

var user *common.User = nil
v = reflect.ValueOf(user) //Value指向一个nil
if v.IsValid() {
	fmt.Printf("v持有的值是nil %t\n", v.IsNil()) //调用IsNil()前先确保IsValid(),否则会panic
}

var u common.User //只声明,里面的值都是0值
v = reflect.ValueOf(u)
if v.IsValid() {
	fmt.Printf("v持有的值是对应类型的0值 %t\n", v.IsZero()) //调用IsZero()前先确保IsValid(),否则会panic
}

通过Value修改原始数据的值

var i int = 10
var s string = "hello"
user := common.User{
	Id:     7,
	Name:   "杰克逊",
	Weight: 65.5,
	Height: 1.68,
}

valueI := reflect.ValueOf(&i) //由于go语言所有函数传的都是值,所以要想修改原来的值就需要传指针
valueS := reflect.ValueOf(&s)
valueUser := reflect.ValueOf(&user)
valueI.Elem().SetInt(8) //由于valueI对应的原始对象是指针,通过Elem()返回指针指向的对象
valueS.Elem().SetString("golang")
valueUser.Elem().FieldByName("Weight").SetFloat(68.0) //FieldByName()通过Name返回类的成员变量

强调一下,要想修改原始数据的值,给ValueOf传的必须是指针,而指针Value不能调用Set和FieldByName方法,所以得先通过Elem()转为非指针Value。
未导出成员的值不能通过反射进行修改。

addrValue := valueUser.Elem().FieldByName("addr")
if addrValue.CanSet() {
	addrValue.SetString("北京")
} else {
	fmt.Println("addr是未导出成员,不可Set") //以小写字母开头的成员相当于是私有成员
}

通过Value修改Slice

users := make([]*common.User, 1, 5) //len=1,cap=5
users[0] = &common.User{
	Id:     7,
	Name:   "杰克逊",
	Weight: 65.5,
	Height: 1.68,
}

sliceValue := reflect.ValueOf(&users) //准备通过Value修改users,所以传users的地址
if sliceValue.Elem().Len() > 0 {      //取得slice的长度
	sliceValue.Elem().Index(0).Elem().FieldByName("Name").SetString("令狐一刀")
	fmt.Printf("1st user name change to %s\n", users[0].Name)
}

甚至可以修改slice的cap,新的cap必须位于原始的len到cap之间,即只能把cap改小。

sliceValue.Elem().SetCap(3)

通过把len改大,可以实现向slice中追加元素的功能。

sliceValue.Elem().SetLen(2)
//调用reflect.Value的Set()函数修改其底层指向的原始数据
sliceValue.Elem().Index(1).Set(reflect.ValueOf(&common.User{
	Id:     8,
	Name:   "李达",
	Weight: 80,
	Height: 180,
}))
fmt.Printf("2nd user name %s\n", users[1].Name)

修改map

Value.SetMapIndex()函数:往map里添加一个key-value对。
Value.MapIndex()函数: 根据Key取出对应的map。

u1 := &common.User{
	Id:     7,
	Name:   "杰克逊",
	Weight: 65.5,
	Height: 1.68,
}
u2 := &common.User{
	Id:     8,
	Name:   "杰克逊",
	Weight: 65.5,
	Height: 1.68,
}
userMap := make(map[int]*common.User, 5)
userMap[u1.Id] = u1

mapValue := reflect.ValueOf(&userMap)                                                         //准备通过Value修改userMap,所以传userMap的地址
mapValue.Elem().SetMapIndex(reflect.ValueOf(u2.Id), reflect.ValueOf(u2))                      //SetMapIndex 往map里添加一个key-value对
mapValue.Elem().MapIndex(reflect.ValueOf(u1.Id)).Elem().FieldByName("Name").SetString("令狐一刀") //MapIndex 根据Key取出对应的map
for k, user := range userMap {
	fmt.Printf("key %d name %s\n", k, user.Name)
}

调用函数

valueFunc := reflect.ValueOf(Add) //函数也是一种数据类型
typeFunc := reflect.TypeOf(Add)
argNum := typeFunc.NumIn()            //函数输入参数的个数
args := make([]reflect.Value, argNum) //准备函数的输入参数
for i := 0; i < argNum; i++ {
	if typeFunc.In(i).Kind() == reflect.Int {
		args[i] = reflect.ValueOf(3) //给每一个参数都赋3
	}
}
sumValue := valueFunc.Call(args) //返回[]reflect.Value,因为go语言的函数返回可能是一个列表
if typeFunc.Out(0).Kind() == reflect.Int {
	sum := sumValue[0].Interface().(int) //从Value转为原始数据类型
	fmt.Printf("sum=%d\n", sum)
}

调用成员方法

common.User{
	Id:     7,
	Name:   "杰克逊",
	Weight: 65.5,
	Height: 1.68,
}
valueUser := reflect.ValueOf(&user)              //必须传指针,因为BMI()在定义的时候它是指针的方法
bmiMethod := valueUser.MethodByName("BMI")       //MethodByName()通过Name返回类的成员变量
resultValue := bmiMethod.Call([]reflect.Value{}) //无参数时传一个空的切片
result := resultValue[0].Interface().(float32)
fmt.Printf("bmi=%.2f\n", result)

//Think()在定义的时候用的不是指针,valueUser可以用指针也可以不用指针
thinkMethod := valueUser.MethodByName("Think")
thinkMethod.Call([]reflect.Value{})

valueUser2 := reflect.ValueOf(user)
thinkMethod = valueUser2.MethodByName("Think")
thinkMethod.Call([]reflect.Value{})

创建对象

创建struct

user :=t := reflect.TypeOf(common.User{})
value := reflect.New(t) //根据reflect.Type创建一个对象,得到该对象的指针,再根据指针提到reflect.Value
value.Elem().FieldByName("Id").SetInt(10)
user := value.Interface().(*common.User) //把反射类型转成go原始数据类型Call([]reflect.Value{})

创建slice

var slice []common.User
sliceType := reflect.TypeOf(slice)
sliceValue := reflect.MakeSlice(sliceType, 1, 3)
sliceValue.Index(0).Set(reflect.ValueOf(common.User{
	Id:     8,
	Name:   "李达",
	Weight: 80,
	Height: 180,
}))
users := sliceValue.Interface().([]common.User)
fmt.Printf("1st user name %s\n", users[0].Name)

创建map

var userMap map[int]*common.User
mapType := reflect.TypeOf(userMap)
// mapValue:=reflect.MakeMap(mapType)
mapValue := reflect.MakeMapWithSize(mapType, 10)

user := &common.User{
	Id:     7,
	Name:   "杰克逊",
	Weight: 65.5,
	Height: 1.68,
}
key := reflect.ValueOf(user.Id)
mapValue.SetMapIndex(key, reflect.ValueOf(user))                    //SetMapIndex 往map里添加一个key-value对
mapValue.MapIndex(key).Elem().FieldByName("Name").SetString("令狐一刀") //MapIndex 根据Key取出对应的map
userMap = mapValue.Interface().(map[int]*common.User)
fmt.Printf("user name %s %s\n", userMap[7].Name, user.Name)

reflect包里除了MakeSlice()和MakeMap(),还有MakeChan()和MakeFunc()。

自行实现json序列化

  所谓序列化即把struct实例转为string。比如定义了User和Book两个struct。

type User struct {
    Name string
    Age int
    Sex byte `json:"gender"`
}
type Book struct {
    ISBN string `json:"isbn"`
    Author User `json:"author"`
    Keywords []string `json:"kws"`
}

  Book的实例序列化后为

{
	"isbn": "4243547567",
	"author": {
		"Name": "钱钟书",
		"Age": 57,
		"gender": 1
	},
	"kws": ["爱情", "民国", "留学"]
}

  序列化实现思路:

  • 从内向外、从简单到复杂地考虑序列化问题。
    1. 如果要序列化一个int、float、string,很简单。
    2. 如果要序列化一个slice,则在第1步的基础上用[]括起来。
    3. 如果要序列化一个struct,FieldName直接打印出来,FieldValue的序列化可以参考第1、2步。
    4. 如果struct内部还嵌套了struct,则递归调用第3步。
  • 通过反射解析struct,得到json key和struct FieldName的对应关系。
  • 如果FieldValue是基本的值类型,则通过反射给FieldValue赋值很简单。
  • 如果FieldValue是slice类型,则需要通过反射先创建一个slice,再给slice里的每个元素赋值。
  • 如果FieldValue是是内嵌struct,则递归调用反序列化函数,给FieldValue赋值。
  • 如果FieldValue是是内嵌struct指针,则需要创建内嵌struct对应的实例(申请内存空间),再递归调用反序列化函数,给FieldValue赋值。

序列化和反序列化

package main

import (
	"bytes"
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

func Marshal(v interface{}) ([]byte, error) {
	value := reflect.ValueOf(v)
	Type := value.Type()
	bf := bytes.Buffer{}

	switch Type.Kind() {
	case reflect.Int, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		//return []byte(strconv.Itoa(int(value.Int()))), nil
		return []byte(fmt.Sprintf("%v", value.Interface())), nil
	case reflect.String:
		return []byte("\"" + value.String() + "\""), nil
	case reflect.Bool:
		return []byte(fmt.Sprintf("%t", value.Bool())), nil
	case reflect.Float32, reflect.Float64:
		return []byte(fmt.Sprintf("%f", value.Float())), nil
	case reflect.Struct:
		bf.WriteByte('{')
		if value.NumField() > 0 {
			for i := 0; i < value.NumField(); i++ {
				fieldValue := value.Field(i)
				fieldType := Type.Field(i)
				if fieldType.IsExported() {
					name := fieldType.Name
					if len(fieldType.Tag.Get("json")) > 0 {
						name = fieldType.Tag.Get("json")
					}
					bf.WriteByte('"')
					bf.WriteString(name)
					bf.WriteByte('"')
					bf.WriteByte(':')
					if bs, err := Marshal(fieldValue.Interface()); err == nil {
						bf.Write(bs)
					} else {
						return nil, err
					}
					bf.WriteByte(',')
				}
			}
			bf.Truncate(len(bf.Bytes()) - 1)
		}
		bf.WriteByte('}')
		return bf.Bytes(), nil
	default:
		//return "", errors.New("暂时不支持这种数据类型")
		return nil, fmt.Errorf("暂时不支持这种数据类型")
	}

}
func UnMarshal(v interface{}, data []byte) error {
	value := reflect.ValueOf(v)
	Type := value.Type()

	//判断是否是指针类型
	if Type.Kind() != reflect.Ptr {
		return errors.New("v must be pointer")
	}
	//将指针类型转换成非指针类型
	value = value.Elem()
	Type = Type.Elem()

	s := string(data)
	switch Type.Kind() {
	case reflect.Int:
		if i, err := strconv.ParseInt(s, 10, 64); err == nil {
			value.SetInt(i)
		} else {
			return err
		}
	case reflect.Uint:
		if i, err := strconv.ParseUint(s, 10, 64); err == nil {
			value.SetUint(i)
		} else {
			return err
		}
	case reflect.String:
		if s[0] == '"' && s[len(s)-1] == '"' {
			value.SetString(s[1 : len(s)-1])

		} else {
			return fmt.Errorf("json格式不对:%s", s)
		}
	case reflect.Bool:
		if b, err := strconv.ParseBool(s); err == nil {
			value.SetBool(b)
		} else {
			return err
		}

	case reflect.Float32, reflect.Float64:
		if f, err := strconv.ParseFloat(s, 64); err == nil {
			value.SetFloat(f)
		} else {
			return err
		}
	case reflect.Struct:
		if s[0] == '{' && s[len(s)-1] == '}' {
			arr := strings.Split(s[1:len(s)-1], ",")
			if len(arr) > 0 {
				fieldCount := Type.NumField()
				tag2Field := make(map[string]string, fieldCount)
				for i := 0; i < fieldCount; i++ {
					fieldType := Type.Field(i)
					name := fieldType.Name
					if len(fieldType.Tag.Get("json")) > 0 {
						name = fieldType.Tag.Get("json")
					}
					tag2Field[name] = fieldType.Name

				}
				for _, ele := range arr {
					brr := strings.SplitN(ele, ":", 2)
					tag := brr[0]
					if tag[0] == '"' && tag[len(tag)-1] == '"' {
						tag = tag[1 : len(tag)-1]
						if fieldName, exists := tag2Field[tag]; exists {
							fieldValue := value.FieldByName(fieldName)
							fieldType := fieldValue.Type()
							if fieldValue.Kind() != reflect.Ptr {
								fieldValue = fieldValue.Addr()
								if err := UnMarshal(fieldValue.Interface(), []byte(brr[1])); err != nil {
									return err
								}
							} else {
								newValue := reflect.New(fieldType.Elem())
								if err := UnMarshal(newValue.Interface(), []byte(brr[1])); err != nil {
									return err
								} else {
									value.FieldByName(fieldName).Set(newValue)
									fieldValue.Set(newValue)
								}
							}
						}
					} else {
						return fmt.Errorf("json格式不对:%s", tag)
					}
				}
			}
		} else {
			return fmt.Errorf("json格式不对:%s", s)
		}
	default:
		//return "", errors.New("暂时不支持这种数据类型")
		return errors.New("暂时不支持这种数据类型")
	}
	return nil
}

type User struct {
	Name string
	Age  string
	Sex  int `json:"gender"`
}

func main() {

	//var v interface{}
	////v = 7
	////v = 8.7
	////v = true
	//v = "9"
	//if data, err := Marshal(v); err == nil {
	//	fmt.Printf("%T %s\n", string(data), string(data))
	//	//var a
	//	//var a float64
	//	//var a bool
	//	var a string
	//	if e := UnMarshal(&a, data); e == nil {
	//		fmt.Printf("%T %s", a, a)
	//	} else {
	//		fmt.Println(a)
	//	}
	//
	//}
	user := User{
		Name: "zuo",
		Age:  "20",
		Sex:  1,
	}
	if data, err := Marshal(user); err == nil {
		fmt.Println(string(data))

		var u2 User
		if err := UnMarshal(&u2, data); err != nil {
			fmt.Println(err)
		} else {
			fmt.Println(u2.Name, u2.Age, u2.Sex)

		}

	} else {
		fmt.Println(err)
	}

}

License:  CC BY 4.0 test