go语言反射的使用

517 阅读4分钟

reflect.ValueOf(i interface{})

reflectValueOf能够将变量转化为reflect.Value类型

  • reflect.Value 表示变量的实际值,如果接口为空则返回0。

  • reflect.Value类型提供了Kind()方法,获取变量实际的种类。

  • reflect.Value类型提供了Type()方法,获取变量实际的类型,此时可以类比于TypeOf()

var i int
value := reflect.ValueOf(i)

fmt.Println(value)        // 输出:0
fmt.Println(value.Type()) // 输出:int
fmt.Println(value.Kind()) // 输出:int


type S struct {
  A string
}

var s = S{"SSS"}
value2 := reflect.ValueOf(s) // 使用ValueOf()获取到结构体的Value对象

fmt.Println(value2)        // 输出:{SSS}
fmt.Println(value2.Type()) // 输出:main.S
fmt.Println(value2.Kind()) // 输出:struct

变量i使用Kind()Type()两个方法都输出了int,而结构体sType()方法输出了SKind()方法输出了struct

由此可以总结如下,如果你想拿到结构体的类型的时候,使用Type()方法。如果只是相判断是否是结构体时,就使用Kind()

type MyInt8 int8
var num MyInt8 = 1

val := reflect.ValueOf(num)
fmt.Println(val.Type()) // main.MyInt8
fmt.Println(val.Kind()) // int8

上面的代码中,虽然变量 v 的静态类型是MyInt,不是 int,Kind 方法仍然返回 reflect.Int。换句话说, Kind 方法不会像 Type 方法一样区分 MyInt 和 int。

setter and getter

Value类型也有一些类似于Int、Float的方法,用来提取底层的数据

var x float64 = 3.4
v := reflect.ValueOf(x)

fmt.Println("value:", v.Float()) // 输出:3.4

var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())       // uint8.

fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
x = uint8(v.Uint())    // v.Uint returns a uint64.

Value 类型的Int 方法返回值为 int64类型,SetInt 方法接收的参数类型也是 int64 类型。实际使用时,可能需要转化为实际的类型

如果setter的函数不是对应的类型会导致程序panic,下面的程序就会panic

var num int8 = 1

val := reflect.ValueOf(num)
fmt.Println(val.Uint())

// panic: reflect: call of reflect.Value.Uint on int8 Value

但对于下面的用法并不会panic

type MyInt8 int8
var num MyInt8 = 1

val := reflect.ValueOf(num)
fmt.Println(val.Int())
var num float64 = 1.2345

// 通过reflect.ValueOf获取num中的reflect.Value,注意,参数必须是指针才能修改其值
pointer := reflect.ValueOf(&num)
newValue := pointer.Elem()

fmt.Println(pointer.Type())    // 输出: *float64
fmt.Println(newValue.Type())   // 输出: float64
fmt.Println(newValue.CanSet()) // 输出: true

// 重新赋值
newValue.SetFloat(77)
fmt.Println("new value of pointer:", num) // 输出: 77

////////////////////
// 如果reflect.ValueOf的参数不是指针,会如何?
pointer = reflect.ValueOf(num)
//newValue = pointer.Elem() // 如果非指针,这里直接panic,“panic: reflect: call of reflect.Value.Elem on float64 Value”

只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值,即:要修改反射类型的对象就一定要保证其值是“addressable”的,(具体原因可以参考《参考资料-1》)

struct中只有暴露到外部的字段才是“可写的”

reflect.Value.Elem() 表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的

对于不确定是否是指针的可以使用 reflect.Indirect(value)

from reflection object to interface value

relfect.Value变量,可以通过它本身的Interface()方法获得接口变量的真实内容,然后可以通过类型判断进行转换,转换为原有真实类型

y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)

在不确定类型的时刻

var num int8 = 1

val := reflect.ValueOf(&num).Elem()

var s string
switch val.Interface().(type) {
  case int, int8, int32, int64:
  s = strconv.FormatInt(val.Int(), 10)
}

fmt.Println(s)

这样也可以

var s string
switch val.Kind() {
  case reflect.Int8:
  s = strconv.FormatInt(val.Int(), 10)
}

struct

对于标量类型可以使用类似v.Uint()的函数直接获取到实际值,对于struct类型的来说,如果是已知的类型可以使用类型断言(ValueOf().Interface().(struct))来获取实际值。

field

对于未知的struct需要可以使用如下的方式遍历结构的每一个字段

type S struct {
  A string
  B int
}

var s = S{"SSS", 1}
val := reflect.ValueOf(s)

for i := 0; i < val.NumField();i++{
  fmt.Println(val.Field(i))
}

其中

val.NumField() 可以获取结构体字段数

val.Field(i int) Value 可以获取第i个字段

当然可以使用另外两个函数

type S struct {
  A string
  B int
  C struct {
    D []int
  }
}

var s = S{"SSS", 1, struct{ D []int }{[]int{1, 2, 3}}}
val := reflect.ValueOf(s)

fmt.Println(val.FieldByIndex([]int{2, 0})) // 输出: [1 2 3] (获取第2个字段里的第0个字段) 
fmt.Println(val.FieldByName("B"))          // 输出: 1

method

对于结构体类型还会涉及到结构体所属方法(函数),使用方式和获取字段类似

for i := 0; i < val.NumMethod(); i++ {
  fmt.Println(val.Method(i))
}

对函数,那么自然可以进行调用,使用call 函数,它的参数就是函数参数的切片,返回的也是返回值切片

type S struct {
	A string
	B int `json:"tag"`
}

func (t S) Mth(p string) (string, int) {
	return t.A + p, t.B
}

s := S{"SSS", 1}

val := reflect.ValueOf(s)
m2 := val.MethodByName("Mth")

params := []reflect.Value{reflect.ValueOf("AAA")}
fmt.Println(m2.Call(params)[0].String()) // 输出: SSSAAA

上面所有函数对于非struct都会panic

对于call来说,传入的slice个数需要和函数的个数匹配,否则也会panic

reflect.TypeOf(i interface{})

reflect.TypeOf可以将变量转化为reflect.Type类型,

  • reflect.Type 表示变量的实际类型,如果接口为空则返回nil。此时可以类比于ValueOf().Type()

  • relfect.Type 提供了Kind()方法,获取变量的种类。

var i int
value := reflect.TypeOf(i)

fmt.Println(value2)       // 输出: int
fmt.Println(value.Type()) // 输出: value.Type undefined (TypeOf 就没有Type函数了)
fmt.Println(value.Kind()) // 输出: int


type S struct {
  a string
}

var s S
value2 := reflect.TypeOf(s) // 使用TypeOf()获取到结构体的Type对象

fmt.Println(value2)        // 输出 main.S
fmt.Println(value2.Type()) // 输出:value2.Type undefined (TypeOf 就没有Type函数了)
fmt.Println(value2.Kind()) // 输出:struct

struct

对于结构体的TypeOf,可以使用下面的方式对属性和函数进行遍历。


type S struct {
	A string
	B int `json: "b"`
}

func (t S) Mth(){
	return
}

var s = S{"SSS", 1}
typ := reflect.TypeOf(s)
for i := 0; i < typ.NumField(); i++ {
  f := typ.Field(i)
  fmt.Println(f.Name, f.Type)
}

for i := 0; i < typ.NumMethod(); i++ {
  m := typ.Method(i)
  fmt.Println(m.Name, m.Func)
}

注意与Valuefield相关函数进行区分:

reflect.Typefield相关函数返回的是 reflect.StructField类型,它可以获取到字段名,字段类型

Field(i int) StructField

FieldByIndex(index []int) StructField

FieldByName(name string) (StructField, bool)

reflect.Valuefield相关函数返回的是reflect.Value类型

func (v Value) Field(i int) Value

func (v Value) FieldByIndex(index []int) Value 

func (v Value) FieldByName(name string) Value

reflect.StructField有一点需要注意就是可以获取tag值

type S struct {
	A string
	B int `json:"tag"`
}

s := S{"SSS", 1}

typ := reflect.TypeOf(s)
f, _ := typ.FieldByName("B")

fmt.Println(f.Tag.Get("json")) // 输出: tag

参考资料