Go语言reflect反射包的用法(非常详细)
Go 语言标准库的反射包 reflect 主要有两个函数 reflect.TypeOf() 和 reflect.ValueOf()。与反射相关的任务基本上都是先通过这两个函数来获得反射对象的两大要素 reflect.Type 接口和 reflect.Value 结构体类型,然后再使用反射包中的其他方法继续下一步操作。
以 reflect.Type 接口为例,它提供的 MethodByName() 方法用来获取当前类型对应方法的引用,Implements() 方法用来判断当前类型是否实现了接口。在 Go 语言中,reflect.Value 可以保存任意类型变量的存储结构,因此,可以直接将函数 reflect.ValueOf() 的返回值作为变量值使用。
笔者针对反射包 reflect 中常用的对象和方法进行了归纳总结,如下图所示:

图 1 反射包 reflect 中常用的对象和方法
从图 1 可以看出,从接口值到反射对象需要经历两次转换。第一次是将基本类型转换为接口类型,第二次是将接口类型转换为反射对象。从反射对象到接口值是上述过程的反向过程。
调用函数 reflect.TypeOf() 最后返回的是一个 reflect.Type 接口类型的值,它表示传入值的类型。如果传入的值为 nil 则返回 nil,这是因为 nil 接口值没有具体的动态类型。
关键代码如下:
调用函数 reflect.ValueOf() 返回的值包含了原始值及其类型信息的反射对象。这个函数广泛用于获取和操作运行时的值信息,是 Go 语言反射机制的核心部分。
在源码中,接口类型实例转换为 reflect.Value 结构体类型的过程是先将 i 转换成 *emptynterace 类型,再将它的字段 typ、word 和一个标志位组装成一个 Value 结构体,然后以此作为函数 reflect.ValueOf() 的返回值。关键代码如下:
reflect.New() 函数可以根据给定的类型创建一个新的指针,它适用于需要创建一个指向某个类型零值的指针的场景。reflect.New() 函数返回指向这个新的零值的 reflect.Value 结构体类型。示例代码如下:
reflect.Zero() 函数可以根据给定的类型创建该类型的零值,并返回这个零值的 reflect.Value 结构体类型。这适用于不需要指针,直接需要类型零值的场景。
如果知道类型值的存储地址,则可以用 NewAt() 函数恢复 reflect.Value 结构体类型,示例代码如下:
在 Go 语言的反射库中,Elem() 方法用于获取一个指针、数组、切片、映射和通道的基础(底层)类型,除此之外的其他类型使用 Elem() 方法时会发生 panic。PtrTo() 和 Elem() 方法是反射机制中处理类型信息时使用的互补方法,二者提供了一种在运行时动态探索和变换 Go 类型的能力,这在要动态处理不同类型的数据时特别有用。
示例代码如下:
例如:
例如,如果 v 表示一个字符串,那么尝试调用 v.Int 方法就会产生运行时 panic。因此,在调用这些转换方法前,通常需要检查 reflect.Value 的结构体类型,可以使用 Kind() 方法进行检查。
另外要注意的是,转换时要区分反射的目标(reflect.ValueOf)是指针还是值!对于指针类型的 reflect.Value,需要先调用 Elem() 方法获取指针指向的实际值,然后再进行类型转换。
示例代码如下:
以下代码演示了将较为复杂的结构体类型从 reflect.Value 结构体类型转换为接口类型的过程。
下面基于上面的示例总结一下获取结构体类型的成员类型和成员值,以及结构体类型相关方法的步骤。
获取结构体类型的成员类型和成员值的步骤如下:
获取结构体类型相关方法的步骤如下:
这种将 reflect.Value 结构体类型转换为 reflect.Type 接口的能力在需要动态处理数据类型的情况下非常有用,比如在序列化和反序列化、泛型编程或者编写依赖于类型检查的复杂算法时。
第一种,Elem() 方法用于获取一个指针或接口类型所指向或包含的值的 reflect.Value 结构体类型。其定义如下:
示例代码如下:
第二种,Indirect() 方法是 reflect 包提供的一个函数,如果 reflect.Value 结构体类型是指针类型,可用该函数获取其所指向的值类型,其定义如下:
首先,定义几个结构体并初始化一个全局的结构体变量,示例代码如下:
以上就是动态调用方法和传值的过程。因为函数还是一种数据类型,所以当以函数作为变量时,也可以使用反射进行操作。关键代码如下:
首先,使用 reflect.ValueOf() 函数获取反射对象的 reflect.Value 结构体类型。其次,在修改接口值前,确保反射对象是可写的(settable)。这通常意味着原始变量应当是通过指针传递的。如果要修改的是结构体中的字段,可以使用 v.Elem().FieldByName("xxx") 来获取该字段的反射对象。最后,使用 Set、SetInt、SetString、SetBool 等方法来修改字段的值。
示例代码如下:
这里总结一下修改接口值时的注意事项:
如果要修改反射类型对象,其值必须是可写的(settable),这是反射的第三定律。
示例代码如下:
获取结构体的反射类型可以直接使用 reflect.Type 接口,但是要获取接口的类型就需要使用 reflect.TypeOf((*<interface>)(nil)).Elem 方法了。
下面这段代码演示了利用反射来判断结构体是否实现了接口的方法:
以 reflect.Type 接口为例,它提供的 MethodByName() 方法用来获取当前类型对应方法的引用,Implements() 方法用来判断当前类型是否实现了接口。在 Go 语言中,reflect.Value 可以保存任意类型变量的存储结构,因此,可以直接将函数 reflect.ValueOf() 的返回值作为变量值使用。
笔者针对反射包 reflect 中常用的对象和方法进行了归纳总结,如下图所示:

图 1 反射包 reflect 中常用的对象和方法
从图 1 可以看出,从接口值到反射对象需要经历两次转换。第一次是将基本类型转换为接口类型,第二次是将接口类型转换为反射对象。从反射对象到接口值是上述过程的反向过程。
Go语言反射对象的转换机制
前面已提到,反射对象的两大要素为 reflect.Type 接口和 reflect.Value 结构体类型,接下来看看与之相关的转换机制。1) 将任意值转换为reflect.Type接口类型的值
函数 reflect.TypeOf() 用于动态获取任意值的类型信息,而不仅限于接口类型的值。这个函数接收 interface{} 类型的参数,可以接收任意类型的值,因为在 Go语言中,任何值都可以被视为 interface{} 类型。调用函数 reflect.TypeOf() 最后返回的是一个 reflect.Type 接口类型的值,它表示传入值的类型。如果传入的值为 nil 则返回 nil,这是因为 nil 接口值没有具体的动态类型。
关键代码如下:
func TypeOf(i interface{}) Type { eface := *(*emptyInterface)(unsafe .Pointer(&i)) return toType(eface .typ) } type emptyInterface struct { typ *rtype word unsafe .Pointer }上述代码中的 emptyInterface 就是 Go 源码 runtime 2.go 中空接口的定义 eface,eface.typ 则是动态类型,返回值的类型是 reflect.Type 接口类型,其具体用法可以参考官方文档。
2) 将任意值转换为reflect.Value结构体类型的值
函数 reflect.ValueOf() 用于获取任何值的类型信息,与函数 reflect.TypeOf() 一样,如果传入的值为 nil 则返回 nil。调用函数 reflect.ValueOf() 返回的值包含了原始值及其类型信息的反射对象。这个函数广泛用于获取和操作运行时的值信息,是 Go 语言反射机制的核心部分。
在源码中,接口类型实例转换为 reflect.Value 结构体类型的过程是先将 i 转换成 *emptynterace 类型,再将它的字段 typ、word 和一个标志位组装成一个 Value 结构体,然后以此作为函数 reflect.ValueOf() 的返回值。关键代码如下:
func ValueOf(i interface{}) Va与ue { if i == nil { return Value{} } ... return unpackEface(i) } func unpackEface(i interface{}) Value { e := (*empt Interface)(unsafe.Pointer(&i)) t := e.typ f := flag(t.Kind()) ... return Value{t, e.word, f} }
Go语言reflect.Type接口的转换方式
从图 1 中可以看到 reflect.Type 接口的转换方式有两种:- 第一种是将 reflect.Type 接口转换为 reflect.Value 结构体类型;
- 第二种是值类型 reflect.Type 接口与指针类型 reflect.Type 接口互转。
1) 将reflect.Type接口转换为reflect.Value结构体类型
我们无法直接将 reflect.Type 接口转换为 reflect.Value 结构体类型,这是因为 reflect.Type 接口中仅有类型信息,没有具体的值信息。办法是通过 reflect.Type 接口构建新的接口类型实例,然后为其赋予零值并返回。reflect.New() 函数可以根据给定的类型创建一个新的指针,它适用于需要创建一个指向某个类型零值的指针的场景。reflect.New() 函数返回指向这个新的零值的 reflect.Value 结构体类型。示例代码如下:
func TestTypeNew(s *testing.T) { t := reflect.TypeOf(1024) // 获取int的reflect.Type接口 v := reflect.New(t) // 创建 *int的reflect.Value结构体类型(指向int的零值) fmt.Println(v.Elem().Int()) // 输出0,因为它是int的零值 }
reflect.Zero() 函数可以根据给定的类型创建该类型的零值,并返回这个零值的 reflect.Value 结构体类型。这适用于不需要指针,直接需要类型零值的场景。
如果知道类型值的存储地址,则可以用 NewAt() 函数恢复 reflect.Value 结构体类型,示例代码如下:
var x float64 = 3.14 t := reflect.TypeOf(x) // 获取x的类型信息 ptr := unsafe.Pointer(&x) // 获取x的内存地址 v := reflect.NewAt(t, ptr) // 使用NewAt根据类型和内存地址恢复reflect.Value结构体类型 fmt.Println("Value:", v.Elem().Float()) // 使用Elem获取Value指向的实际值,输出3.14在上面的示例中:
- 定义了一个 float64 类型的变量 x;
- 使用函数 reflect.TypeOf(x) 获取变量 x 的类型信息;
- 使用函数 unsafe.Pointer(&x) 获取变量 x 的内存地址;
- 调用函数 reflect.NewAt(t, ptr) 创建一个 reflect.Value 结构体类型,这个 reflect.Value 结构体类型代表了存储在变量 x 中的内存地址的值;
- 最后,通过 v.Elem().Float() 获取并输出这个 reflect.Value 结构体类型所代表的值。
2) 值类型reflect.Type接口与指针类型reflect.Type接口互转
将值类型 reflect.Type 接口转换为指针类型 reflect.Type 接口时使用 PtrTo() 方法,将指针类型 reflect.Type 接口转换为值类型 reflect.Type 接口时使用 Elem() 方法。在 Go 语言的反射库中,Elem() 方法用于获取一个指针、数组、切片、映射和通道的基础(底层)类型,除此之外的其他类型使用 Elem() 方法时会发生 panic。PtrTo() 和 Elem() 方法是反射机制中处理类型信息时使用的互补方法,二者提供了一种在运行时动态探索和变换 Go 类型的能力,这在要动态处理不同类型的数据时特别有用。
示例代码如下:
// reflect.Type接口 的elem方法说明 func TestTypeElem(t *testing .T) { s := 1 var intPtr = &s mySlice := []string{"Oracle", "MySQL", "PgSQL"} myMap := map[string]int{"Java": 1, "Go": 2} myArray := [ . . .]string{} var myChan chan int = make(chan int) intPtrKind := reflect .TypeOf(intPtr) .Elem() mySliceKind := reflect .TypeOf(mySlice) .Elem() myMapKind := reflect .TypeOf(myMap) .Elem() myArrayKind := reflect .TypeOf(myArray) .Elem() myChanKind := reflect .TypeOf(myChan) .Elem() fmt .Printf("intPtr .Elem():%s\n", intPtrKind)//输出: intPtr .Elem():int fmt .Printf("mySlice .Elem():%s\n", mySliceKind)//输出:mySlice .Elem():string fmt .Printf("myMap .Elem():%s\n", myMapKind)//输出: myMap .Elem():int fmt .Printf("myArray .Elem():%s\n", myArrayKind)//输出:myArray .Elem():string fmt .Printf("myChan .Elem():%s\n", myChanKind)//输出: myChan .Elem():int }从上述代码的输出结果中可以看到如下信息:
- 对于指针类型,使用 Elem() 方法会得到指针指向的数据的类型;
- 对于数组和切片类型,使用 Elem() 方法会得到它存储的元素的类型;
- 对于映射类型,使用 Elem() 方法会得到它们存储的值的类型,而不是键的类型;
- 对于通道类型,使用 Elem() 方法会得到通道可以存储的数据的类型。
Go语言reflect.Value结构体的使用方法
下面来看看 reflect.Value 结构体类型是如何转换为原始的接口类型和获取 reflect.Type 接口的。1) 转换为原始的接口类型
reflect.Value 结构体类型本身就包含类型和值信息,因此能很轻松地转换为接口类型。示例代码如下:func main() { //结构体的反射 v := MyStruct{} value := reflect .ValueOf(v) fmt .Printf("Kind : %s , Type : %s\n", value .Kind(), value .Type()) } type MyStruct struct {} //输出: Kind : struct , Type : main .MyStruct
2) 将已知的原有类型转换为具体类型
reflect.Value 结构体提供了一系列方法可直接将 reflect.Value 结构体类型转换为 Go 语言中的具体类型,例如 Int、Uint、Float、Bool 等。这些方法对应着各种基本数据类型,它们允许我们从 reflect.Value 结构体类型中提取其表示的原始值,前提是这个值确实是对应的类型。例如:
v := reflect .ValueOf(42) // v是一个reflect.Value结构体类型 i := v .Int() // 使用Int方法将v转换为int64类型,i的值为42但是这些转换方法只在 reflect.Value 结构体类型表示的值与方法类型匹配时才可以使用,如果两者不匹配,运行时会产生 panic。
例如,如果 v 表示一个字符串,那么尝试调用 v.Int 方法就会产生运行时 panic。因此,在调用这些转换方法前,通常需要检查 reflect.Value 的结构体类型,可以使用 Kind() 方法进行检查。
另外要注意的是,转换时要区分反射的目标(reflect.ValueOf)是指针还是值!对于指针类型的 reflect.Value,需要先调用 Elem() 方法获取指针指向的实际值,然后再进行类型转换。
示例代码如下:
var x float64 = 3.14 v := reflect.ValueOf(x) // 正确的使用方式 if v.Kind() == reflect.Float64 { fmt.Println("Float value:", v.Float()) } // 错误的使用方式会导致产生panic // fmt.Println("Int value:", v.Int()) // 处理指针类型 pv := reflect.ValueOf(&x) if pv.Kind() == reflect.Ptr && pv.Elem().Kind() == reflect.Float64 { fmt.Println("Float value:", pv.Elem().Float()) }
3) 原有类型未知,进行探索式转换
很多情况下,我们是不知道原有类型的。此时,需要使用 Interface 方法进行探索式转换。以下代码演示了将较为复杂的结构体类型从 reflect.Value 结构体类型转换为接口类型的过程。
//接受一个interface{}类型的参数obj,它可以是Go语言中任意类型的值。它使用反射来获取并打印有关参数obj的信息 func GetObjInfo(obj interface{}) { getType := reflect .TypeOf(obj) fmt .Println("获取的类型为 :", getType .Name()) getValue := reflect .ValueOf(obj) fmt .Println("获取的值为:", getValue) //是struct类型时才继续获取参数obj的字段和方法 if getType .Kind()!=reflect .Struct{ return } // 获取方法字段 // 先获取interface的reflect.Type接口,然后通过NumField进行遍历 // 再获取reflect.Type接口的Field // 最后通过Field的Interface方法得到对应的value for i := 0; i < getType .NumField(); i++ { field := getType .Field(i) value := getValue .Field(i) .Interface() fmt .Printf("%s: %v = %v\n", field .Name, field .Type, value) } // 获取方法 // 先获取interface的reflect.Type接口,然后通过NumMethod进行遍历 for i := 0; i < getType .NumMethod(); i++ { m := getType .Method(i) fmt .Printf("%s: %v\n", m .Name, m .Type) } } type MyFloat float64 //定义一个结构体Database type DataBase struct { DbName string DbType string DbIndex int } // 测试使用Value获取原始的类型对象 func TestValue2Object(t *testing .T) { var MyDatabase = DataBase{ DbName: "Oracle", DbType: "rdbms", DbIndex: 0, } GetObjInfo(MyDatabase) fmt .Println("----") var i MyFloat = 6.4 GetObjInfo(i) fmt .Println("----") GetObjInfo(1) }输出结果为:
获取的类型为 : DataBase
获取的值为 : {Oracle rdbms 0}
DbName: string = Oracle
DbType: string = rdbms
DbIndex: int = 0
ToString: func(__reflect .DataBase, . . .string)
----
获取的类型为 : MyFloat
获取的值为 : 6.4
----
获取的类型为 : int
获取的值为 : 1
下面基于上面的示例总结一下获取结构体类型的成员类型和成员值,以及结构体类型相关方法的步骤。
获取结构体类型的成员类型和成员值的步骤如下:
- 获取接口变量类型 reflect.Type.kind,判断其是否为结构体类型;
- 如果是结构体类型,则通过NumField方法开始遍历;
- 在遍历过程中使用 reflect.Type.Field() 方法获取其成员信息,使用 Field.Interface() 方法获取对应的 value。
获取结构体类型相关方法的步骤如下:
- 获取接口变量类型 reflect.Type.kind,判断其是否为结构体类型。
- 如果是结构体类型,通过 NumMethod() 方法开始遍历。
- 在遍历过程中使用 reflect.Type.Method() 方法获取对应的真实方法(函数)。
- 当通过 reflect.Type 接口获取一个结构体的方法信息时,每个方法都表示为一个 reflect.Method 类型。这个 reflect.Method 类型包含了关于方法的多个字段,包括但不限于方法的名称(Name)和类型(Type)等。
4) 将reflect.Value结构体类型转换为reflect.Type接口
因为每个 reflect.Value 结构体类型内部都包含一个指向对应类型信息的指针,所以可以直接调用方法 func(v Value) Type() Type 将 reflect.Value 结构体类型转换为对应的 reflect.Type 接口。这个 func(v Value) Type() Type 方法返回一个 reflect.Type 接口实例,它表示 reflect.Value 结构体类型所持有值的类型。这种将 reflect.Value 结构体类型转换为 reflect.Type 接口的能力在需要动态处理数据类型的情况下非常有用,比如在序列化和反序列化、泛型编程或者编写依赖于类型检查的复杂算法时。
5) 如果reflect.Value结构体类型是指针类型,将其转换为值类型
在 Go 语言的反射库中,如果 reflect.Value 结构体类型是指针类型,有两种方法可以将其转换为值类型。第一种,Elem() 方法用于获取一个指针或接口类型所指向或包含的值的 reflect.Value 结构体类型。其定义如下:
func (v Value) Elem() Value
- 如果 v 是 nil,函数返回零值。如果 v 是接口类型,则返回接口绑定的动态值的 reflect.Value 结构体类型;
- 如果 v 是指针类型,则返回这个指针指向的值的 reflect.Value 结构体类型;
- 如果 v 不是接口类型或指针类型,调用函数则会产生 panic。
示例代码如下:
var x int = 10 v := reflect.ValueOf(&x) value := v.Elem() // value是指向x的指针所指向的int值的reflect.Value结构体类型
第二种,Indirect() 方法是 reflect 包提供的一个函数,如果 reflect.Value 结构体类型是指针类型,可用该函数获取其所指向的值类型,其定义如下:
func Indirect(v Value) Value如果 v 是指针类型,函数返回指针值的 Value,否则返回 v 本身。示例代码如下:
var x int = 10 v := reflect.ValueOf(&x) value := reflect.Indirect(v) // value是指向x的指针所指向的int值的reflect.Value结构体类型使用 Elem() 方法需要先确保 reflect.Value 结构体类型是指针类型,否则可能会引发 panic。而使用 Indirect() 方法时不需要进行这样的前置检查,因为它在处理非指针类型时只是简单地返回原始的 reflect.Value 结构体类型。
Go语言reflect反射包的使用示例
reflect 反射提供了一种强大的机制以在运行时探索类型的结构和行为,接下来将演示反射包reflect中常用函数的使用示例。首先,定义几个结构体并初始化一个全局的结构体变量,示例代码如下:
//定义一个结构体Database type DataBase struct { DbName string DbType string DbIndex int } //定义Database这个结构体的ToString方法 func (db DataBase) ToString(args . . .string) { fmt .Printf("par=%s, DbName=%s, DbType=%s , DbIndex=%d\n", args, db .DbName, db .DbType, db .DbIndex) } //定义一个结构体Storage type Storage struct { StorageType string `json:"name" bson:"Naming"` StorageSize float32 `json:"size" bson:"BigSize"` } //设置一个全局变量 var MyDatabase = DataBase{ DbName: "Oracle", DbType: "rdbms", DbIndex: 0, }
1) 获取变量的类型和值
接下来使用 reflect.TypeOf() 和 reflect.ValueOf() 函数获取变量的类型和值,以及传入接口的值的底层类型,示例代码如下:// 反射的简单使用 func TestReflectBasicUse(test *testing .T) { //reflect .Value转换成了原来的对象 obj_db := reflect .ValueOf(MyDataBase) obj := obj_db .Interface() . (DataBase) fmt .Printf("db的类型是%T:,值是%v\n", MyDataBase, MyDataBase) fmt .Printf("obj_db的类型是%T:,值是%v\n", obj_db, obj_db) fmt .Printf("obj的类型是%T:,值是%v\n", obj, obj) //%v+reflect .TypeOf(db) 等价于 %t+db fmt.Printf("db的类型是%v:,值是%v\n", reflect.TypeOf(MyDataBase), reflect.ValueOf (MyDataBase)) //获取传入接口的底层原始数据结构 //底层数据结构的种类可以参考type .go的const fmt .Println("底层的数据类型是", obj_db .Type() .Kind()) }输出结果为:
db的类型是__reflect .DataBase:,值是{Oracle rdbms 0}
obj_db的类型是reflect .Value:,值是{Oracle rdbms 0}
obj的类型是__reflect .DataBase:,值是{Oracle rdbms 0}
db的类型是__reflect .DataBase:,值是{Oracle rdbms 0}
底层的数据类型是struct
- 使用 obj_db := reflect.ValueOf(MyDataBase) 获取 MyDataBase 的 Value;
- 如果 Value 中的 Interface() 方法返回的是空接口类型 interface{},那么还可以使用 obj := obj_db. Interface().(DataBase) 进行类型断言。这样一来,又可重新获取 MyDataBase 对象;
- 使用 reflect.ValueOf(MyDataBase).Type().Kind() 获取传入接口的值的底层类型。此时,返回的 type 是 Go 语言中内置的基础类型(包括数值型、字符串、通道和切片等)。
2) 获取结构体的属性和方法
接下来看看获取结构体的属性和方法的示例,代码如下:// 获取结构体的属性和方法 func TestGetStructPropsAndMethod(test *testing .T) { t := reflect .TypeOf(MyDatabase) for i := 0; i < t .NumField(); i++ { f := t .Field(i) fmt .Printf("fieldIndex: %d, fieldName: %s\n", f .Index, f .Name) } for i := 0; i < t .NumMethod(); i++ { m := t .Method(i) fmt .Printf("methodIndex: %d, methodName: %s\n", m .Index, m .Name) } }输出结果为:
fieldIndex: [0], fieldName: DbName fieldIndex: [1], fieldName: DbType fieldIndex: [2], fieldName: DbIndex methodIndex: 0, methodName: ToString上述代码主要通过 reflect.TypeOf(MyDatabase) 的方法 NumField 和方法 NumMethod 分别获取属性和方法。
3) 动态调用方法和传值
接下来演示如何动态调用方法和传值,示例代码如下:func TestDynamicCallMethod(test *testing .T) { v := reflect .ValueOf(MyDataBase) methods := v .MethodByName("ToString") if methods .IsValid() { args := []reflect .Value{reflect .ValueOf("参数1"), reflect .ValueOf("参数2"), reflect .ValueOf("参数3")} fmt.Println(methods.Call(args))//输出:par=[参数1参数2参数3], DbName=Oracle, DbType=rdbms , DbIndex=0 } }对于上述代码,有以下几点需要注意:
- 使用 reflect.ValueOf.MethodByName 时需要指定明确的方法名。
- 在调用指定的方法名之前,可以使用 methods.IsValid 检查此方法名是否存在。
- 因为 Call() 方法的定义为 func (v Value) Call(in []Value) []Value,所以传入参数的类型是 []Value,这里可以使用 reflect.ValueOf() 函数将参数转换为 Value 类型。
- Call() 方法最终会调用真实的方法,传入的参数务必与真实方法的参数保持一致。如果 reflect. Value.Kind 不是一个方法,那么将直接产生 panic。
以上就是动态调用方法和传值的过程。因为函数还是一种数据类型,所以当以函数作为变量时,也可以使用反射进行操作。关键代码如下:
func fun1(){} func fun2(i int, s string){} value1 := reflect .ValueOf(fun1) value2 := reflect .ValueOf(fun2) value1 .Call(nil) value2 .Call([]reflect .Value{reflect .ValueOf(100),reflect .ValueOf("hello")})
4) 修改接口值
我们可以通过反射机制来修改接口值。首先,使用 reflect.ValueOf() 函数获取反射对象的 reflect.Value 结构体类型。其次,在修改接口值前,确保反射对象是可写的(settable)。这通常意味着原始变量应当是通过指针传递的。如果要修改的是结构体中的字段,可以使用 v.Elem().FieldByName("xxx") 来获取该字段的反射对象。最后,使用 Set、SetInt、SetString、SetBool 等方法来修改字段的值。
示例代码如下:
//通过反射机制修改接口值 //修改的原理是先获取reflect.Value结构体类型 //再通过v.Elem().FieldByName("xxx")来获取该字段的反射对象 //最后使用Set、SetInt、SetString、 SetBool等方法来修改字段的值 func TestModifyValueByReflect(test *testing .T) { //注意,这里传入的是指针 myMySQL := &MyDataBase fmt .Println(myMySQL)//输出: &{Oracle rdbms 0} v := reflect .ValueOf(myMySQL) .Elem() v .FieldByName("DbName") .Set(reflect .ValueOf("MySQL")) fmt .Println(myMySQL)//输出: &{MySQL rdbms 0} fmt .Println(MyDataBase)//输出: {MySQL rdbms 0} }对于上述代码,需要注意的内容如下:
- 结构体首字母必须大写,否则会出现“panic: reflect: reflect.Value.Set using value obtained using unexported field”。这一点遵循了 Go 语言中的导出规则,即首字母小写则包外无法访问。
- 反射需要使用类型的指针 &i,否则会出现“panic: reflect: reflect.Value.Set using unaddressable value”。这一点也与前面提到的指针语义相吻合。
- 上述代码演示的是对结构体的修改,事实上,修改映射、切片、通道和基本类型时的方法与之类似。
这里总结一下修改接口值时的注意事项:
- 想要修改接口值,传入 reflect.ValueOf() 函数的参数 v 必须是指针类型变量。可以使用 v.Elem 方法获取指针指向的元素;
- 即使传入的参数 v 是一个指针,也需要通过 v. Elem().CanSet 方法判断其指向的值是否可设置。只有当 CanSet 返回 true 时,接口值才可被修改;
- reflect.Value.Elem 方法用于获取原始值对应的反射类型对象。只有原始对象才能被修改,当前反射类型的对象是不能修改的,直接修改它会发生 panic;
- 结构体及其嵌套字段的处理方式与普通变量相同。如果要修改结构体的字段,这些字段必须是可导出的(即首字母大写);
- 可取址函数 CanAddr() 和可赋值函数 CanSet() 不完全等价。两者的主要区别在于如何处理不可被导出的结构体成员。前者表示是否可以获取当前反射值的地址,后者表示反射值是否可以被修改。利用反射机制可以读取结构体中不可导出的成员,但不能修改其值。
如果要修改反射类型对象,其值必须是可写的(settable),这是反射的第三定律。
5) 判断结构体实现了哪个接口
除了类型断言和编译器自检,判断类型是否实现了接口还可以使用反射包提供的 reflect.TypeOf.Implements() 函数。示例代码如下:
// implements reports whether the type V implements the interface type T . func implements(T, V *rtype) bool分析函数 implements 的算法,会发现它的算法时间复杂度是 O(m+n)而不是 O(m×n),这与通过接口中的 getitab 方法判断类型是否实现了接口的算法类似。
获取结构体的反射类型可以直接使用 reflect.Type 接口,但是要获取接口的类型就需要使用 reflect.TypeOf((*<interface>)(nil)).Elem 方法了。
下面这段代码演示了利用反射来判断结构体是否实现了接口的方法:
type coder interface { coding() } type Person struct {} func (p *Person) coding() {} func StructIsImplInterface(o interface{}, t reflect .Type) bool { obj := reflect .TypeOf(o) if obj .Implements(t) { return true } return false } //测试结构体是否实现了接口 func TestStructIsImplInterface(t *testing .T) { typeOfCoder := reflect .TypeOf((*coder)(nil)) .Elem() var person Person = Person{} fmt .Println(StructIsImplInterface(person, typeOfCoder))//输出: false fmt .Println(StructIsImplInterface(&person, typeOfCoder))//输出: true }