golang反射
反射
如何动态的获取某个对象的类型或值信息?通过是反射.
反射是指程序在运行期间 (Runtime) 可以访问、检测和修改它本身状态或行为的一种能力。简单理解就是动态获取对象有什么(字段),能做什么(方法)、甚至可以创建或设置对象,例如添加、修改字段、添加、调用、修改方法等.
反射的使用场景
- 项目中需要动态获取对象的信息,类似于自动生成、自动调用等类似的场景
- 序列化和反序列化,获取字段的tag
- 配置文件解析的库,例如ymal,ini
- 一些框架如ORM,会使用反射。动态获取对象的类型或值信息并操作
应该使用反射吗
- Clear is better than clever. Reflection is never clear. 明确胜于聪明,反射不够明确
- 反射是比较高级的概念,应谨慎使用. 使用反射很难编写清晰且可维护的代码, 尽可能避免使用,仅在必要情况时使用
总结起来就是如果有一个场景需要动态获取对象的信息,比如有什么字段或方法,做一些自动的操作,必要时可以使用反射,而一般的业务场景不太会使用到反射,也不推荐大量使用,影响性能并不利于维护
reflect包
在go中提供了reflect包用于反射操作,主要会用到reflect.TypeOf
和reflect.ValueOf
两个函数
- reflect.TypeOf 接收一个任意类型的对象(空接口), 反射得到该对象的类型信息,返回一个reflect.Type接口
func TypeOf(i interface{}) Type
- reflect.ValueOf 接收到一个任何类型的对象(空接口),反射得到该对象的值信息,返回一个reflect.Value结构体
func ValueOf(i interface{}) Value
反射的常用操作
- 获取对象的
Type
(类型),Kind
(类别)、值信息 - 获取、修改对象的值
- 如果反射的对象是结构体,可以获取、添加、修改字段信息,调用方法、通过字符串方式调用方法
- 甚至可以创建对象
- 可以将反射类型转成interface,通过断言转换成原始列席
Type (类型) 与Kind (类别) 的概念
Type(类型)
是指用type
关键字定义的类型名,而Kind(类别)
是指具体类型 (如int、strcut、map)等, Type与Kind
有可能一样也可能不一样。Kind
代表类型范围更大一点
- 当 x = 5, 那么x的
Type
为int
,Kind
也为int
- 当
type integer int32
, 那么该类型的变量的Type
为interger
,Kind
为int32
- 当 type Stu struct, 那么该结构体的变量的
Type
为Stu,Kind
为struct
package main
import (
"fmt"
"reflect"
)
func main() {
// 1. 变量x的Type和Kind都为int
x := 5
xType := reflect.TypeOf(x).Name()
xKind := reflect.TypeOf(x).Kind()
fmt.Printf("变量x的Type为: %s, Kind为: %s\n", xType, xKind) // 变量x的Type为: int, Kind为: int
// 2. 变量y的Type为Integer,Kind为int
type Integer int
var y Integer = 300
yType := reflect.TypeOf(y).Name()
yKind := reflect.TypeOf(y).Kind()
fmt.Printf("变量y的Type为: %s, Kind为: %s\n", yType, yKind) // 变量y的Type为: Integer, Kind为: int
// 3. 结构体变量stu的Type为Integer,Kind为int
type Stu struct{}
stu := Stu{}
stuType := reflect.TypeOf(stu).Name()
stuKind := reflect.TypeOf(stu).Kind()
fmt.Printf("结构体变量stu的Type为: %s, Kind为: %s\n", stuType, stuKind) // 结构体变量stu的Type为: Stu, Kind为: struct
}
反射基本类型
package main
import (
"fmt"
"reflect"
)
var x int = 5
反射变量x的类型与值信息
func main() {
// 对变量x的类型反射,得到reflect.Type类型
xType := reflect.TypeOf(x)
// 对变量x的值反射,得到reflect.Value类型
xValue := reflect.ValueOf(&x)
// 类型与值的类别(kind)
fmt.Println(xType.Kind()) // int
fmt.Println(xValue.Kind()) // ptr
}
转成具体类型
将反射后的变量值转换成in64,必须要要和原数据类型匹配
// 通过Int方法转换成Int64
xValue.Elem().Int() // 10
// 查看转换后的类型为int64
fmt.Printf("%T\n", xValue.Elem().Int()) // int64
修改变量的值
通过反射将变量x的值进行修改,必须要传入指针类型的变量,因为是拷贝不能直接修改
// 修改变量x的值,必须和原先的类型一致
xValue.Elem().SetInt(100) // 通过反射将变量x的值修改为100
// 查看是否修改成功(必须要通过指针)
fmt.Println(Value.Elem()) // 100
类型断言
将反射类型转换为具体类型,思路是先将反射类型转换为空接口,然后在通过断言转换成具体类型
// 将反射类型先转成Interface,然后在断言
data, ok := xValue.Interface().(*int)
if ok {
ret := *data
fmt.Printf("ret的值为: %d, 类型为: %T\n", ret, ret) // ret的值为: 100, 类型为: int
}
反射结构体
对基本类型反射意义不大,更多是反射结构体对象
package main
import (
"fmt"
"reflect"
)
type Stu struct {
Name string `json:"name"`
Age int
City string
}
func (s *Stu) Hello() {
fmt.Println("hello i am", s.Name)
}
func (s *Stu) Say(str string) string {
return fmt.Sprintf("hello %s", str)
}
func main() {
// stu实例,是指针类型的结构体实例
stu := &Stu{Name: "alice", Age: 28, City: "shanghai"}
}
上面创建了一个Stu结构体,以及对*Stu结构体添加了Hello、Say两个方法,并创建了stu(指针结构体)对象
反射stu对象得到类型和值信息
// 对stu反射,得到类型信息
stuType := reflect.TypeOf(stu)
// 对stu反射,得到值信息
stuVal := reflect.ValueOf(stu)
fmt.Println(stuType) // *main.Stu
fmt.Println(stuVal) // &{alice 28 shanghai}
// 获取stu的Kind信息(类别)
stuType.Kind()) // ptr
// 获取stu的Type信息
stuType.String() // *main.Stu
// stu的kind是指针类型吗?
fmt.Println(stuType.Kind() == reflect.Ptr) // true
获取对象字段名与tag
// 获取stu对象的方法个数
stuType.Elem().NumField() // 3
// 第0个字段的字段名
stuType.Elem().Field(0).Name // Name
// 第0个字段的类型
stuType.Elem().Field(0).Type.Name) // string
// 第0个字段的的tag
stuType.Elem().Field(0).Tag.Get("json")) // name
上述表示反射对象共有3个字段名,可以通过数字索引的方式访问,得到字段的类型、名字等。如果觉得比较麻烦可以使用遍历
// 遍历字段的的信息,例如字段名,类型, tag
for i := 0; i < stuType.Elem().NumField(); i++ {
fmt.Printf("字段名: %s 字段类型: %T\n", stuType.Elem().Field(i).Name, stuType.Elem().Field(i).Type.Name)
}
// 输出
字段名: Name 字段类型: string
字段名: Age 字段类型: int
字段名: City 字段类型: string
获取方法信息
// 反射对象的方法个数
stuType.NumMethod() // 2
// 第1个方法的名字(函数名)
stuType.Method(1).Name) // Say
// 第一个方法的类型,也就是函数的类型
fmt.Println(stuType.Method(1).Type) // func(*main.Stu, string) string
调用方法
调用方法只能通过reflect.Value对象,也就是通过reflect.ValueOf函数返回的对象 (这里为stuVal)
// 获取方法个数,与stuType一样
stuVal.NumMethod() // 3
stuVal.Type() // 可以将Value类型转成Type接口类型
调用没有的参数的方法,也就是Hello方法
// 这里调用的是第0个方法,也就是Hello方法,Call函数传的值为nil,表示没有参数
stuVal.Method(0).Call(nil) // hello i am alice
调用有参数的方法,这里是Say方法,此时Call函数的参数必须是[]reflect.Value类型
// 先组织[]reflect.Value类型的参数,用于传给Call函数
args := []reflect.Value{reflect.ValueOf("eric") // 后面的eric表示要给函数传的参数
// 传参并调用,返回的是[]Value类型
methodValue := stuVal.Method(1).Call(args) // 返回的是[]Value类型
// 因为只有一个结果,所以取第0个Value,然后将它转成字符串(必须和该方法的返回类型一致)
result := methodValue[0].String()
fmt.Println(result) // hello eric
- Call函数的参数是切片
[]reflect.Value
类型,切片每个元素就是传入参数值 - Call函数返回值类型也是切片
[]reflect.Value
类型,切片对应的是返回的每个值,真正的返回需要转成具体的类型
通过字符串方式调用方法
给定一个字符串方法,如果有该方法则这些这个方法,下面的例子如果Say方法则执行,没有报错
// 准备参数,mike是要传入方法的参数值
args := []reflect.Value{reflect.ValueOf("mike")}
// 通过字符串的方式获取到指定的方法
method := stuVal.MethodByName("Say")
// 得到返回值[]Value并转成String类型
ret := method.Call(args)[0].String()
fmt.Println(ret) // hello mike
上面的问题,如果方法不存在调用则会异常,所以可以按照下面的方式改进下,先判断下有没有这个方法,然后在执行
method := "Say"
// 遍历方法,匹配方法名
for i := 0; i < stuType.NumMethod(); i++ {
if method == stuType.Method(i).Name {
// 组装参数,mike是传入方法的参数
args := []reflect.Value{reflect.ValueOf("mike")}
// 将method字符串方法传给MethodByName函数
methodValue := stuVal.MethodByName(method)
// 将args传给Call方法然后调用,最后转成字符串
ret := methodValue.Call(args)[0].String()
fmt.Println(ret) // hello mike
}
}
获取与修改字段值信息
// 字段个数
stuVal.Elem().NumField() // 3
// 第0个字段的值
stuVal.Elem().Field(0) // alice
// 第1个字段的值
stuVal.Elem().Field(1) // 28
// 第2个字段的值
stuVal.Elem().Field(2) // shanghai
// 修改第1个字段的值,必须用对应的类型方法才能设置
stuVal.Elem().Field(1).SetInt(120)
// 验证
stuVal.Elem().Field(1) // 120
觉得通过索引的方式获取字段值比较麻烦,同样可以遍历字段的值
// stuVal := reflect.ValueOf(stu)
for i := 0; i < stuVal.Elem().NumField(); i++ {
fmt.Println(stuVal.Elem().Field(i))
}
// 输出
❯ go run main.go
alice
28
shanghai
反射使用细节
私有(小写开头)字段不能修改、私有方法名是不能反射
如果结构体对象的字段是私有的则不能修改值,如果方法名私有也不能反射得到,也不能调用
package main
import (
"fmt"
"reflect"
)
type Stu struct {
Name string `json:"name"`
age int
City string
}
func (s Stu) hello() {
fmt.Println("hello i am", s.Name)
}
func (s Stu) Say() {
fmt.Printf("hello %s\n", "word")
}
func main() {
stu := Stu{Name: "alice", age: 30, City: "beijing"}
stuType := reflect.TypeOf(stu)
stuVal := reflect.ValueOf(stu)
// 1. 获取字段数
stuType.NumField() // 3
// 2. 获取方法数
stuType.NumMethod() // 1
// 3. 将age字段(私有)的值修改为30,报错
stuVal.Field(1) // 30
stuVal.Field(1).SetInt(100) // panic: reflect: reflect.Value.SetInt using value obtained using unexported field
// 4. 尝试调用第0个方法,也就是hello方法,保错
stuVal.Method(0).Call(nil) // panic: reflect: reflect.Value.SetInt using value obtained using unexported field
}
- 第2步,为什么只有1个方法?因为hello方法是小写开头,不能被反射
- 第3步,不能修改age字段的值,因为age字段是小写开头,不能被反射
- 第4步,不能调用hello方法,因为hello是小写开头,不能被反射
指针类型的对象需要通过Elem()函数获取
如果变量的类型是指针结构体,访问字段时必须通过Elem()函数得到指针地址
package main
import (
"fmt"
"reflect"
)
type Stu struct {
Name string `json:"name"`
age int
City string
}
func (s Stu) hello() {
fmt.Println("hello i am", s.Name)
}
func (s Stu) Say() {
fmt.Printf("hello %s\n", "word")
}
func main() {
// 1. stu是指针结构体
stu := &Stu{Name: "alice", age: 30, City: "beijing"}
stuVal := reflect.ValueOf(stu)
// 2. 访问第0个字段,必须要通过Elem()函数获取指针
fmt.Println(stuVal.Elem().Field(0)) // alice
// 3. 访问第0个字段,必须要通过Elem()函数获取指针
fmt.Println(stuVal.Elem().Field(1)) // 30
// 4. 没有通过Elem()函数获则抛出异常
fmt.Println(stuVal.Field(1)) // panic: reflect: call of reflect.Value.Field on ptr Value
}
- 第1步只是得到结构体指针类型
- 第2-3步,必须通过Elem函数获取到指针才能引用字段值
- 第4步,没有通过Elem函数获取指针则抛出异常
接口值、反射对象以及原始类型互相转换
- 原始类型分配给接口值,然后反射为反射对象
- 反射对象转换成接口类型的值,然后对接口值断言,转成原始数据类型
package main
import (
"fmt"
"reflect"
)
type Stu struct {
Name string `json:"name"`
age int
City string
}
func (s Stu) hello() {
fmt.Println("hello i am", s.Name)
}
func (s Stu) Say() {
fmt.Printf("hello %s\n", "word")
}
func Ref(p interface{}) {
// 1. 转换成反射对象
pVal := reflect.ValueOf(p)
// 根据kind,先将反射类型转换为interface,然后断言得到原始类型
switch pVal.Kind() {
case reflect.Struct:
if pp, ok := pVal.Interface().(Stu); ok {
fmt.Printf("pp的值为: %v, 类型为: %T\n", pp, pp)
}
case reflect.Int:
if pp, ok := pVal.Interface().(int); ok {
fmt.Printf("pp的值为: %d, 类型为: %T\n", pp, pp)
}
case reflect.Float64:
if pp, ok := pVal.Interface().(float64); ok {
fmt.Printf("pp的值为: %f, 类型为: %T\n", pp, pp)
}
}
}
func main() {
// 1. stu是指针结构体
stu := Stu{Name: "alice", age: 30, City: "beijing"}
Ref(stu)
// 2. float64类型
x := 5.14
Ref(x)
// 3. int类型
y := 100
Ref(y)
}
// 输出:
pp的值为: {alice 30 beijing}, 类型为: main.Stu
pp的值为: 5.140000, 类型为: float64
pp的值为: 100, 类型为: int
反射实战
根据用户的输入进行反射
接收用户的输入参数,判断是否在结构体有该方法,有则执行
package main
import (
"fmt"
"os"
"path"
"reflect"
"strings"
)
type Service struct{}
func (s *Service) Start() {
fmt.Println("service start ...")
}
func (s *Service) Stop() {
fmt.Println("service stop ...")
}
func (s *Service) Restart() {
fmt.Println("service restart ...")
}
func (s *Service) Status() {
fmt.Println("service status ...")
}
func main() {
// 异常处理
defer func() {
if err := recover(); err != nil {
fmt.Printf("%s [start|stop|restart|status]\n", path.Base(os.Args[0]))
}
}()
// 第一个参数
argv := os.Args[1]
sr := &Service{}
// 反射
srType := reflect.TypeOf(sr)
srVal := reflect.ValueOf(sr)
// 遍历方法,如果有存在argv方法那么就去执行
for i := 0; i < srType.NumMethod(); i++ {
// 转成小写比较
if strings.ToLower(srType.Method(i).Name) == strings.ToLower(argv) {
srVal.MethodByName(srType.Method(i).Name).Call(nil)
return
}
}
fmt.Println("错误选项:", argv)
}
根据结构体字段生成插入sql
创建一个函数,接收任意参数(空接口类型),如果传的结构体对象,通过反射获取字段与字段值,然后生成insert语句
package main
import (
"fmt"
"reflect"
)
type Student struct {
name string
age int
city string
}
type Employee struct {
name string
age int
salary float64
}
func GenInsert(obj interface{}) {
objVal := reflect.ValueOf(obj)
objKind := objVal.Kind()
if objKind == reflect.Struct {
objtab := reflect.TypeOf(obj).Name()
insert := fmt.Sprintf("insert into %s values(", objtab)
for i := 0; i < objVal.NumField(); i++ {
switch objVal.Field(i).Kind() {
case reflect.Int:
if i == 0 {
insert = fmt.Sprintf("%s%d", insert, objVal.Field(i).Int())
} else {
insert = fmt.Sprintf("%s, %d", insert, objVal.Field(i).Int())
}
case reflect.String:
if i == 0 {
insert = fmt.Sprintf("%s%s", insert, objVal.Field(i).String())
} else {
insert = fmt.Sprintf("%s, %s", insert, objVal.Field(i).String())
}
case reflect.Float64:
if i == 0 {
insert = fmt.Sprintf("%s%f", insert, objVal.Field(i).Float())
} else {
insert = fmt.Sprintf("%s,%f", insert, objVal.Field(i).Float())
}
default:
fmt.Println("Unsupported type")
return
}
}
insert = fmt.Sprintf("%s)", insert)
fmt.Println(insert)
}
//fmt.Println("error")
}
func main() {
e := Employee{"alice", 20, 864.56}
o := Student{"bob", 33, "beijing"}
// 传结构体实例e和o给函数,通过反射拿到字段的值,拼装成SQL
GenInsert(e) // insert into Employee values(alice, 20,864.560000)
GenInsert(o) // insert into Student values(bob, 33, beijing)
}
本作品采用《CC 协议》,转载必须注明作者和本文链接