浅谈Go的反射

84 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情

今天来简单学习下Go的反射概念,反射是指在程序运行期间可以对程序本身进行访问和修改的能力

变量的概念

  1. 变量包含类型信息值信息:例如 var name string = "hello world"
  2. 类型信息:每一个变量都会有一个静态的原始信息,这个类型是在编译时就已经定义好的
  3. 值信息:在程序运行过程中会动态改变的值

go中的reflect包封装了反射相关的方法:

  1. reflect.TypeOf():静态获取类型信息,返回的是reflect.Type类型
  2. reflect.VauleOf():动态获取值信息,返回的是reflect.Value类型

反射获取interface类型、值信息

package main

import (
   "fmt"
   "reflect"
)

func reflect_type(a interface{}) {
	t := reflect.TypeOf(a) //获取静态类型
        v := reflect.ValueOf(a)//获取值
        fmt.Println("值是: ",v.Float()) //调用Float()方法
	fmt.Println("类型是:", t)
	// kind()可以获取具体类型
	k := t.Kind()
	fmt.Println(k)
	switch k {
	case reflect.Float64:
		fmt.Printf("a is float64\n")
	case reflect.String:
		fmt.Println("string")
	}
}

func main() {
	var x float64 = 2.6
	reflect_type(x)
}

image.png

反射中关于类型的定义分为两种:类型(Type)和种类(Kind) 因为在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型是不可以修改的,当需要区分指针、结构体、map等大类型时,就会用到种类(Kind),通过kind()获取种类

Go语言的反射中的数组、切片、Map、指针等类型的变量,它们的.Name()都是返回

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     // 类型别名
   var d map[string]string
   reflectType(a)
   reflectType(b)
   reflectType(c)
   reflectType(d)
}

image.png

使用reflect.ValueInterface()将反射还原成一个接口(接口信息包括类型和值)

var val interface{} = 100
var val_ int = reflect.ValueOf(val).Interface().(int)
fmt.Println(val_)  //100

反射修改变量的值

想要通过反射修改变量的值,必须传递变量地址才能修改变量值,如果传递值拷贝就不会修改原来的值,反射中使用Elem()方法来获取指针对应的值

// 反射修改值
func reflect_set_value(a interface{}) {
   v := reflect.ValueOf(a)
   k := v.Kind() //获取类型
   switch k {
   case reflect.Float64:
      // 反射修改值
      v.SetFloat(6.9) //错误
      fmt.Println("a is ", v.Float())
   case reflect.Ptr:
      // Elem()获取地址指向的值
      v.Elem().SetFloat(7.9)
      fmt.Println("case:", v.Elem().Float())
   }
}

func main() {
   var x float64 = 3.4
   // 反射认为下面是指针类型,不是float类型
   fmt.Println("before:", x)
   reflect_set_value(&x)
   fmt.Println("after:", x)
}

image.png

反射修改结构体

通过reflect.ValueOf()方法获取结构体指针对象,再使用FieldByName方法获取对应字段的值,再通过SetString来修改结构体的值,演示代码如下:

// 定义结构体
type User struct {
   Id   int    `json:"id" db:"id2"'`
   Name string `json:"name" db:"name2"'`
   Age  int    `json:"age" db:"age2"'`
}

// 修改结构体值
func SetValue(u interface{}) {
   v := reflect.ValueOf(u)
   // 获取指针指向的元素
   v = v.Elem()
   // 取字段
   f := v.FieldByName("Name")
   if f.Kind() == reflect.String {
      f.SetString("xiaoMing")
   }
}

func main() {
   u := User{1, "YYQQ", 20}
   fmt.Println("before:", u)
   SetValue(&u)
   fmt.Println("after:", u)
  //获取字段信息
    v := reflect.TypeOf(u)
    for i := 0; i < v.NumField(); i++ {
            // 获取字段的值信息
            tag := v.Field(i).Tag.Get("json")
            name := v.Field(i).Name
            types := v.Field(i).Type
            index := v.Field(i).Index
            fmt.Printf("name:%s index:%d type:%v json tag:%v\n",
                    name, index, types, tag)
    }
}

image.png

不能滥用反射:

  1. 基于反射的代码是极其脆弱的,反射的错误只有在运行过程中,才会触发panic
  2. 大量使用反射的代码,会让其他人难以理解
  3. 反射的性能低,运行效率不高

总结

今天简单的学习了Go反射知识,还有很多细节的用法可以查看官方文档,之后要开始着手学习框架方面的知识,对于刚入门go语言的我来说,还有许多地方需要学习,有错误的地方欢迎大家指出,共同进步!!