谈谈go的relfect

go的reflect实现了一个运行时反射,它允许程序操纵任意类型的对象

reflect.TypeOf函数能把对象的类型信息,它返回一个relect.Type

reflect.Type.Field模块可以获得struct或者interface中的字段名,字段类型,字段的tag等信息

reflect.ValueOf可以获得一个对象的值信息,比如它是指针还是实体,值的类型和interface类型下的值,它返回一个reflect.Value

我这里简单谈谈reflect的用法和哪些能做到哪些不能做到

Ps:以下所有代码都包含在这个测试项目

简介

reflect库有3个重要的类型,Type,Value和StructField,分别对应对象类型,对象值,字段对象

Type和Value可以通过TypeOf(obj)和ValueOf(obj)的方法来获取obj的类型属性和值属性

当Type的类型为Struct时可以通过Field,FieldByIndex,FieldByName模块等到里面的StructField

而Value下的Field,FieldByIndex,FieldByName只是获取字段对应的Value

普通字段的取值

int类型,其他比如float,uint和complex都是这种,就不举例了

t是*testing.T,因为是在Test函数里面跑,所以用t.Logf来打印日志

var a int = 20
aVal := reflect.ValueOf(a)
aType := reflect.TypeOf(a)
t.Logf("variable a Value is %v", aVal.Interface())
t.Logf("variable a Type is %s", aType.String())

String类型

var a string = "abcd1234"
aVal := reflect.ValueOf(a)
aType := reflect.TypeOf(a)
t.Logf("variable a Value is %v", aVal.Interface())
t.Logf("variable a Type is %s", aType.String())
t.Logf("variable a length is %d, last character is %c", aVal.Len(), aVal.Index(aVal.Len()-1))

list类型

var a []int = []int{1, 2, 3, 4, 5}
// 当使用append遇到list的空间不足时会重分配n*2的空间,这里只是为了让cap和length不同才这么做
a = append(a, 6)
aVal := reflect.ValueOf(a)
aType := reflect.TypeOf(a)
t.Logf("variable a Value is %v", aVal.Interface())
t.Logf("variable a Type is %s", aType.String())
t.Logf("variable a length is %d,cap is %d, last int is %d", aVal.Len(), aVal.Cap(), aVal.Index(aVal.Len()-1))

map类型

var a map[int]string = map[int]string{1: "a", 2: "b", 3: "c"}
aVal := reflect.ValueOf(a)
aType := reflect.TypeOf(a)
OneVal := reflect.ValueOf(1)
t.Logf("variable a Value is %v", aVal.Interface())
t.Logf("variable a Type is %s", aType.String())
t.Logf("variable a length is %d,one index value is %s", aVal.Len(), aVal.MapIndex(OneVal))

修改对象的值

var a int32 = 10
// 要改变reflect的中的值必须是一个指针的Elem()下的reflect.Value
aVal := reflect.ValueOf(a)
aPointVal := reflect.ValueOf(&a)
t.Logf("variable a set status %v", aVal.CanSet())
t.Logf("variable a in point set status %v", aPointVal.Elem().CanSet())
// 不用担心因为SetInt传入的是一个int64而设置负数会导致不正确,在这个函数中会根据int具体类型而做转换
aPointVal.Elem().SetInt(-20)
t.Logf("variable a changed value %d", aPointVal.Elem().Interface())
// Slice中元素都可以修改
var b []int = []int{1, 2, 3, 4}
bVal := reflect.ValueOf(b)
t.Logf("slice b first element set status %v", bVal.Index(0).CanSet())
// Map中元素都不能直接修改
var c map[int]int = map[int]int{1: 2, 2: 4, 3: 6}
cVal := reflect.ValueOf(c)
oneValue := reflect.ValueOf(1)
fourValue := reflect.ValueOf(4)

t.Logf("map c the 1 key l element set status %v", cVal.MapIndex(oneValue).CanSet())
// 不过可以使用SetMapIndex修改Val的值
cVal.SetMapIndex(oneValue, oneValue)
t.Logf("map c the value with 1 key is %d", cVal.MapIndex(oneValue))
// 也可以对不存在的key设值
cVal.SetMapIndex(fourValue, oneValue)
t.Logf("now map c is %v", cVal)

结构体字段的取值

创建几个相关联的结构体和模块

type Application struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	packageData []byte `json:"-"`
}
type MoneyType int

const (
	MoneyTypeUS = MoneyType(iota)
	MoneyTypeCN
)

type Money struct {
	MoneyType MoneyType `json:"money_type" xml:"MoneyType"`
	Number    float64   `json:"number"`
}

type MacApplication struct {
	Application `json:"application"`
	AppleStore  string `json:"apple_store"`
	Favorite    int    `json:"favorite"`
	Money       Money  `json:"money"`
}

func (app Application) GetData() []byte {
	return app.packageData
}

并创建对应的对象

app := MacApplication{
  Application: Application{
    Name:        "sandbox tower defence",
    Description: "a rpg td game",
    packageData: []byte{},
  },
  AppleStore: "https://itunes.apple.com/us/app/example",
  Favorite:   0,
  Money: Money{
    MoneyType: MoneyTypeUS,
    Number:    0,
  },
}

通过reflect.Type下的Field(i)模块函数 来获取结构体的第i个字段的StructField信息

获得StructField可以获取字段的名字,标签和偏移量和索引信息

structVal := reflect.TypeOf(app)
for i := 0; i < structVal.NumField(); i++ {
			field := structVal.Field(i)
      // 如果是匿名字段,字段名等于类型名
      t.Logf("%s, this field is a %s and kind is %v", field.Name, field.Type, field.Type.Kind())

}

通过StructField.Anonymous判断是否匿名字段,StructField.Type.Kind() == reflect.Struct判断是否结构体,用这种方式来递归读取struct中的结构体信息

下面这个函数带有一个回调,递归读取structType的字段, 并把它下面的每一个field都交给f去处理

var RecursionGetField func(string, reflect.Type, func(field *reflect.StructField))
RecursionGetField = func(prefix string, structType reflect.Type, f func(field *reflect.StructField)) {
  for i := 0; i < structType.NumField(); i++ {
    field := structType.Field(i)
    field.Name = prefix + "." + field.Name
    f(&field)
    if field.Type.Kind() == reflect.Struct {
      // 匿名结构体字段中的字段当成当前结构体的
      if field.Anonymous == true {
        RecursionGetField(prefix, field.Type, f)
      } else {
        RecursionGetField(field.Name, field.Type, f)
      }
    }
  }
}

通过StructField.Tag获取标签相关信息

structVal := reflect.TypeOf(app)
for i := 0; i < structVal.NumField(); i++ {
     field := structVal.Field(i)
     t.Logf("this field %s,tag is '%s' json field '%s'", field.Name, field.Tag, field.Tag.Get("json"))
}

应用实现

通过获取Field Name和Type Name来组装创建表的sql语句

// 这里只是做简单取类型和字段名,不涉及主键和附加属性的处理
// tableFields[table name][field name][field kind name]
tableFields := map[string]map[string]string{}
tables := []string{}
sqlTypeMap := map[reflect.Kind]string{
  reflect.Int:     "integer",
  reflect.String:  "varchar(255)",
  reflect.Float64: "real",
}
structType := reflect.TypeOf(MacApplication{})
var GetTableField func(string, reflect.Type)

GetTableField = func(tName string, structType reflect.Type) {

  if tableFields[tName] == nil {
    tableFields[tName] = map[string]string{}
  }
  for i := 0; i < structType.NumField(); i++ {
    field := structType.Field(i)
    if field.Type.Kind() == reflect.Struct {
      if field.Anonymous == true {
        GetTableField(tName, field.Type)
      } else {
        GetTableField(field.Type.Name(), field.Type)
      }
    } else {
      tableFields[tName][field.Name] = sqlTypeMap[field.Type.Kind()]
    }
  }
}
GetTableField(structType.Name(), structType)
// 通过tableFields表组装sql到tables中
for tName, fields := range tableFields {
  fNameTypeList := []string{}
  for fName, fType := range fields {
    fNameTypeList = append(fNameTypeList, fmt.Sprintf(`"%s" %s`, fName, fType))
  }
  tables = append(tables, fmt.Sprintf(`CREATE TABLE "%s"(%s)`, tName, strings.Join(fNameTypeList, ",")))
}
for _, tab := range tables {
  t.Log(tab)
}

根据字段值的指针猜字段名,需要提供对象的指针和对象字段的指针,然后通过偏移量计算出StructField

structType := reflect.TypeOf(MacApplication{})
offsetmap := map[uintptr]reflect.StructField{}

for i := 0; i < structType.NumField(); i++ {
  field := structType.Field(i)
  offsetmap[field.Offset] = field
}

{
  realFieldName := "Application"
  structVal := reflect.ValueOf(&app)
  ApplicationVal := reflect.ValueOf(&app.Application)
  offset := ApplicationVal.Elem().UnsafeAddr() - structVal.Elem().UnsafeAddr()
  assert.Equal(t, realFieldName, offsetmap[offset].Name)
  t.Logf("I guess this field is %s, real field is %s", offsetmap[offset].Name, realFieldName)
}
{
  realFieldName := "AppleStore"
  structVal := reflect.ValueOf(&app)
  AppleStoreVal := reflect.ValueOf(&app.AppleStore)
  offset := AppleStoreVal.Elem().UnsafeAddr() - structVal.Elem().UnsafeAddr()
  assert.Equal(t, realFieldName, offsetmap[offset].Name)
  t.Logf("I guess this field is %s, real field is %s", offsetmap[offset].Name, realFieldName)
}

类型比对

a := MoneyTypeCN
b := 2
aType := reflect.TypeOf(a)
bType := reflect.TypeOf(b)
// 虽然他们的种类一样但类型不一样(kind翻译为种类是为了不和Type混淆)
t.Logf("a kind is %v, b kind is %v", aType.Kind(), bType.Kind())
t.Logf("a Type is %v, b Type is %v", aType.String(), bType.String())
// 也可以通过转为interface直接比较类型
_, aIsInt := reflect.New(aType).Interface().(int)
t.Logf("a is int?%v", aIsInt)
_, bIsMoneyType := reflect.New(bType).Interface().(MoneyType)
t.Logf("b is MoneyType?%v", bIsMoneyType)