goland 反射基本入门 | 青训营笔记

121 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作的第2篇笔记

反射介绍

反射可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind)

如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)通过反射,可以修改变量的值,还可以调用关联的方法

Reflect.TypeOf

在Go语言中,使用reflect.TypeOf()函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。

  • 基础数据类型的反射类型

    func reflectType(x interface{}) {
    	v := reflect.TypeOf(x) 	   // 通过反射获取字段类型
    	fmt.Printf("type:%v\n", v) // 输出反射类型
    }
    
    reflectType(3.14)              // 调用函数
    output:type:float64
    
  • 结构体的反射类型

    // book 结构体
    type book struct{ title string }
    
    func reflectType(x interface{}) {
    	t := reflect.TypeOf(x)
    	fmt.Printf("type:%v\n", t)
    }
    
    reflectType(book{title: "《Go 语言圣经》"}) // 调用函数
    output:main.book
    

所以在对基本数据类型使用TypeOf 获取属性类型时,返回的就是基本数据类型;在对结构体类型使用Typeof 获取属性时,返回的是包名.结构体名

type name和type kind

在反射中关于类型还划分为两种:类型(Type)种类(Kind)。因为在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。 举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。

package main

import (
	"fmt"
	"reflect"
)

type myInt int64

func reflectType(x interface{}) {
	t := reflect.TypeOf(x)
	fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
}

func main() {
	var a *float32 // 指针
	var b myInt    // 自定义类型
	var c rune     // 类型别名
	reflectType(a) // type: kind:ptr
	reflectType(b) // type:myInt kind:int64
	reflectType(c) // type:int32 kind:int32

	type person struct {
		name string
		age  int
	}
	type book struct{ title string }
	var d = person{
		name: "zack",
		age:  18,
	}
	var e = book{title: "《Go 语言圣经》"}
	reflectType(&d) // type: kind:str
	reflectType(e)  // type:book kind:struct
}

output:
type: kind:ptr		-> reflectType(a)
type:myInt kind:int64	-> reflectType(b)
type:int32 kind:int32	-> reflectType(c)
type: kind:ptr		-> reflectType(&d) !结构体指针的kind 为ptr
type:book kind:struct	-> reflectType(e)

Reflect.ValueOf

reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息reflect.Value与原始值之间可以互相转换。

通过反射获取值

  • Reflect.ValueOf() 类型的操作:
方法说明
Interface() interface {}将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool将值以 bool 类型返回
Bytes() []bytes将值以字节数组 []bytes 类型返回
String() string将值以字符串类型返回
  • 其中可以通过Interface() 方法来实现reflect.Value 类型,和基本数据类型的转换
  • Reflect.ValueOf() 类型可以通过上述基本数据方法来从反射中获取基本数据类型的原始值

通过反射设置变量的值

想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中使用专有的Elem()方法来获取指针对应的值。

func reflectSetValue1(x interface{}) {
	v := reflect.ValueOf(x)
	if v.Kind() == reflect.Int64 {
		v.SetInt(200) //修改的是副本,reflect包会引发panic
	}
}

func reflectSetValue2(x interface{}) {
	v := reflect.ValueOf(x)
	// 反射中使用 Elem()方法获取指针对应的值
	if v.Elem().Kind() == reflect.Int64 {
		v.Elem().SetInt(200)
	}
}

var a int64 = 100
//reflectSetValue1(a)   // error 如果不传入引用或者不适用Elem() 就想修改a 的值,程序就会抛出一个异常
reflectSetValue2(&a)

output: 200             // 修改成功

结构体反射

任意值通过reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的NumField()Field()方法获得结构体成员的详细信息。

  • reflect.Type中与获取结构体成员相关的的方法如下表所示。
方法说明
Field(i int) StructField根据索引,返回索引对应的结构体字段的信息。
NumField() int返回结构体成员字段数量。
FieldByName(name string) (StructField, bool)根据给定字符串返回字符串对应的结构体字段的信息。
FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。
FieldByNameFunc(match func(string) bool) (StructField,bool)根据传入的匹配函数匹配需要的字段。
NumMethod() int返回该类型的方法集中方法的数目
Method(int) Method返回该类型方法集中的第i个方法
MethodByName(string)(Method, bool)根据方法名返回该类型方法集中的方法
  • 其中Reflect.TypeOf()类型可以使用Field StructField 结构体中更详细的信息
type StructField struct {
    // Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。
    Name    string
    PkgPath string
    Type      Type      // 字段的类型
    Tag       StructTag // 字段的标签
    Offset    uintptr   // 字段在结构体中的字节偏移量
    Index     []int     // 用于Type.FieldByIndex时的索引切片
    Anonymous bool      // 是否匿名字段
}

示例

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name  string `json:"name"`
	Score int    `json:"score"`
}

func (s Student) Study(name string) string {
	msg := "好好学习,天天向上。"
	fmt.Println(name + msg)
	return msg
}

func (s Student) Sleep(name string) string {
	msg := "好好睡觉,快快长大。"
	fmt.Println(name + msg)
	return msg
}

func printMethod(x interface{}) {
	t := reflect.TypeOf(x)
	v := reflect.ValueOf(x)

	fmt.Println(t.NumMethod())
	var args []reflect.Value
	args = append(args, reflect.ValueOf("zack"))  // 方法传参

	for i := 0; i < v.NumMethod(); i++ {
		methodType := v.Method(i).Type()
		fmt.Printf("method name:%s\n", t.Method(i).Name)
		fmt.Printf("method:%s\n", methodType)
		// 通过反射调用方法传递的参数必须是 []reflect.Value 类型
		v.Method(i).Call(args)
	}
}

func main() {
	// 利用反射遍历结构体字段
	stu1 := Student{
		Name:  "zack",
		Score: 90,
	}
	rTyp := reflect.TypeOf(stu1)  // 获取reflect.TypeOf(stu1) 的类型
	rVal := reflect.ValueOf(stu1) // 获取reflect.ValueOf(stu1) 的值
	numField := rTyp.NumField()   // 获取字段数
	for i := 0; i < numField; i++ {
		field := rTyp.Field(i)
		fmt.Printf("name = %v, field = %v, tag = %v\n", field.Name, rVal.Field(i), field.Tag)
	}
	fmt.Println("-----------------")

	printMethod(stu1)
}

output:
2                              // 两个字段
method name:Sleep	       // 方法名
method:func(string) string     // 方法体
zack好好睡觉,快快长大.          // 方法执行结果
method name:Study
method:func(string) string
zack好好学习,天天向上.