我的学习日志 我的学习日志
首页
  • Go

    • Go基础知识
  • Python

    • Python进阶
  • 操作系统
  • 计算机网络
  • MySQL
  • 学习笔记
  • 常用到的算法
其他技术
  • 友情链接
  • 收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

xuqil

一介帆夫
首页
  • Go

    • Go基础知识
  • Python

    • Python进阶
  • 操作系统
  • 计算机网络
  • MySQL
  • 学习笔记
  • 常用到的算法
其他技术
  • 友情链接
  • 收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 环境部署

  • 测试

  • 反射

    • 反射
      • reflect.Kind
      • reflect.Type和reflect.Value
        • reflect.Type
        • reflect.Value
      • 遍历基础类型
      • 遍历结构体
        • 遍历结构体字段
        • 遍历结构体方法
      • 通过reflect.Value修改值
        • 修改基础类型的值
        • 修改结构体的值
      • 参考
  • 数据库操作

  • 并发编程

  • 内存管理

  • Go 技巧

  • 《go基础知识》
  • 反射
Xu Qil
2023-01-18
0
目录

反射

# 反射

学习 Go 反射,可以查看reflect里的各种注释,还可以跟着$GOROOT/src/reflect/all_test.go测试文件尝试反射的各种用法。

相关代码链接:https://github.com/xuqil/learning-go/tree/base/base/ch12

绝大多数编程语言的类型系统都是类似的,会有声明类型、实际类型之类的分别。

在 Go 反射里,一个实例可以看成两个部分:

  • 值(reflect.Value)
  • 实际类型(reflect.Type)

# reflect.Kind

Kind是一个枚举值,用来判断操作的对应类型,例如是否是指针、是否是数组、是否是切片等。

reflect/type.go

// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Pointer
	Slice
	String
	Struct
	UnsafePointer
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

例如,判断一个常量的反射类型:

reflect_test.go

func TestReflect_Kind(t *testing.T) {
	var age int8 = 18
	typ := reflect.TypeOf(age)
	switch typ.Kind() {
	case reflect.Int8:
		fmt.Println("age的类型:", reflect.Int8)
	default:
		fmt.Println("age的类型:", typ.Kind().String())
	}
}
1
2
3
4
5
6
7
8
9
10

# reflect.Type和reflect.Value

反射的相关 API 都在reflect包,最核心的两个:

  • reflect.Value:用于操作值,部分值时可以被反射修改的
  • reflect.Type:用于操作类型信息,类型信息只能被读取

image-20230116224353395

注意:reflect.Type可以通过reflect.Value得到,但是反过来则不行。

# reflect.Type

一个reflect.Type表示一个 Go 类型。它是一个接口,有许多方法来区分类型以及检查它们的组成部分,例如一个结构体的成员或一个函数的参数等。reflect.Type体现的是动态类型。

reflect/Type.go

type Type interface {
	...
	Method(int) Method
	MethodByName(string) (Method, bool)
	NumMethod() int
	Name() string
	PkgPath() string
	...
	String() string

	// Kind returns the specific kind of this type.
	Kind() Kind
	...

	// Elem returns a type's element type.
	// It panics if the type's Kind is not Array, Chan, Map, Pointer, or Slice.
	Elem() Type

	// Field returns a struct type's i'th field.
	// It panics if the type's Kind is not Struct.
	// It panics if i is not in the range [0, NumField()).
	Field(i int) StructField

	// FieldByIndex returns the nested field corresponding
	// to the index sequence. It is equivalent to calling Field
	// successively for each index i.
	// It panics if the type's Kind is not Struct.
	FieldByIndex(index []int) StructField

	// FieldByName returns the struct field with the given name
	// and a boolean indicating if the field was found.
	FieldByName(name string) (StructField, bool)
	FieldByNameFunc(match func(string) bool) (StructField, bool)
    ...

	// Key returns a map type's key type.
	// It panics if the type's Kind is not Map.
	Key() Type
	Len() int

	// NumField returns a struct type's field count.
	// It panics if the type's Kind is not Struct.
	NumField() int
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

可以通过reflect.TypeOf和Value.Type拿到Type:

typ := reflect.TypeOf(3)  // a reflect.Type
fmt.Println(typ.String()) // "int"
fmt.Println(typ)          // "int"

typ2 := reflect.TypeOf(int8(3)) // a reflect.Type
fmt.Println(typ2.String())     // "int8"
fmt.Println(typ2)              // "int8"
1
2
3
4
5
6
7

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

因为reflect.TypeOf返回的是一个动态类型的接口值,它总是返回具体的类型。因此,下面的代码将打印"*os.File"而不是"io.Writer"。

var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File"
1
2

# reflect.Value

一个reflect.Value可以装载任意类型的值。函数reflect.ValueOf接受任意的any类型,并返回一个装载着其动态值的reflect.Value。和reflect.TypeOf类似,reflect.ValueOf返回的结果也是具体的类型,但是reflect.Value也可以持有一个接口值。

val := reflect.ValueOf(3) // a reflect.Value
fmt.Println(val)          // "3"
fmt.Printf("%v\n", val)   // "3"
fmt.Println(val.String()) // "<int Value>"

val2 := reflect.ValueOf(int8(3)) // a reflect.Value
fmt.Println(val2)               // "3"
fmt.Printf("%v\n", val2)        // "3"
fmt.Println(val2.String())      // "<int8 Value>"
1
2
3
4
5
6
7
8
9

reflect.ValueOf的逆操作是reflect.Value.Interface方法。它返回一个any类型,装载着与reflect.Value相同的具体值:

val := reflect.ValueOf(3) // a reflect.Value
x := val.Interface()      // an interface{}
i := x.(int)              // an int
fmt.Printf("%d\n", i)     // "3"

val2 := reflect.ValueOf(int8(3)) // a reflect.Value
x2 := val2.Interface()           // an interface{}
i2 := x2.(int8)                  // an int8
fmt.Printf("%d\n", i2)           // "3"
1
2
3
4
5
6
7
8
9

此处注意需要通过类型断言来访问内部的值。

对Value调用Type方法将返回具体类型所对应的reflect.Type:

val := reflect.ValueOf(3) // a reflect.Value
typ := val.Type()         // a reflect.Type
fmt.Println(typ.String()) // "int"

val2 := reflect.ValueOf(int8(3)) // a reflect.Value
typ2 := val2.Type()              // a reflect.Type
fmt.Println(typ2.String())       // "int8"
1
2
3
4
5
6
7

# 遍历基础类型

无论是基础类型,还是结构体,我们都可以通过reflect.Type和reflect.Value来分别取的它们的类型和值。

下面示例是获取基础类型string的类型和值,

func TestReflect_String(t *testing.T) {
	var name = "Jerry"
	nTyp := reflect.TypeOf(name)
	nVal := reflect.ValueOf(name)

	// string->type: string value: Jerry
	fmt.Printf("%s->type: %s value: %s\n", nTyp.String(), nTyp.Kind().String(), nVal.Interface())
}
1
2
3
4
5
6
7
8

如果reflct.ValueOf传入的参数是指针,需要通过Elem方法来获取指针指向的值:

func TestReflect_StringPtr(t *testing.T) {
	var name = "Jerry"

	nTyPtr := reflect.TypeOf(&name)
	nValPtr := reflect.ValueOf(&name)

	// *string->type: ptr value: 0xc0000745e0
	fmt.Printf("%s->type: %s value: %v\n", nTyPtr.String(), nTyPtr.Kind().String(), nValPtr.Interface())
	// *string->type: ptr value: Jerry
	fmt.Printf("%s->type: %s value: %v\n", nTyPtr.String(), nTyPtr.Kind().String(), nValPtr.Elem().Interface())
}
1
2
3
4
5
6
7
8
9
10
11

# 遍历结构体

对于基础类型,可以直接通过reflect.Type的方法拿到它类型:

对于结构体类型,可以通过Type的NumField()方法取得结构体字段的数量,然后根据下标从Field(i int)方法取得字段的信息,Field方法的返回值为StructField结构体。此外,还可以通过FieldByName方法,传入结构体字段的名称来取得字段的信息。

# 遍历结构体字段

  1. 遍历普通结构体

    func TestReflect_StructField(t *testing.T) {
    
    	type ReflectUser struct {
    		Name string
    		Age  int
    	}
    
    	ru := ReflectUser{
    		Name: "Tom",
    		Age:  18,
    	}
    
    	IterateFields(ru)
    }
    func IterateFields(entity any) {
    	typ := reflect.TypeOf(entity)
    	val := reflect.ValueOf(entity)
    	for i := 0; i < typ.NumField(); i++ {
    		ft := typ.Field(i)
    		fv := val.Field(i)
    		fmt.Printf("%s.%s 的类型: %s  值: %v\n", typ.Name(), ft.Name, ft.Type.String(), fv.Interface())
    	}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    打印结果:

    ReflectUser.Name 的类型: string  值: Tom
    ReflectUser.Age 的类型: int  值: 18
    
    1
    2
  2. 遍历带私有变量的结构体

    给ReflectUser结构体新增一个不可导出变量phone:

    type ReflectUser struct {
        Name string
        Age  int
        phone string
    }
    
    ru := ReflectUser{
        Name:  "Tom",
        Age:   18,
        phone: "666",
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    再次运行程序,发生panic了:

    panic: reflect.Value.Interface: cannot return value obtained from unexported field or method [recovered]
    	panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
    
    1
    2

    由于phone字段是unexported类型,所以不能获取phone的值。

    新增一个unexported类型判断,如果是unexported,就取它的零值,可以使用func Zero(Type) Value函数生成带零值的Value:

    func IterateFields(entity any) {
    	typ := reflect.TypeOf(entity)
    	val := reflect.ValueOf(entity)
    	for i := 0; i < typ.NumField(); i++ {
    		ft := typ.Field(i)
    		fv := val.Field(i)
    		if ft.IsExported() {
    			fmt.Printf("%s.%s 的类型: %s  值: %v\n",
    				typ.Name(), ft.Name, ft.Type.String(), fv.Interface())
    		} else {
    			fmt.Printf("%s.%s 的类型: %s  值: %q\n",
    				typ.Name(), ft.Name, ft.Type.String(), reflect.Zero(ft.Type).Interface())
    		}
    	}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    打印结果:

    ReflectUser.Name 的类型: string  值: Tom
    ReflectUser.Age 的类型: int  值: 18
    ReflectUser.phone 的类型: string  值: ""
    
    1
    2
    3
  3. 零值结构体

    如果结构体传入的为nil结构体,就不能拿到对应的字段了。可以通过func (v Value) IsZero() bool方法判断是否为零值结构体。

    func TestReflect_StructField(t *testing.T) {
        // ...
    	var ru = (*ReflectUser)(nil)
    	IterateFields(ru)
    }
    func IterateFields(entity any) {	
        // ...
    	if val.IsZero() {
    		fmt.Println("不支持零值结构体")
    		return
    	}
        // ...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    此外,还有个func (v Value) IsNil() bool方法,但它只能判断chan, func, interface, map, pointer, or slice value是否为nil。

  4. 遍历结构体指针

    这里,我们尝试着向IterateFields方法传入一个结构体指针:

    IterateFields(&ru)
    
    1

    运行程序后,发生panic了:

    panic: reflect: NumField of non-struct type *ch12.ReflectUser [recovered]
    	panic: reflect: NumField of non-struct type *ch12.ReflectUser
    
    1
    2

    从reflect.Type接口里的NumField方法的注释里可以看出,如果typ的Kind不是Struct就会发生panic:

    // NumField returns a struct type's field count.
    // It panics if the type's Kind is not Struct.
    NumField() int
    
    1
    2
    3

    我们传入的参数是&ru,即指针类型,所以会发生panic,现在只需要修改一下IterateFields函数,让typ的Kind不是reflect.Ptr:

    func IterateFields(entity any) {
    	// ...
    	for typ.Kind() == reflect.Ptr {
    		// 拿到指针指向的对象
    		typ = typ.Elem()
    		val = val.Elem()
    	}
    	if typ.Kind() != reflect.Struct {
    		fmt.Println("不是结构体类型")
    		return
    	}
    	// ...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    Type的Elem()方法返回Type的元素类型;Value的Elem()方法返回Value指针指向的Value,我们可以通过遍历的操作,拿到reflect.Ptr的元素类型。

    type Type interface {
        // ...
        // Elem returns a type's element type.
        // It panics if the type's Kind is not Array, Chan, Map, Pointer, or Slice.
        Elem() Type
        // ...
    }
    
    // Elem returns the value that the interface v contains
    // or that the pointer v points to.
    // It panics if v's Kind is not Interface or Pointer.
    // It returns the zero Value if v is nil.
    func (v Value) Elem() Value
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  5. 遍历嵌套结构体

    例如ReflectUser嵌套了一个*Address:

    type Address struct {
        city string
    }
    
    type ReflectUser struct {
        Name string
        Age  int
        phone   string
        address *Address
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    这时候需要用到递归遍历,可以参考《Go 语言圣经》的“Display递归打印” (opens new window)篇章。

# 遍历结构体方法

结构体的方法信息可以通过NumMethod和Method两个方法遍历得到,也可以使用MethodByName方法根据方法名称直接取得;其中Method和MethodByName返回的都是Method结构体。

// Method represents a single method.
type Method struct {
   // Name is the method name.
   Name string

   PkgPath string

   Type  Type  // method type
   Func  Value // func with receiver as first argument
   Index int   // index for Type.Method
}

type Type interface {
    // ...
    Method(int) Method
	Method(int) Method
	MethodByName(string) (Method, bool)
    // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

下面的例子,通过遍历结构体和结构体指针取得对应的方法,并调用方法获取返回值。

types/user.go

package types

import "fmt"

type User struct {
	Name string
	Age  int
	phone string
}

func NewUser(name string, age int, phone string) User {
	return User{
		Name:  name,
		Age:   age,
		phone: phone,
	}
}

func NewUserPtr(name string, age int, phone string) *User {
	return &User{
		Name:  name,
		Age:   age,
		phone: phone,
	}
}

func (u User) GetAge() int {
	return u.Age
}

func (u User) GetPhone() string {
	return u.phone
}

func (u *User) ChangeName(newName string) {
	u.Name = newName
}

func (u User) private() {
	fmt.Println("private")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

methods.go

package ch12

import (
	"fmt"
	"reflect"
)

func IterateFunc(entity any) {
	typ := reflect.TypeOf(entity)
	numMethod := typ.NumMethod()
	fmt.Println(typ.String(), "方法的个数:", numMethod)
	for i := 0; i < numMethod; i++ {
		method := typ.Method(i)
		fn := method.Func // 拿到结构体方法的 Value,需要注意的是,方法的第一个参数为 receiver

		numIn := fn.Type().NumIn()                     // 方法参数的个数
		inputTypes := make([]reflect.Type, 0, numIn)   // 每个参数的类型
		inputValues := make([]reflect.Value, 0, numIn) //每个入参的零值

		inputValues = append(inputValues, reflect.ValueOf(entity)) // 第一个参数为 receiver
		inputTypes = append(inputTypes, reflect.TypeOf(entity))    // 第一个入参为 receiver

		paramTypes := fmt.Sprintf("%s", typ.String())
		// 这个遍历是为了得到方法的各个参数类型,以及参数对应的零值
		for j := 1; j < numIn; j++ {
			fnInType := fn.Type().In(j)
			inputTypes = append(inputTypes, fnInType)                 // append 参数的类型
			inputValues = append(inputValues, reflect.Zero(fnInType)) // append 入参的零值

			paramTypes = paramTypes + "," + fnInType.String()
		}

		returnTypes := ""

		numOut := fn.Type().NumOut() // 返回值的个数
		outputTypes := make([]reflect.Type, 0, numOut)
		// 拿到每个返回值的类型
		for j := 0; j < numOut; j++ {
			fnOutType := fn.Type().Out(j)
			outputTypes = append(outputTypes, fnOutType)

			if j > 0 {
				returnTypes += ","
			}
			returnTypes += fnOutType.String()
		}

		resValues := fn.Call(inputValues) // 调用结构体里的方法,Call 的参数为方法入参的切片,返回值存储在切片
		result := make([]any, 0, len(resValues))
		for _, v := range resValues {
			result = append(result, v.Interface())
		}

		fSign := fmt.Sprintf("func(%s) %s", paramTypes, returnTypes)
		fmt.Println("方法签名:", fSign)
		fmt.Printf("调用方法: %s 入参: %v 返回结果: %v\n", method.Name, inputValues, result)
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

User有三个公开的方法GetAge、GetPhone和ChangeName以及一个私有方法private,其中GetAge、GetPhone和private的receiver都为User,而ChangeName的receiver为*User。不同的receiver有不同的方法集,指针类型的receiver拥有非指针类型receiver的方法集,而非指针类型的receiver不会包含指针类型receiver的方法集。

需要额外注意的一点是,方法的第一个参数实际上是它的receiver:

func TestReflect_Method(t *testing.T) {
	u := types.NewUser("Tom", 18, "666")
	f := types.User.GetAge
	fmt.Printf("%T\n", u.GetAge)             // func() int
	fmt.Printf("%T\n", f)                    // func(types.User) int
	fmt.Printf("%T\n", (*types.User).GetAge) //func(*types.User) int

	// 下面两条语句是等价的
	fmt.Println(u.GetAge()) // 18
	fmt.Println(f(u))       // 18
}
1
2
3
4
5
6
7
8
9
10
11

测试IterateFunc:

  • 传入普通结构体

    func TestIterateFunc(t *testing.T) {
    	user := types.NewUser("Tom", 18, "666")
    	IterateFunc(user)
    }
    
    1
    2
    3
    4

    执行结果:

    types.User 方法的个数: 2
    方法签名: func(types.User) int
    调用方法: GetAge 入参: [<types.User Value>] 返回结果: [18]
    方法签名: func(types.User) string
    调用方法: GetPhone 入参: [<types.User Value>] 返回结果: [666]
    
    1
    2
    3
    4
    5
  • 传入指针结构体

    func TestIterateFunc(t *testing.T) {
    	user := types.NewUserPtr("Tom", 18, "666")
    	IterateFunc(user)
    }
    
    1
    2
    3
    4

    执行结果:

    *types.User 方法的个数: 3
    方法签名: func(*types.User,string) 
    调用方法: ChangeName 入参: [<*types.User Value> ] 返回结果: []
    方法签名: func(*types.User) int
    调用方法: GetAge 入参: [<*types.User Value>] 返回结果: [18]
    方法签名: func(*types.User) string
    调用方法: GetPhone 入参: [<*types.User Value>] 返回结果: [666]
    
    1
    2
    3
    4
    5
    6
    7

从结果可以看到私有方法是拿不到的,也是不能执行的。

# 通过reflect.Value修改值

可以通过Value的CanSet方法判断Value是否可被修改;调用Value的Set方法可以给变量重新设置一个新的值。值得注意的是,只有addressable的Value才可以被修改,例如指针;此外如果变量的Type为unexported也是不可修改的。

// CanSet reports whether the value of v can be changed.
// A Value can be changed only if it is addressable and was not
// obtained by the use of unexported struct fields.
// If CanSet returns false, calling Set or any type-specific
// setter (e.g., SetBool, SetInt) will panic.
func (v Value) CanSet() bool

// Set assigns x to the value v.
// It panics if CanSet returns false.
// As in Go, x's value must be assignable to v's type.
func (v Value) Set(x Value)
1
2
3
4
5
6
7
8
9
10
11

# 修改基础类型的值

  1. 尝试修改基础类型的值

    func TestReflect_SetValue(t *testing.T) {
    	var Name = "Tom"
    	fmt.Println("Name before:", Name)
    	val := reflect.ValueOf(Name)
    	val.Set(reflect.ValueOf("Jerry"))
    	fmt.Println("Name after:", Name)
    }
    
    1
    2
    3
    4
    5
    6
    7

    执行程序,发生panic了:

    panic: reflect: reflect.Value.Set using unaddressable value [recovered]
    	panic: reflect: reflect.Value.Set using unaddressable value
    
    1
    2

    这里传给reflect.ValueOf的是string类型,生成的是unaddressable的Value,因此是无法Set的。可以通过CanSet方法判断是否可以Set设置新值。

    func TestReflect_SetValue(t *testing.T) {
    	var Name = "Tom"
    	fmt.Println("Name before:", Name)
    	val := reflect.ValueOf(Name)
    	if !val.CanSet() {
    		fmt.Println("Name不可被修改")
    	} else {
    		val.Set(reflect.ValueOf("Jerry"))
    	}
    	fmt.Println("Name after:", Name)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    打印的结果,变量Name并没有被修改:

    Name before: Tom
    Name不可被修改
    Name after: Tom
    
    1
    2
    3
  2. 传入指针,修改指针的值

    func TestReflect_SetValue(t *testing.T) {
    	var Name = "Tom"
    	fmt.Println("Name before:", Name)
    	val := reflect.ValueOf(&Name)
    	fmt.Println(val.Type().Kind()) // "ptr"
    	if !val.CanSet() {
    		fmt.Println("Name不可被修改")
    	} else {
    		val.Set(reflect.ValueOf("Jerry"))
    	}
    	fmt.Println("Name after:", Name)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    输出的结果,变量Name并没有被修改:

    Name before: Tom
    ptr
    Name不可被修改
    Name after: Tom
    
    1
    2
    3
    4

    因为reflect.ValueOf(&Name)只是reflect.Ptr类型,指向的是一个&Name,稍微修改下代码:

    val := reflect.ValueOf(&Name)
    val = val.Elem()
    fmt.Println(val.Type().Kind()) // "string"
    
    1
    2
    3

    执行后输出的结果,Name的值被修改了:

    Name before: Tom
    string
    Name after: Jerry
    
    1
    2
    3

# 修改结构体的值

跟基础类型一样,也是通过CanSet判断字段是否可被修改,如果可以修改,可以使用Set方法进行赋值。

type ReflectUser struct {
	Name string
	Age  int
	phone string
}

func (r ReflectUser) String() string {
	return fmt.Sprintf("Name: %s Age: %d phone: %s",
		r.Name, r.Age, r.phone)
}
func TestReflect_StructSetValue(t *testing.T) {
	ru := ReflectUser{
		Name:  "Tom",
		Age:   18,
		phone: "666",
	}
	fmt.Println("before:", ru)
	SetField(&ru, "Name", "Jerry")
	SetField(&ru, "Age", 20)
	SetField(&ru, "phone", "888") // unexported 字段不可修改
	fmt.Println("after:", ru)
}

func SetField(entity any, field string, newValue any) {
	val := reflect.ValueOf(entity)
	for val.Type().Kind() == reflect.Pointer {
		val = val.Elem()
	}
	fv := val.FieldByName(field)
	if !fv.CanSet() {
		fmt.Println("为不可修改字段")
		return
	}
	fv.Set(reflect.ValueOf(newValue))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

从执行后的结果看,可导出字段被修改了,不可导出字段没有被修改:

before: Name: Tom Age: 18 phone: 666
phone 为不可修改字段
after: Name: Jerry Age: 20 phone: 666
1
2
3

# 参考

  • 大明的《Go 项目实战》
  • 《Go 语言圣经》 (opens new window)

评论

上次更新: 2023/03/01, 07:47:26
go的测试
Go 数据库操作

← go的测试 Go 数据库操作→

最近更新
01
Golang 逃逸分析
03-22
02
深入理解 channel
03-04
03
深入理解 sync.WaitGroup
03-01
更多文章>
Theme by Vdoing | Copyright © 2018-2023 FeelingLife | 粤ICP备2022093535号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式