Golang 通过反射的方式调用结构体方法

env107 · · 10015 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

在Go语言中,反射就是用来检查储存在接口变量内部pair对的一种机制,pair对是以值(value)和实际类型(concrete type)组成.在go中提供两种方法让我们可以轻松地访问接口变量的内容,分别是 reflect.ValueOf()和 reflect.TypeOf()

reflect.ValueOf(i interface{} )

用来获取输入参数接口中的数据的值,如果接口为空则返回0

reflect.TypeOf(i interface{} )

用来获取输入参数接口中的值的类型,如果接口为空则返回nil

var num float64 = 1.2345
fmt.Println("type : " , reflect.TypeOf(num)) //float64
fmt.Println("type : " , reflect.ValueOf(num)) //1.2345

这说明反射可以将“接口类型变量”转换为“反射类型变量” , 反射类型指的就是reflect.Type和reflect.Value
在构建框架工程的时候,需要可以随意扩展的方法,或者说在Web程序框架设计中编写调度分发控制器的时候,往往需要用到反射(reflect)来完成相关工作
以下例子是演示通过反射来调用结构体方法:

package main
import (
    "fmt"
    "reflect"
)
type User struct{
    Id int 
    Name string
    Age int
}
//ToString方法
func (u User) String() string {
    return "User[ Id " + string(u.Id) +"]"
}
//设置Name方法
func (u *User) SetName(name string) string{
  oldName := u.Name
  u.Name = name
  return oldName 
}
//年龄数+1
func (u *User) AddAge() bool {
    u.Age++
    return true
}
//测试方法
func (u User) TestUser() {
  fmt.Println("我只是输出某些内容而已....")
}

func main(){
    //通过反射的方式调用结构体类型的方法
    var setNameStr string = "SetName"
    var addAgeStr string = "AddAge"
    user := User{
        Id : 1,
        Name : "env107" , 
        Age : 18 ,
    }
    //1.获取到结构体类型变量的反射类型
    refUser:= reflect.ValueOf(&user)  //需要传入指针,后面再解析
    fmt.Println(refUser)
    //2.获取确切的方法名
    //带参数调用方式
    setNameMethod := refUser.MethodByName( setNameStr  )
    args := []reflect.Value{ reflect.ValueOf("Mike")  } //构造一个类型为reflect.Value的切片
    setNameMethod.Call(args) //返回Value类型
    //不带参数调用方式
    addAgeMethod := refUser.MethodByName( addAgeStr )
    addAgeMethod.Call( make([]reflect.Value , 0) )
    
    fmt.Println("User.Name = ",user.Name)
    fmt.Println("User.Age = ",user.Age)

}

上述代码运行的结果将会是

<*main.User Value>
User.Name =  Mike
User.Age =  19

可见,通过反射的方式成功的将user的名字更改为Mike并将Age的数+1
在这里需要思考一个问题,方法TestUser接收者的类型是User类型而非User的指针类型,在结构体当中,接收者类型的区别将影响该结构体方法的可见性。假设接收者类型为指针类型则该方法称为指针方法,假如是值类型,则该方法称为值方法。
如果将代码改成

refUser:= reflect.ValueOf(user)

在我们后续通过反射调用TestUser的时候,将会引起恐慌

panic: reflect: call of reflect.Value.Call on zero Value

造成该恐慌的原因是refUser.MethodByName( setNameStr )并没有返回一个reflect.Value而是一个nil
原因在于,User类型是*User的基底类型,在Go的指针知识中,有一条规则:一个指针类型拥有它以及它的基底类型为接收者类型的所有方法,而它的基底类型却只能拥有以它本身为接收者类型的方法。

也就是说,User为User基底类型,所以User类型只能拥有以它本身为接收者类型(User类型)的方法,也就是TestUser , 而指针方法(SetName和AddAge)只有User类型才拥有,而 refUser:= reflect.ValueOf(user) 拿到的是User类型的反射类型对象,因此并没有指针方法,引起恐慌。

这里补充一下指针方法和值方法的区别

type User struct{
    Id int 
    Name string
    Age int
}
func (u *User) test1() {}
user := User{
        Id : 1,
        Name : "env107" , 
        Age : 18 ,
    }

以接收者类型为*User的结构体方法,其中变量u是user的值的指针的副本,但如果接收者类型为User的结构体方法,也就是值方法,则变量u是user的一个副本,如果尝试改变u的副本的属性值,则对user的属性值是不会造成影响的

参考文章:https://studygolang.com/articles/12348?fr=sidebar


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:env107

查看原文:Golang 通过反射的方式调用结构体方法

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

10015 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传