G

[Golang基础] 反射

RoLingG Golang 2025-05-22

反射

反射算是Go语言的高级功能,它主要是用于框架之类的编写,一般用于不知道变量类型的时候去获取相应类型与值的场景下使用。

package main

import (
   "fmt"
   "reflect"
)

// 获取变量类型
func refType(obj any) {
   objType := reflect.TypeOf(obj)
   fmt.Println(objType.Kind())
   switch objType.Kind() {
   case reflect.String:
      fmt.Println("obj的类型为: string")
   case reflect.Slice:
      fmt.Println("obj的类型为: Slice")
   case reflect.Map:
      fmt.Println("obj的类型为: Map")
   case reflect.Func:
      fmt.Println("obj的类型为: Func")
   default:
      fmt.Println("未知类型")
   }
}

// 获取变量的值(类型也可以获取)
func refValue(obj any) {
   objValue := reflect.ValueOf(obj)
   fmt.Println(objValue, objValue.Type())
}

// 通过反射修改变量的值
func refSetValue(obj any) {
   // 因为得是引用才能进行浅拷贝,将主函数的值改变,所以这里的obj是变量obj的地址
   value := reflect.ValueOf(obj)
   // elem解引用,解引用出obj的值
   elem := value.Elem()
   // 专门取指针反射的值
   switch elem.Kind() {
   case reflect.String:
      elem.SetString("你好")
   }
}

func main() {
   refType("123")
   refType(map[string]string{"name": "你好"})
   refValue("456")
   str := "test"
   // 因为要通过反射去修改值,要通过函数修改主函数的参数,就需要使用引用
   refSetValue(&str)
   fmt.Println(str)
}

反射还可以做到将结构体的值进行修改,这是反射的重要使用方法,像是GORM框架的编写就离不开反射,Go语言的ORM框架需要使用Go语言的结构体去一一对应数据库中表的各个属性,因此在GORM的底层代码里面,反射是很常见的。

补充

反射 reflect 还可以对结构体进行反射操作:

package main

import (
    "fmt"
    "reflect"
    "strings"
)

type Student1 struct {
    Name string `json:"name"`
    Age  int
}

func (Student1) See(name string) {
    fmt.Println("see name:", name)
}

func main() {
    s := Student1{
        Name: "rolingg",
        Age:  21,
    }
    t := reflect.TypeOf(s)
    // 记得传递引用,否则改不了
    v := reflect.ValueOf(&s).Elem()

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fieldName := field.Name
        fieldType := field.Type
        fieldTag := field.Tag.Get("json")
        fmt.Println("结构体当前字段名字为:", fieldName, "结构体当前字段类型为:", fieldType, "结构体当前字段标签为:", fieldTag)

        // 找到想要操作的标签,对其进行操作
        if fieldTag == "name" {
            valueFiled := v.Field(i)
            fmt.Println("修改前:", valueFiled)
            valueFiled.SetString(strings.ToTitle(valueFiled.String()))
            fmt.Println("修改后:", valueFiled)
        }
    }
}

整完之后输出为:

结构体当前字段名字为: Name 结构体当前字段类型为: string 结构体当前字段标签为: name
修改前: rolingg
修改后: ROLINGG
结构体当前字段名字为: Age 结构体当前字段类型为: int 结构体当前字段标签为: 

Call结构体方法操作:

package main

import (
    "fmt"
    "reflect"
)

type Student1 struct {
    Name string `json:"name"`
    Age  int
}

func (Student1) See(name string) {
    fmt.Println("see name:", name)
}

func main() {
    s := Student1{
       Name: "rolingg",
       Age:  21,
    }
    t := reflect.TypeOf(s)
    v := reflect.ValueOf(s)

    for i := 0; i < t.NumMethod(); i++ {
       methodType := t.Method(i)
       fmt.Println("当前结构体的方法为:", methodType.Name, "当前结构体方法的类型为:", methodType.Type)
       if methodType.Name != "See" {
          continue
       }
       methodValue := v.Method(i)
       methodValue.Call([]reflect.Value{
          reflect.ValueOf("rolingg"), // 注意这里的类型要和结构体方法的入参类型对应上
          // reflect.ValueOf(21) // 如果说要多传个id什么的就接着往下写
       })
    }
}
// 甚至可以结合前面去获取结构体的值去进行方法入参赋予。

输出为:

当前结构体的方法为: See 当前结构体方法的类型为: func(main.Student1, string)
see name: rolingg

这里可以看得到能通过反射直接Call一些对应结构体的方法,进行实际操作。

综上

我们可以看得出来反射其实用起来挺麻烦的,能直接获得的东西它需要先套一遍反射才能获得。

我们了解一下 Gorm 这些Go语言的开源框架以后也会发现,它们需要用反射去进行获取对应开发人员使用框架时定义的未知命名的结构体和所赋予的值,这样的场景下就很适合用反射。不用反射确实不太好写。

但也如上面所说的,实际业务开发的时候都是站在巨人的肩膀上写代码,频繁的使用反射这样多套一层是会影响代码的可读性和代码运行的效率的。因为反射的性能没有正常代码高,会慢个一到两个数量级,并且也不能在编译期间发生错误。

PREV
UCloud笔试 编程第二题
NEXT
[Golang] 反射实现ORM的Find方法

评论(0)

发布评论