主要内容转载自:mp.weixin.qq.com/s/9OjvKLEei… (为方便个人翻阅),也加了一些个人的理解和内容。
0. 引言
反射是编程语言的高级特性,允许程序在运行时访问变量的类型信息和值信息,如:类型名、类型有哪些方法、结构体有哪些字段、函数/方法的入参和出参、修改变量值、调用变量的方法等等。
反射使得程序具有更大的灵活性和可扩展性,但也有缺点:性能问题、代码可读性和可维护性问题。
1. Go语言反射基础
Go语言内置的reflect包是所有Go反射编程的基础API,是进行Go反射编程的必经之路。
1.1 Type和Value
在reflect包中,Type和Value是两个非常重要的概念,它们分别表示了反射世界中的类型信息和值信息。
Type表示一个类型的元信息,它包含了类型的名称、大小、方法集合等信息。在反射编程中,我们可以使用TypeOf函数来获取一个值的类型信息。
Value表示一个值的信息,它包含了值的类型、值本身以及对值进行操作的方法集合等信息。在反射中,我们可以使用ValueOf函数来获取一个值的Value信息。
reflect包的TypeOf和ValueOf两个函数是进入反射世界的基本入口。
1.2 获取类型信息(TypeOf)
TypeOf函数的签名如下:
func TypeOf(i any) Type
示例:
func main() {
s := "hello, world!"
t := reflect.TypeOf(s)
fmt.Println(t.Name()) // string
}
1.3 获取值信息(ValueOf)
func ValueOf(i any) Value
示例:
func main() {
i := 42
v := reflect.ValueOf(i)
fmt.Println(v.Int()) // 42
}
2.获取类型信息详解
reflect.Type实质上是一个接口类型,它封装了reflect可以提供的类型信息的所有方法(Go 1.20版本中的reflect.Type):
// $GOROOT/src/reflect/type.go
type Type interface {
// Methods applicable to all types.
// Align returns the alignment in bytes of a value of
// this type when allocated in memory.
Align() int
// FieldAlign returns the alignment in bytes of a value of
// this type when used as a field in a struct.
FieldAlign() int
// Method returns the i'th method in the type's method set.
// It panics if i is not in the range [0, NumMethod()).
//
// For a non-interface type T or *T, the returned Method's Type and Func
// fields describe a function whose first argument is the receiver,
// and only exported methods are accessible.
//
// For an interface type, the returned Method's Type field gives the
// method signature, without a receiver, and the Func field is nil.
//
// Methods are sorted in lexicographic order.
Method(int) Method
// MethodByName returns the method with that name in the type's
// method set and a boolean indicating if the method was found.
//
// For a non-interface type T or *T, the returned Method's Type and Func
// fields describe a function whose first argument is the receiver.
//
// For an interface type, the returned Method's Type field gives the
// method signature, without a receiver, and the Func field is nil.
MethodByName(string) (Method, bool)
// NumMethod returns the number of methods accessible using Method.
//
// For a non-interface type, it returns the number of exported methods.
//
// For an interface type, it returns the number of exported and unexported methods.
NumMethod() int
// Name returns the type's name within its package for a defined type.
// For other (non-defined) types it returns the empty string.
Name() string
// PkgPath returns a defined type's package path, that is, the import path
// that uniquely identifies the package, such as "encoding/base64".
// If the type was predeclared (string, error) or not defined (*T, struct{},
// []int, or A where A is an alias for a non-defined type), the package path
// will be the empty string.
PkgPath() string
// Size returns the number of bytes needed to store
// a value of the given type; it is analogous to unsafe.Sizeof.
Size() uintptr
// String returns a string representation of the type.
// The string representation may use shortened package names
// (e.g., base64 instead of "encoding/base64") and is not
// guaranteed to be unique among types. To test for type identity,
// compare the Types directly.
String() string
// Kind returns the specific kind of this type.
Kind() Kind
// Implements reports whether the type implements the interface type u.
Implements(u Type) bool
// AssignableTo reports whether a value of the type is assignable to type u.
AssignableTo(u Type) bool
// ConvertibleTo reports whether a value of the type is convertible to type u.
// Even if ConvertibleTo returns true, the conversion may still panic.
// For example, a slice of type []T is convertible to *[N]T,
// but the conversion will panic if its length is less than N.
ConvertibleTo(u Type) bool
// Comparable reports whether values of this type are comparable.
// Even if Comparable returns true, the comparison may still panic.
// For example, values of interface type are comparable,
// but the comparison will panic if their dynamic type is not comparable.
Comparable() bool
// Methods applicable only to some types, depending on Kind.
// The methods allowed for each kind are:
//
// Int*, Uint*, Float*, Complex*: Bits
// Array: Elem, Len
// Chan: ChanDir, Elem
// Func: In, NumIn, Out, NumOut, IsVariadic.
// Map: Key, Elem
// Pointer: Elem
// Slice: Elem
// Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField
// Bits returns the size of the type in bits.
// It panics if the type's Kind is not one of the
// sized or unsized Int, Uint, Float, or Complex kinds.
Bits() int
// ChanDir returns a channel type's direction.
// It panics if the type's Kind is not Chan.
ChanDir() ChanDir
// IsVariadic reports whether a function type's final input parameter
// is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's
// implicit actual type []T.
//
// For concreteness, if t represents func(x int, y ... float64), then
//
// t.NumIn() == 2
// t.In(0) is the reflect.Type for "int"
// t.In(1) is the reflect.Type for "[]float64"
// t.IsVariadic() == true
//
// IsVariadic panics if the type's Kind is not Func.
IsVariadic() bool
// 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 returns the struct field with a name
// that satisfies the match function and a boolean indicating if
// the field was found.
//
// FieldByNameFunc considers the fields in the struct itself
// and then the fields in any embedded structs, in breadth first order,
// stopping at the shallowest nesting depth containing one or more
// fields satisfying the match function. If multiple fields at that depth
// satisfy the match function, they cancel each other
// and FieldByNameFunc returns no match.
// This behavior mirrors Go's handling of name lookup in
// structs containing embedded fields.
FieldByNameFunc(match func(string) bool) (StructField, bool)
// In returns the type of a function type's i'th input parameter.
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumIn()).
In(i int) Type
// Key returns a map type's key type.
// It panics if the type's Kind is not Map.
Key() Type
// Len returns an array type's length.
// It panics if the type's Kind is not Array.
Len() int
// NumField returns a struct type's field count.
// It panics if the type's Kind is not Struct.
NumField() int
// NumIn returns a function type's input parameter count.
// It panics if the type's Kind is not Func.
NumIn() int
// NumOut returns a function type's output parameter count.
// It panics if the type's Kind is not Func.
NumOut() int
// Out returns the type of a function type's i'th output parameter.
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumOut()).
Out(i int) Type
common() *rtype
uncommon() *uncommonType
}
我们看到这是一个“超级接口”,严格来说并不符合Go接口设计的惯例。
注:Go崇尚小接口。以Type接口为例,可以对Type接口做进一步分解,分解成若干内聚的小接口,然后将Type看成小接口的组合。
对于不同类型,Type接口的有些方法是冗余的,比如像上面的NumField、NumIn和NumOut方法对于一个int变量的类型信息来说就毫无意义。Type类型的注释中也提到:“Not all methods apply to all kinds of types”。
一旦通过TypeOf进入反射世界,拿到Type类型变量,那么我们就可以基于上述方法“翻看”类型的各种信息了。
对于像int、float64、string这样的基本类型来说,其类型信息的检视没有太多可说的。但对于其他类型,诸如复合类型、指针类型、函数类型等,还是有一些可聊聊的,我们下面逐一简单地看一下。
2.1 复合类型
2.1.1 数组类型
在Go中,数组类型是一种典型的复合类型,它有若干属性,包括数组长度、数组是否支持可比较、数组元素的类型等,看下面示例:
import (
"fmt"
"reflect"
)
func main() {
arr := [5]int{1, 2, 3, 4, 5}
typ := reflect.TypeOf(arr)
fmt.Println(typ.Kind()) // array
fmt.Println(typ.Len()) // 5
fmt.Println(typ.Comparable()) // true
elemTyp := typ.Elem()
fmt.Println(elemTyp.Kind()) // int
fmt.Println(elemTyp.Comparable()) // true
}
注:通过类型信息无法间接得到值信息,反之不然,稍后系统说明reflect.Value时会提到。
Kind是什么
在上面例子,我们输出了arr这个数组类型变量的Kind信息。什么是Kind信息呢?reflect包中是如此定义的:
// 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
)
Type是具体类型,而Kind是类型的种类。比如:切片类型具体可以是int
切片、string
切片......
以两个数组类型为例:
var arr1 [10]string
var arr2 [8]int
这两个数组类型的类型分别是[10]string和[8]int,但它们在反射世界的reflect.Type的Kind信息却都为Array。
再比如下面两个指针类型:
var p1 *float64
var p2 *MyFoo
这两个指针类型的类型分别是*float64
和*MyFoo
,但它们在反射世界的reflect.Type的Kind信息却都为Pointer
。
Kind信息可以帮助开发人员在反射世界中区分类型,以对不同类型作不同的处理。比如对于Kind为Int的reflect.Type,你不能使用其Len()方法,否则会panic;但对于Kind为Array的则可以。开发人员使用反射提供的Kind信息可以处理不同类型的数据。
2.1.2 切片类型
在Go中切片是动态数组,可灵活、透明的扩容,多数情况下切片都能替代数组完成任务。在反射世界中通过reflect.Type我们可以获取切片类型的信息,包括元素类型等。下面是一个示例:
func main() {
s := make([]int, 5, 10)
typ := reflect.TypeOf(s)
fmt.Println(typ.Kind()) // slice
fmt.Println(typ.Elem()) // int
}
如果我们使用上面的变量typ调用Type类型的Len和Cap方法会发生什么呢?在运行时,你将得到类似"panic: reflect: Len of non-array type []int"的报错!
那么问题来了!切片长度、容量到底是否是slice type的信息范畴呢? 我们来看一个例子:
var a = make([]int, 5, 10)
var b = make([]int, 7, 8)
变量a和b的类型都是[]int。显然长度、容量等并不在切片类型的范畴,而是与切片变量值绑定的,下面的示例印证了这一点:
func main() {
s := make([]int, 5, 10)
val := reflect.ValueOf(s)
fmt.Println(val.Len()) // 5
fmt.Println(val.Cap()) // 10
}
我们获取了切片变量s的reflect.Value信息,通过Value我们得到了变量s的长度和容量信息。
注意:切片的长度和容量属于值信息。
2.1.3 结构体类型
结构体类型是与反射联合使用的重要类型,下面代码展示了如何通过reflect.Type获取结构体类型的相关信息:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
gender string
}
func (p Person) SayHello() {
fmt.Printf("Hello, my name is %s, and I'm %d years old.\n", p.Name, p.Age)
}
func (p Person) unexportedMethod() {
}
func main() {
p := Person{Name: "Tom", Age: 20, gender: "male"}
typ := reflect.TypeOf(p)
fmt.Println(typ.Kind()) // struct
fmt.Println(typ.NumField()) // 2
fmt.Println(typ.Field(0).Name) // Name
fmt.Println(typ.Field(0).Type) // string
fmt.Println(typ.Field(0).Tag) // json:"name"
fmt.Println(typ.Field(1).Name) // Age
fmt.Println(typ.Field(1).Type) // int
fmt.Println(typ.Field(1).Tag) // json:"age"
fmt.Println(typ.Field(2).Name) // gender
fmt.Println(typ.Method(0).Name) // SayHello
fmt.Println(typ.Method(0).Type) // func(main.Person)
fmt.Println(typ.Method(0).Func) // 0x109b6e0
fmt.Println(typ.MethodByName("SayHello")) // {SayHello func(main.Person)}
fmt.Println(typ.MethodByName("unexportedMethod")) // { <nil> <invalid Value> 0} false
}
从上面例子可以看到,我们可以使用NumField、Field、NumMethod、Method和MethodByName等方法获取结构体的字段信息和方法信息。其中,Field方法返回的是StructField
类型的值,包含了字段的名称、类型、标签等信息;Method方法返回的是Method
类型的值,包含了方法的名称、类型和函数值等信息。
不过要注意:通过Type可以得到结构体中非导出字段的信息(如上面示例中的gender),但无法获取结构体类型的非导出方法信息(如上面示例中的unexportedMethod)。
2.1.4 channel类型
channel是Go特有的类型,channel与切片很像,它的类型信息包括元素类型、chan读写特性,但channel的长度与容量与channel变量是绑定的,看下面示例:
func main() {
ch := make(chan<- int, 10)
ch <- 1
ch <- 2
typ := reflect.TypeOf(ch)
fmt.Println(typ.Kind()) // chan
fmt.Println(typ.Elem()) // int
fmt.Println(typ.ChanDir()) // chan<-
fmt.Println(reflect.ValueOf(ch).Len()) // 2
fmt.Println(reflect.ValueOf(ch).Cap()) // 10
}
2.1.5 map类型
map是go常用的内置的复合类型,它是一个无序键值对的集合,通过反射可以获取其键和值的类型信息:
func main() {
m := map[string]int{"a": 1, "b": 2, "c": 3}
typ := reflect.TypeOf(m)
fmt.Println(typ.Kind()) // map
fmt.Println(typ.Key()) // string
fmt.Println(typ.Elem()) // int
fmt.Println(reflect.ValueOf(m).Len()) // 3
}
我们看到,和切片一样,map变量的长度信息是与map变量的Value绑定的,另外要注意:map变量不能获取容量信息。
2.2 指针类型
指针类型是一个大类,通过Type可以获得指针的kind和其指向的变量的类型信息:
func main() {
i := 10
p := &i
typ := reflect.TypeOf(p)
fmt.Println(typ.Kind()) // ptr
fmt.Println(typ.Elem()) // int
}
2.3 接口类型
接口即契约。在Go中非作为约束的接口类型本质就是一个方法集合,通过reflect.Type可以获得接口类型的这些信息:
type Animal interface {
Speak() string
}
type Cat struct{}
func (c Cat) Speak() string {
return "Meow"
}
func main() {
var a Animal = Cat{}
typ := reflect.TypeOf(a)
fmt.Println(typ.Kind()) // interface
fmt.Println(typ.NumMethod()) // 1
fmt.Println(typ.Method(0).Name) // Speak
fmt.Println(typ.Method(0).Type) // func(main.Animal) string
}
2.4 函数类型
函数在Go中是一等公民,我们可以将其像普通int类型那样去使用,传参、赋值、做返回值都是ok的。下面是通过Type获取函数类型信息的示例:
func foo(a, b int, c *int) (int, bool) {
*c = a + b
return *c, true
}
func main() {
typ := reflect.TypeOf(foo)
fmt.Println(typ.Kind()) // func
fmt.Println(typ.NumIn()) // 3
fmt.Println(typ.In(0), typ.In(1), typ.In(2)) // int int *int
fmt.Println(typ.NumOut()) // 2
fmt.Println(typ.Out(0)) // int
fmt.Println(typ.Out(1)) // bool
}
我们看到和其他类型不同,函数支持NumOut、NumIn、Out等方法。其中In是输出参数的集合,Out则是返回值参数的集合。
3.获取值和修改值信息
掌握了如何在反射世界获取一个变量的类型信息后,我们再来看看如何在反射世界获取并修改一个变量的值信息。
注:并不是所有变量都可以修改值的,可以使用Value的CanSet方法判断值是否可以设置。
// 测试通过反射修改变量值
func main() {
var x float64 = 3.14
valueOfX := reflect.ValueOf(x)
// 获取值
fmt.Println("valueOfX.value:", valueOfX.Interface().(float64)) // valueOfX.value: 3.14
fmt.Println("CanSet:", valueOfX.CanSet()) // CanSet: false
//valueOfX.SetFloat(6.66) // panic: reflect: reflect.Value.SetFloat using unaddressable value。因为valueOfX是x变量副本的值,并不能改变原本的x变量值,类似函数想修改函数外的变量值时必须传变量的指针
valueOfXPtr := reflect.ValueOf(&x)
fmt.Println("valueOfXPtr.value:", valueOfXPtr.Interface()) // valueOfXPtr.value: 0xc000018088
fmt.Println("CanSet:", valueOfXPtr.CanSet()) // CanSet: false
//valueOfXPtr.SetFloat(7.77) // panic: reflect: reflect.Value.SetFloat using unaddressable value
elem := valueOfXPtr.Elem()
fmt.Println("CanSet:", elem.CanSet()) // CanSet: true
elem.SetFloat(8.88)
fmt.Println("x:", x) // x: 8.88
fmt.Println("elem.Value:", elem.Interface().(float64)) // elem.Value: 8.88
elem.Set(reflect.ValueOf(9.99))
fmt.Println("x:", x) // x: 9.99
fmt.Println("elem.Value:", elem.Interface().(float64)) // elem.Value: 9.99
}
4. Value
的Elem()
、IsNil()
和IsValid()
Elem()
方法:
// 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 {
k := v.kind()
switch k {
case Interface:
var eface any
if v.typ.NumMethod() == 0 {
eface = *(*any)(v.ptr)
} else {
eface = (any)(*(*interface {
M()
})(v.ptr))
}
x := unpackEface(eface)
if x.flag != 0 {
x.flag |= v.flag.ro()
}
return x
case Pointer:
ptr := v.ptr
if v.flag&flagIndir != 0 {
if ifaceIndir(v.typ) {
if !verifyNotInHeapPtr(*(*uintptr)(ptr)) {
panic("reflect: reflect.Value.Elem on an invalid notinheap pointer")
}
}
ptr = *(*unsafe.Pointer)(ptr)
}
// The returned value's address is v's value.
if ptr == nil {
return Value{}
}
tt := (*ptrType)(unsafe.Pointer(v.typ))
typ := tt.elem
fl := v.flag&flagRO | flagIndir | flagAddr
fl |= flag(typ.Kind())
return Value{typ, ptr, fl}
}
panic(&ValueError{"reflect.Value.Elem", v.kind()})
}
注释:
调用Elem
方法时Value
的Kind必须是接口或指针,否则会panic;Elem
方法会返回接口包含的值或指针指向的值;如果Value
的值是nil
(如:接口值是nil、指针是nil),则返回「零值」——Value{}
。
IsNil
方法:
// IsNil reports whether its argument v is nil. The argument must be
// a chan, func, interface, map, pointer, or slice value; if it is
// not, IsNil panics. Note that IsNil is not always equivalent to a
// regular comparison with nil in Go. For example, if v was created
// by calling ValueOf with an uninitialized interface variable i,
// i==nil will be true but v.IsNil will panic as v will be the zero
// Value.
func (v Value) IsNil() bool {
k := v.kind()
switch k {
case Chan, Func, Map, Pointer, UnsafePointer:
if v.flag&flagMethod != 0 {
return false
}
ptr := v.ptr
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
return ptr == nil
case Interface, Slice:
// Both interface and slice are nil if first word is 0.
// Both are always bigger than a word; assume flagIndir.
return *(*unsafe.Pointer)(v.ptr) == nil
}
panic(&ValueError{"reflect.Value.IsNil", v.kind()})
}
注释:
IsNil
方法返回Value
是否是nil
。
调用IsNil
方法时,Value
的Kind必须是chan
、func
、接口、map
、指针或切片,否则会panic。
【注意】IsNil
并不总是等同于Go中与nil
的常规比较,比如:如果 v 是通过使用未初始化的接口变量 i 调用 ValueOf 创建的(此时ValueOf
方法会返回零值),则i==nil
将为 true,但v.IsNil()
将崩溃,因为 v 将是零值。
IsValid
方法:
// IsValid reports whether v represents a value.
// It returns false if v is the zero Value.
// If IsValid returns false, all other methods except String panic.
// Most functions and methods never return an invalid Value.
// If one does, its documentation states the conditions explicitly.
func (v Value) IsValid() bool {
return v.flag != 0
}
注释:
IsValid
方法返回Value
是否代表一个值,如果Value
是零值,则返回false
。
若IsValid
返回false
,则除String
方法之外的其他所有方法都会panic。
大多数函数或方法永远不会返回无效的Value
,如果会返回,则文档中会明确说明条件。
测试示例:
type T1Interface interface {
T1Method() string
}
type T1Struct struct {
}
func (t *T1Struct) T1Method() string {
return "T1Struct"
}
func main() {
// 未初始化的接口变量
var i1 interface{}
fmt.Println(i1) // <nil>
fmt.Println(i1 == nil) // true
i1V := reflect.ValueOf(i1)
fmt.Println(i1V.IsValid()) // false
//fmt.Println(i1V.IsNil()) // 会panic
var i2 T1Interface
fmt.Println(i2) // <nil>
fmt.Println(i2 == nil) // true
i2V := reflect.ValueOf(i2)
fmt.Println(i2V.IsValid()) // false
//fmt.Println(i2V.IsNil()) // 会panic
// 接口初始化
var i3 T1Interface
i3 = &T1Struct{}
fmt.Println(i3 == nil) // false
i3V := reflect.ValueOf(i3)
fmt.Println(i3V.Type()) // *main.T1Struct
fmt.Println(i3V.IsValid()) // true
fmt.Println(i3V.Kind()) // ptr
fmt.Println(i3V.Elem().IsValid()) // true
fmt.Println(i3V.Elem().Kind()) // struct
// 空指针
var p1 *int
p1V := reflect.ValueOf(p1)
fmt.Println(p1V.IsValid()) // true
fmt.Println(p1V.Kind()) // ptr
fmt.Println(p1V.Elem()) // <invalid reflect.Value>。因为Value是空指针
fmt.Println(p1V.IsNil()) // true
}
5. 调用函数和方法
通过反射我们可以在反射世界调用函数,也可以调用特定类型的变量的方法。
下面是一个通过reflect.Value调用函数的简单例子:
func add(a, b int) int {
return a + b
}
func main() {
// 获取函数类型变量
val := reflect.ValueOf(add)
// 准备函数参数
args := []reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}
// 调用函数
result := val.Call(args)
fmt.Println(result[0].Int()) // 输出:3
}
从示例看到,我们通过Value的Call方法来调用函数add。add有两个入参,我们不能直接传入int类型,因为这是在反射世界,我们要用反射世界的“专用参数”,即ValueOf后的值。Call的结果就是反射世界的返回值的Value形式,通过Value.Int方法可以还原反射世界的Value为int。
注:通过reflect.Type无法调用函数和方法。
方法的调用与函数调用类似,下面是一个例子:
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area(factor float64) float64 {
return r.Width * r.Height * factor
}
func main() {
r := Rectangle{Width: 10, Height: 5}
val := reflect.ValueOf(r)
method := val.MethodByName("Area")
args := []reflect.Value{reflect.ValueOf(1.5)}
result := method.Call(args)
fmt.Println(result[0].Float()) // 输出:75
}
通过MethodByName获取反射世界的method value,然后同样是通过Call方法实现方法Area的调用。
注:reflect目前不支持对非导出方法的调用。
总结:函数和方法的调用必须通过Value
来调用,和值有关!
6. 动态创建类型实例
reflect更为强大的功能是可以在运行时动态创建各种类型的实例。下面是在反射世界动态创建各种类型实例的示例。
6.1 基本类型
下面以int、float64和string为例演示一下如何通过reflect在运行时动态创建基本类型的实例。
New
方法:
// New returns a Value representing a pointer to a new zero value
// for the specified type. That is, the returned Value's Type is PointerTo(typ).
func New(typ Type) Value
New
返回的是指向指定类型的新零值的指针
- 创建int类型实例
func main() {
val := reflect.New(reflect.TypeOf(0))
val.Elem().SetInt(42)
fmt.Println(val.Elem().Int()) // 输出:42
}
- 创建float64类型实例
func main() {
val := reflect.New(reflect.TypeOf(0.0))
val.Elem().SetFloat(3.14)
fmt.Println(val.Elem().Float()) // 输出:3.14
}
- 创建string类型实例
func main() {
val := reflect.New(reflect.TypeOf(""))
val.Elem().SetString("hello")
fmt.Println(val.Elem().String()) // 输出:hello
}
更为复杂的类型的实例,我们继续往下看。
6.2 数组类型
使用reflect在运行时创建一个[3]int
类型的数组实例,并设置数组实例各个元素的值:
func main() {
typ := reflect.ArrayOf(3, reflect.TypeOf(0))
val := reflect.New(typ)
arr := val.Elem()
arr.Index(0).SetInt(1)
arr.Index(1).SetInt(2)
arr.Index(2).SetInt(3)
fmt.Println(arr.Interface()) // 输出:[1 2 3]
arr1, ok := arr.Interface().([3]int)
if !ok {
fmt.Println("not a [3]int")
return
}
fmt.Println(arr1) // [1 2 3]
}
6.3 切片类型
使用reflect在运行时创建一个[]int
类型的切片实例,并设置切片实例中各个元素的值:
func main() {
typ := reflect.SliceOf(reflect.TypeOf(0)) // 切片元素类型
val := reflect.MakeSlice(typ, 3, 3) // 动态创建切片实例
val.Index(0).SetInt(1)
val.Index(1).SetInt(2)
val.Index(2).SetInt(3)
fmt.Println(val.Interface()) // 输出:[1 2 3]
sl, ok := val.Interface().([]int)
if !ok {
fmt.Println("sl is not a []int")
return
}
fmt.Println(sl) // [1 2 3]
}
6.4 map类型
使用reflect在运行时创建一个map[string]int
类型的实例,并设置map实例中键值对:
func main() {
typ := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
val := reflect.MakeMap(typ)
key1 := reflect.ValueOf("one")
value1 := reflect.ValueOf(1)
key2 := reflect.ValueOf("two")
value2 := reflect.ValueOf(2)
val.SetMapIndex(key1, value1)
val.SetMapIndex(key2, value2)
fmt.Println(val.Interface()) // 输出:map[one:1 two:2]
m, ok := val.Interface().(map[string]int)
if !ok {
fmt.Println("m is not a map[string]int")
return
}
fmt.Println(m)
}
6.5 channel类型
使用reflect在运行时创建一个chan int
类型的实例,并从该channel实例接收数据:
func main() {
typ := reflect.ChanOf(reflect.BothDir, reflect.TypeOf(0))
val := reflect.MakeChan(typ, 0)
go func() {
val.Send(reflect.ValueOf(42))
}()
ch, ok := val.Interface().(chan int)
if !ok {
fmt.Println("ch is not a chan int")
return
}
fmt.Println(<-ch) // 42
}
6.6 结构体类型
使用reflect在运行时创建一个struct类型的实例,并设置该实例的字段值并调用该实例的方法:
type Person struct {
Name string
Age int
}
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s and I am %d years old\n", p.Name, p.Age)
}
func (p Person) SayHello(name string) {
fmt.Printf("Hello, %s! My name is %s\n", name, p.Name)
}
func main() {
typ := reflect.StructOf([]reflect.StructField{
{
Name: "Name",
Type: reflect.TypeOf(""),
},
{
Name: "Age",
Type: reflect.TypeOf(0),
},
})
ptrVal := reflect.New(typ)
val := ptrVal.Elem()
val.FieldByName("Name").SetString("Alice")
val.FieldByName("Age").SetInt(25)
person := (*Person)(ptrVal.UnsafePointer())
person.Greet() // 输出:Hello, my name is Alice and I am 25 years old
person.SayHello("Bob") // 输出:Hello, Bob! My name is Alice
}
我们看到:上面代码在反射世界中动态创建了一个带有两个字段Name和Age的struct类型,注意该struct类型与Person并非同一个类型,但他们的内存结构是一致的。这就是上面代码尾部基于反射世界创建出的匿名struct显式转换为Person类型后能正常工作的原因!
注:目前reflect不支持在运行时为动态创建的结构体类型添加新方法。
6.7 指针类型
使用reflect在运行时创建一个指针类型的实例,并通过指针设置其指向内存对象的值:
type Person struct {
Name string
Age int
}
func main() {
typ := reflect.PtrTo(reflect.TypeOf(Person{}))
val := reflect.New(typ.Elem())
val.Elem().FieldByName("Name").SetString("Alice")
val.Elem().FieldByName("Age").SetInt(25)
person := val.Interface().(*Person)
fmt.Println(person.Name) // 输出:Alice
fmt.Println(person.Age) // 输出:25
}