Golang reflect使用指南

Go提供了各类变量、切片、结构体等等特性,咱们能够很是方便的定义与使用它们。例如,当你想定义一个结构体的类型,只须要简单地定义:golang

type A struct {
  Name string
}

然而,当须要处理处理动态数据结构时,咱们没法在编译阶段就知道未知数据的结构,其中一个很是经典的使用情景就是对Json串的Marshal。此时,就该reflect包出场了,它提供了在运行时建立、更新某种类型以及获取该类型的各类信息的能力,有了它,咱们不只能有效处理动态数据类型,还能够大大提升代码的复用性、可读性。shell

Type

在reflect包中,是用Type来描述Go中某个对象的类型,并提供了一系列方法,来获取类型的相关信息,通常经过调用TypeOf来获取一个任意变量的类型Type编程

例如,Name()返回的就是该类型的具体名称,String()返回类型的字符串表示。api

值得注意的是Kind()方法,它返回的是该类型的类别,这彷佛有点拗口,但其实十分好理解,举个例子,type A struct{} ,它的类型是A而类别是struct。一般,在开始阶段,咱们会先判断传入的interface的类别,从而避免panic。由于有些方法只适用于某种类别,随意使用的话代码很容易panic,例如NumField()方法,只能用以获取Kind为结构体的字段数量。数据结构

还有一个方法Elem(),返回Type的子元素的Type。举个例子,若Type为指针,那么Elem()返回指针所指向的Type,若为切片,则Elem()返回切片元素的类型Type。例如*[]int,它的Elem()方法返回[]int的Type。而[]intElem()方法返回int的Type。函数

import (
	"fmt"
	"reflect"
)

type A []int

func printInfo(t reflect.Type) {
	fmt.Printf("Kind = %s\tName = %s\n", t.Kind(), t.Name())
}

func main() {
	a := &A{}
	printInfo(reflect.TypeOf(a))
	printInfo(reflect.TypeOf(a).Elem())
	printInfo(reflect.TypeOf(a).Elem().Elem())
}

输出以下:优化

Kind = ptr      Name = 
Kind = slice    Name = A
Kind = int      Name = int

Value

Value描述了在Go运行时某个对象的值,咱们能够针对它进行增删改查之类的操做,通常经过ValueOf方法来获取对象的Value设计

一般状况下,咱们能够经过Set()方法来修改变量的值。例以下述代码指针

var a = 1
	val := reflect.ValueOf(&a)
	val.Elem().Set(reflect.ValueOf(2))
	fmt.Printf("a = %d", a)

输出:code

a = 2

能够看到,变量a的值由1被修改成2了。

使用举例

动态初始化结构体

实际工做中,struct一般用来表示某种数据结构(或对象),是十分简洁易懂的。然而,缺点也很明显,即其表达能力颇有限,好比,你想指定某个字段的默认值,你不得不在构造函数中手动指定。这种方式虽然可行,可是不够优雅,可读性也不好。

type DS struct {
	FieldOne string
}

func NewDS() *DS {
	return &DS{
		FieldOne: "something",
	}
}

那么该如何优化呢?很简单,即利用字段的tag信息。例如,下述代码,我在tag中设置了默认值。

type DS struct {
	FieldOne string `default:"something"`
}

而后,我使用一个初始化函数initStruct()来读取tag并设置字段默认值。

func NewDS() *DS {
	ds := &DS{}
	initStruct(ds)
	fmt.Printf("FieldOne = %s", ds.FieldOne)
	return ds
}

func initStruct(v interface{}) error {
   e := reflect.Indirect(reflect.ValueOf(v))
   if e.Kind() != reflect.Struct {
      return errors.New("v must be struct")
   }
   et, ev := e.Type(), e
   for i := 0; i < et.NumField(); i++ {
      field, val := et.Field(i), ev.Field(i)
      defaultValue, ok := field.Tag.Lookup("default")
      if !ok {
         continue
      }
      switch field.Type.Kind() {
      case reflect.String:
         val.SetString(defaultValue)
      case reflect.Int:
         if x, err := strconv.ParseInt(defaultValue, 10, 64); err != nil {
            val.SetInt(x)
         }
      // 针对不一样Kind,将defaultValue转换为对应类型并赋值
      ...
      }
   }
   return nil
}

至此,咱们就能够既方便又优雅地给结构体设置默认值了,固然,你还能够在tag中设置其余动态属性来动态更改结构体。

动态建立Map

一般状况下,咱们是经过make来建立一个map,而有了reflect包后,咱们也能够经过reflet包来动态地建立一个map。

这里,咱们有个需求,须要将一个表明长方形的结构体转换为一个map,而且存在额外要求,例如浮点字段只保留两位小数且转换为字符串。

首先,定义一个名为Rectangle的结构体来表明一个长方形

type Rectangle struct {
	Name   string
	Unit   string
	Length float64
	Width  float64
}

而后,使用一个convert函数,将其转换为map。

func convert(rectangle *Rectangle) (res map[string]string, err error) {
	e := reflect.Indirect(reflect.ValueOf(rectangle))
	if e.Kind() != reflect.Struct {
		return nil, errors.New("v must be struct")
	}
	et, ev := e.Type(), e

	var mapStringType = reflect.TypeOf(make(map[string]string))
	mapReflect := reflect.MakeMap(mapStringType)
	for i := 0; i < et.NumField(); i++ {
		field, val := et.Field(i), ev.Field(i)
		switch field.Type.Kind() {
		case reflect.String:
			mapReflect.SetMapIndex(reflect.ValueOf(field.Name), reflect.ValueOf(val.String()))
		case reflect.Float64:
			s := strconv.FormatFloat(val.Float(), 'f', 2, 64)
			mapReflect.SetMapIndex(reflect.ValueOf(field.Name), reflect.ValueOf(s))
		// other cases
		...
		}
	}
	return mapReflect.Interface().(map[string]string), nil
}

最后,咱们能够打印出转换后的map。

func main() {
	res, _ := convert(&Rectangle{
		Name: "rec-1",
		Unit: "cm",
		Length: 12.121764,
		Width: 5.989681,
	})
	fmt.Printf("res = %+v", res)
}

输出:

res = map[Length:12.12 Name:rec-1 Unit:cm Width:5.99]

总结

至此,对于reflect的简单介绍已完毕,相信你已经有了一个大概的认知了。是否是以为这个包很强大,想跃跃欲试呢?可是,在此以前,仍是要提醒你要铭记如下注意点。

  1. reflect大多只能适用于动态数据类型的场景,且较为危险,所以能使用原生类型尽可能使用原生类型。
  2. 书写要当心,错误使用reflect很容易panic,你须要确保你的类型使用了正确的相关方法,并提早返回错误。
  3. 编程界没有银弹,所以reflect也不是万能,例如你没法动态建立结构体的方法。

本人才疏学浅,文章不免有些不足之处,很是欢迎你们评论指出。

参考

相关文章
相关标签/搜索