go中reflect反射的应用实践和注意事项详解

337 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

reflect反射的使用

reflect.Type和reflect.Value

go语言中反射是由reflect包提供的,它定义了两个重要的类型,TypeValue

1、一个reflect.Type表示一个Go类型。

函数reflect.TypeOf接受任意类型的 interface{}类型,并以reflect.Type形式返回其动态类型;

t := reflect.TypeOf(1)
fmt.Println(t) //int
fmt.Println(t.String())  //int

其中TypeOf(1)传给interface{}参数,将一个值转为接口类型会有一个隐式的接口转换操作。该操作会创建一个包含两个信息的接口值:操作数的动态类型(这里是int)和它的动态的值(这里是1)

2、一个reflect.Value可以装载任意类型的值。

函数reflect.ValueOf接受任意的interface{}类型,并返回一个装载着其动态值的reflect.Value

v := reflect.ValueOf(1)
fmt.Println(v)  //1
fmt.Println(v.String())  //<int Value>

reflect.MethodByName()

reflect.MethByName用于获取与具有给定名称的v方法相对应的函数值。

用法:

func (v Value) MethodByName(name string) Value

package main 
     
import ( 
    "fmt"
    "reflect"
) 
   
// Main function 
type T struct {} 
  
func (t *T) Hello() { 
    fmt.Println("Hello World") 
} 
  
func main() { 
    var t T 
    reflect.ValueOf(&t).MethodByName("Hello").Call([]reflect.Value{}) 
}

call()函数

在反射中,函数 和方法的类型(Type)都是reflect.Func,如果要调用函数的话,可以通过ValueCall方法。

package main

import (
	"fmt"
	"reflect"
)

func hello(){
	fmt.Println("Hello World!")
}
func main()  {
	f := hello
	rv := reflect.ValueOf(f)
	fmt.Println(rv.Kind()) // func
	rv.Call(nil) //Hello World!
}

Call()方法的参数是一个Valueslice,对应的反射函数类型的参数,返回值也是一个Valueslice,也是对应反射函数类型的返回值。

如果反射值对象(reflect.Value)中值的类型为函数时,可以通过reflect.Value调用该函数。使用反射调用函数时,需要将参数使用反射值对象的切片[]reflect.Value构造后传入Call()中,调用完成后,函数的返回值通过[]reflect.Value返回。

reflect.IsValid()

reflect.IsValid()函数用于检查v是否有效。当值本身非法时,返回false,例如reflect Value不包含任何值,值为nil等。

用法:

func (v Value) IsValid() bool

示例:

rv := reflect.ValueOf(f)
fmt.Printf("rv IsVaild :%t\n", rv.IsValid()) //rv IsVaild :true

reflect.IsNil()

reflect.IsNil()函数用于检查v是否为nil。返回值是bool类型,如果值类型不是通道、函数、接口、map、指针或切片时发生panic。

reflect.Kind()

在反射包中还有一种重要的类型,称为Kind,反射包中Kind和Type类型相似,但他们也有一定区别,示例如下:

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	 Id int
	 Name string
}
func main()  {
	u := User{
		Id:   1,
		Name: "test",
	}
	Hello(u)
}

func Hello(u interface{})  {
	t := reflect.TypeOf(u)
	fmt.Println("u type of ", t) //u type of  main.User
	fmt.Println("u kind", t.Kind()) //u kind struct
}

通过上面的示例可以看出

  • Type表示interface{}的实际类型
  • Kind表示特定类型。

总结

这里只是介绍了反射的一些基本用法,还有很多方法没有涉及,可以参考文档进一步的学习,反射在Go中是一个非常强大和先进的概念,当然也不是没有弊端的,使用反射编写清晰和可维护的代码是非常困难的。在任何可能的情况下都应该避免使用,应该谨慎使用,只有在绝对必要的时候才应该使用。反射会使得代码执行效率较慢,原因有:

1、涉及到内存分配以及后续的垃圾回收

2、 reflect实现里面有大量的枚举,也就是for循环,比如类型之类的