Reflect

Reflect #

reflect包是Go语言标准库中的一个包,它提供了一组功能,允许我们在运行时动态地查看和操作Go程序中的变量、函数和类型。通过使用reflect包,我们可以以一种通用的方式处理和操作各种类型的值,而无需知道它们的具体类型。

反射三大定律 #

Go 语言中的反射,其归根究底都是在实现三大定律:

  1. Reflection goes from interface value to reflection object.
  2. Reflection goes from reflection object to interface value.
  3. To modify a reflection object, the value must be settable.

我们将针对这核心的三大定律进行介绍和说明,以此来理解 Go 反射里的各种方法是基于什么理念实现的。

第一定律 #

反射的第一定律是:“反射可以从接口值(interface)得到反射对象”。

示例代码:

func main() {
	var x float64 = 3.4
	fmt.Println("type:", reflect.TypeOf(x))
}

输出结果:

type: float64

可能有读者就迷糊了,我明明在代码中传入的变量 x,他的类型是 float64。怎么就成从接口值得到反射对象了。

其实不然,虽然在代码中我们所传入的变量基本类型是 float64,但是 reflect.TypeOf 方法入参是 interface{},本质上 Go 语言内部对其是做了类型转换的。这一块会在后面会进一步展开说明。

第二定律 #

反射的第二定律是:“可以从反射对象得到接口值(interface)”。其与第一条定律是相反的定律,可以是互相补充了。

示例代码:

func main() {
	vo := reflect.ValueOf(3.4)
	vf := vo.Interface().(float64)
	log.Println("value:", vf)
}

输出结果:

value: 3.4

可以看到在示例代码中,变量 vo 已经是反射对象,然后我们可以利用其所提供的的 Interface 方法获取到接口值(interface),并最后强制转换回我们原始的变量类型。

第三定律 #

反射的第三定律是:“要修改反射对象,该值必须可以修改”。第三条定律看上去与第一、第二条均无直接关联,但却是必不可少的,因为反射在工程实践中,目的一就是可以获取到值和类型,其二就是要能够修改他的值。

否则反射出来只能看,不能动,就会造成这个反射很鸡肋。例如:应用程序中的配置热更新,必然会涉及配置项相关的变量变动,大多会使用到反射来变动初始值。

示例代码:

func main() {
	i := 2.33
	v := reflect.ValueOf(&i)
	v.Elem().SetFloat(6.66)
	log.Println("value: ", i)
}

输出结果:

value:  6.66

单从结果来看,变量 i 的值确实从 2.33 变成了 6.66,似乎非常完美。

但是单看代码,似乎有些 “问题”,怎么设置一个反射值这么 ”麻烦“:

  1. 为什么必须传入变量 i 的指针引用?
  2. 为什么变量 v 在设置前还需要 Elem 一下?

本叛逆的 Gophper 表示我就不这么设置,行不行呢,会不会出现什么问题:

func main() {
	i := 2.33
	reflect.ValueOf(i).SetFloat(6.66)
	log.Println("value: ", i)
}

报错信息:

panic: reflect: reflect.Value.SetFloat using unaddressable value

goroutine 1 [running]:
reflect.flag.mustBeAssignableSlow(0x8e)
        /usr/local/Cellar/go/1.15/libexec/src/reflect/value.go:259 +0x138
reflect.flag.mustBeAssignable(...)
        /usr/local/Cellar/go/1.15/libexec/src/reflect/value.go:246
reflect.Value.SetFloat(0x10b2980, 0xc00001a0b0, 0x8e, 0x401aa3d70a3d70a4)
        /usr/local/Cellar/go/1.15/libexec/src/reflect/value.go:1609 +0x37
main.main()
        /Users/eddycjy/go-application/awesomeProject/main.go:10 +0xc5

根据上述提示可知,由于使用 “使用不可寻址的值”,因此示例程序无法正常的运作下去。并且这是一个 reflect 标准库本身就加以防范了的硬性要求。

这么做的原因在于,Go 语言的函数调用的传递都是值拷贝的,因此若不传指针引用,单纯值传递,那么肯定是无法变动反射对象的源值的。因此 Go 标准库就对其进行了逻辑判断,避免出现问题。

因此期望变更反射对象的源值时,我们必须主动传入对应变量的指针引用,并且调用 reflect 标准库的 Elem 方法来获取指针所指向的源变量,并且最后调用 Set 相关方法来进行设置。

函数 #

New()创建指向指定类型的指针 #

reflect.New 函数用于创建一个新的指向指定类型的指针。它返回一个 reflect.Value,这个值持有一个指向新分配的零值的指针。

func main() {
	varType := reflect.TypeOf(0) // 例如,创建一个 int 类型的指针
	newValue := reflect.New(varType)
	
	// 获取指针指向的值
	originalValue := newValue.Elem()
	
	fmt.Println("Type:", newValue.Type())         // *int
	fmt.Println("Original Value:", originalValue) // 0

	// 修改指针指向的值
	originalValue.SetInt(42)
	fmt.Println("Updated Value:", newValue.Elem()) // 42
}

type Type #

TypeOf() 回任何变量的类型 #

TypeOf()ValueOf()函数是reflect包中最常用的两个函数之一。TypeOf()函数可以返回任何变量的类型,而ValueOf()函数可以返回变量的值。这两个函数都接受一个interface{}类型的参数,并返回一个reflect.Typereflect.Value类型的结果。

lessCopy codevar x int = 42
fmt.Println(reflect.TypeOf(x)) // 输出:int
fmt.Println(reflect.ValueOf(x)) // 输出:42

PtoTo() 返回一个表示指向指定类型的指针 #

返回一个reflect.Type表示指向指定类型的指针。

reflect.PtrTo以下是使用获取reflect.Type指向结构类型的指针的示例Person

type Person struct {
    Name string
    Age  int
}
func main() {
    personType := reflect.TypeOf(Person{})
    pointerType := reflect.PtrTo(personType)
    fmt.Printf("%v\n", pointerType)
}

在上面的代码片段中,personType是一个reflect.Type表示Person结构类型的值。然后我们使用reflect.PtrToto 获取reflect.Type表示指向结构类型的指针的值Person,并将结果存储在pointerType. 最后,我们打印pointerType值。

该程序的输出将是:

*main.Person

如我们所见,reflect.PtrTo返回了一个reflect.Type值,表示指向结构类型的指针Person

SliceOf() #

它返回一个reflect.Type代表指定元素类型的切片。

ValueOf() 返回变量的值 #

reflect.ValueOf() 获取数据信息,返回 Value 类型。

Type()获取 Value 的类型信息 #

Type() 函数在 Go 的反射中用于获取 reflect.Value 的类型信息。

用法

  • 获取类型:返回一个 reflect.Type,表示该值的类型。
  • 常用场景:检查变量的类型,比较类型是否匹配。
func main() {
	var age int = 30
	v := reflect.ValueOf(age)

	// 获取类型
	t := v.Type()
	fmt.Println("Type:", t)

	// 检查类型
	if t.Kind() == reflect.Int {
		fmt.Println("The type is int")
	}
}

Elem() 获取其指向的值的反射值 #

使用 Elem() 方法获取其指向的值的反射值。例如,如果 valueOf 是一个指向结构体类型的指针的反射值,那么可以使用以下代码获取其指向的结构体值的反射值:

structValue := valueOf.Elem()

Elem() 方法只能用于指针类型的反射值,否则会抛出一个 panic。如果要获取其他类型的值的成员或字段,需要使用 FieldByName()MethodByName() 等其他反射方法。另外,如果指针的值为 nil,则 Elem() 方法也会抛出一个 panic。因此,在使用 Elem() 方法时,需要先使用 IsNil() 方法检查指针是否为 nil

if !valueOf.IsNil() {
    structValue := valueOf.Elem()
    // ...
}
if valueOf.Kind() == reflect.Ptr {
	valueOf = valueOf.Elem()
}

CanSet()值是否可以被设置 #

CanSet() 方法用于检查一个 reflect.Value 是否可以被设置。只有当该值是可以寻址的(例如,通过指针访问)时,它才返回 true

Set()设置值 #

在 Go 中,reflect.ValueSet 方法用于设置一个值。要使用 Set 方法,值必须是可设置的(可寻址)。

func main() {
	// 定义一个整数变量
	x := 10

	// 获取变量的反射值
	value := reflect.ValueOf(&x).Elem()

	// 使用 Set 方法设置新值
	if value.CanSet() {
		value.SetInt(42)
	}

	fmt.Println("Updated x:", x) // 输出: Updated x: 42
}

Kind() 获取该反射值的类型 #

Kind() 方法获取该反射值的类型。

func (v Value) Kind() Kind
valueOf := reflect.ValueOf(data) //获取数据
if valueOf.Kind() == reflect.String {
    // valueOf 是字符串类型的反射值
}

Interface() 将其转换为对应的接口类型值 #

可以使用 Interface() 方法将其转换为对应的接口类型值。

在将反射值转换为接口类型值时需要使用断言操作符 .(interface{}) 显式地将其转换为 interface{} 类型。

if implStruct, ok := valueOf.Interface().(model.TypeCheckSelf); ok {
	implStruct.TypeCheckSelf()
}

IsZero() 检查该反射值是否为零值 #

IsZero() 方法检查该反射值是否为零值。

IsZero() 方法则返回一个布尔值,表示该反射值是否为其类型的零值。对于大部分类型来说,零值就是其类型的默认值,例如 int 类型的零值是 0string 类型的零值是空字符串 ""

func (v Value) IsZero() bool
valueOf := reflect.ValueOf(data) //获取数据
if valueOf.IsZero() {            //判断是否为其类型的零值
	panic("WriteData data is zero")
}

Indirect(v) 获取v指向的值 #

reflect.Indirect(v)函数用于获取v指向的值,即,如果v是nil指针,则Indirect返回零值。如果v不是指针,则Indirect返回v。

func main() { 
     val1:= []int  {1, 2, 3, 4}              
  
     var val2 reflect.Value = reflect.ValueOf(&val1) 
     fmt.Println("&val2 type:", val2.Kind()) 
      
     // using the function 
     indirectI:= reflect.Indirect(val2) 
     fmt.Println("indirectI  type:", indirectI.Kind()) 
     fmt.Println("indirectI  value:", indirectI) 
}
&val2 type: ptr
indirectI  type: slice
indirectI  value: [1 2 3 4]

Index() 获取其指定索引位置的元素的反射值 #

valueOf 是一个切片、数组或字符串类型的反射值,可以使用 Index() 方法获取其指定索引位置的元素的反射值。

需要注意的是,Index() 方法只能用于切片、数组或字符串类型的反射值,否则会抛出一个 panic。如果要获取其他类型的值的成员或字段,需要使用 FieldByName()MethodByName() 等其他反射方法。另外,如果指定的索引超出了切片或数组的长度,或者字符串中没有对应的 Unicode 码点,则 Index() 方法也会抛出一个 panic。因此,在使用 Index() 方法时,需要先使用 Len() 方法获取切片、数组或字符串的长度,然后确保指定的索引不超出范围。

func (v Value) Index(i int) Value 
valueOf := reflect.ValueOf(data) //获取数据
if valueOf.Kind() == reflect.Slice {
	for i := 0; i < valueOf.Len(); i++ {
		fmt.Println(valueOf.Index(i))
	}
}

FieldByName(name string)通过字段名称获取结构体中的字段值 #

FieldByName 方法用于通过字段名称获取结构体中的字段值。其签名如下:

func (v Value) FieldByName(name string) Value
  • 参数
    • name:字段的名称,类型是 string
  • 返回值
    • 返回具有指定名称的字段的值,如果字段不存在,则返回一个无效的 reflect.Value
type Person struct {
    Name string
    Age  int
}

func main() {
    p := Person{Name: "Alice", Age: 30}
    v := reflect.ValueOf(p)
    
    nameField := v.FieldByName("Name")
    if nameField.IsValid() {
        fmt.Println("Name:", nameField.String())
    } else {
        fmt.Println("Name field not found")
    }
    
    ageField := v.FieldByName("Age")
    if ageField.IsValid() {
        fmt.Println("Age:", ageField.Int())
    } else {
        fmt.Println("Age field not found")
    }
}

FieldByNameFunc 通过一个匹配函数获取结构体中的字段值 #

FieldByNameFunc 方法用于通过一个匹配函数获取结构体中的字段值。其签名如下:

func (v Value) FieldByNameFunc(match func(string) bool) Value
  • 参数
    • match:一个函数,接受一个字符串(字段名)并返回一个布尔值。用于匹配字段名。
  • 返回值
    • 返回第一个与 match 函数匹配的字段的值,如果没有匹配的字段,则返回一个无效的 reflect.Value
type Person struct {
    Name     string
    Age      int
    Location string
}

func main() {
    p := Person{Name: "Alice", Age: 30, Location: "New York"}
    v := reflect.ValueOf(p)
    
    // 匹配以 "Loc" 开头的字段
    field := v.FieldByNameFunc(func(name string) bool {
        return strings.HasPrefix(name, "Loc")
    })

    if field.IsValid() {
        fmt.Println("Field found:", field.String())
    } else {
        fmt.Println("No matching field found")
    }
}

IsValid()检查 reflect.Value 是否有效 #

IsValid() 函数在 Go 的反射中用于检查 reflect.Value 是否有效。以下是其主要用途:

  • 无效值:如果 reflect.Value 是无效的,通常表示反射操作失败,比如访问了不存在的字段。
  • 返回值IsValid() 返回 false 表示该值无效,true 表示有效。
type Person struct {
	Name string
	Age  int
}

func main() {
	p := Person{Name: "Alice", Age: 30}
	v := reflect.ValueOf(p)

	// 尝试获取存在的字段
	nameField := v.FieldByName("Name")
	if nameField.IsValid() {
		fmt.Println("Name field is valid")
	} else {
		fmt.Println("Name field is invalid")
	}

	// 尝试获取不存在的字段
	invalidField := v.FieldByName("NonExistentField")
	if invalidField.IsValid() {
		fmt.Println("NonExistentField is valid")
	} else {
		fmt.Println("NonExistentField is invalid")
	}
}

业务实例 #

获取Size字段大小 #

for _, data := range datas {
		v := reflect.ValueOf(data)
		if v.Kind() != reflect.Struct {
			common.Log.Errorf("expected a struct, but got %s", v.Kind())
		}
		// 获取字段值
		fieldVal := v.FieldByName("Size")
		if !fieldVal.IsValid() {
			fieldVal = v.FieldByName("FileSize")
			if !fieldVal.IsValid() {
				continue
			}
		}
		totalSize += fieldVal.Int()
}

通过结构体interface类型,得到结构体,并查询数据库 #

func (dp *DataProxy) DeleteDatas(dataType string, cid, eid, nid int64) (err error) {
	data, found := vmodel.DataType[dataType]
	if !found {
		err = fmt.Errorf("data type not found: %s", dataType)
		return
	}
	tableName := db.GetDataTableName(eid, dataType)
	engine, found, errTemp := db.GetDBInstance().GetDataEngine(cid, eid, dataType, false)
	if !found {
		return
	}
	if errTemp != nil {
		err = errTemp
		return
	}
	if !found {
		err = fmt.Errorf("table not found: %s", tableName)
		return
	}

	vp := reflect.New(reflect.TypeOf(data))
	var v reflect.Value
	if vp.Kind() == reflect.Ptr {
		v = vp.Elem()
	}

	field := v.FieldByName("Pid")
	if !field.IsValid() {
		return fmt.Errorf("no such field: %s in struct")
	}

	if !field.CanSet() {
		return fmt.Errorf("cannot set field %s")
	}

	val := reflect.ValueOf(nid)
	if field.Type() != val.Type() {
		return fmt.Errorf("provided value type doesn't match field type")
	}

	field.Set(val)

	err = engine.DeleteByStruct(tableName, vp.Interface())
	if err != nil {
		err = fmt.Errorf("delete by struct err:%s", err.Error())
		return
	}
	return err
}