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

栏目: Go · 发布时间: 5年前

内容简介:在Go语言中,反射就是用来检查储存在接口变量内部pair对的一种机制,pair对是以值(value)和实际类型(concrete type)组成.在go中提供两种方法让我们可以轻松地访问接口变量的内容,分别是 reflect.ValueOf()和 reflect.TypeOf()用来获取输入参数接口中的数据的值,如果接口为空则返回0用来获取输入参数接口中的值的类型,如果接口为空则返回nil

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Introduction to Tornado

Introduction to Tornado

Michael Dory、Adam Parrish、Brendan Berg / O'Reilly Media / 2012-3-28 / USD 23.99

Tornado is a scalable, non-blocking web server and web application framework written in Python. It is also light-weight to deploy, fun to write for, and incredibly powerful. Tornado was written with p......一起来看看 《Introduction to Tornado》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具