Go是一门强类型的语言,在大多数情况下,申明一个变量、函数、struct都是直截了当的。在大多数情况下,这些都是够用的,但有时你想在程序运行中来动态扩展程序的信息,也许你想把文件或网络请求中的数据映射到一个变量中;也许你想建立一个能处理不同类型的工具(虽然Go1.18有了泛型)。在这些情况下,你需要使用反射。反射使你有能力在运行时检查、修改和创建变量、函数和结构的能力。

反射的核心

图片转自 Go 语言设计与实现

反射的三大核心是*Types, Kinds, Values,下面将围绕这三个方面来进行讲解。

我们先定义一个struct对象。

type User struct {
  Name	string
  Age int
}

类型Types

通过反射获取类型

u := User{
    Name: "czyt",
    Age:  18,
}
uptr := &u
ot := reflect.TypeOf(u)
otptr := reflect.TypeOf(uptr)
log.Println(ot.Name())
// 打印 User
log.Println(otptr.Name())
// 打印 空

通过调用Name()方法返回类型的名称,某些类型,如切片或指针,没有名称,此方法返回一个空字符串。

种类Kinds

Kind通过调用Kind()得来。

u := User{
    Name: "czyt",
    Age:  18,
}
uptr := &u
ot := reflect.TypeOf(u)
otptr := reflect.TypeOf(uptr)
log.Println(ot.Kind())
// 输出 struct
log.Println(otptr.Kind())
// 输出 ptr

Kind() 返回的是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
)

kind 和 type 之间的区别可能很难理解,但可以这样想。如果你定义了一个名为 User的结构体,那么种类是struct,类型是 User.

使用反射时需要注意的一件事:反射包中的所有内容都假定您知道自己在做什么,如果使用不正确,许多函数和方法调用都会出现panic。例如,如果您调用 reflect.Type 上的方法,该方法与与当前类型不同的类型相关联,您的代码将panic。请务必记得使用你的反射类型来知道哪些方法会起作用,哪些会panic。

如果您的变量是指针、映射、切片、通道或数组,您可以使用 varType.Elem() 找出包含的类型。

如果您的变量是一个结构,您可以使用反射来获取结构中的字段数,并取回包含在反射.StructField 结构中的每个字段的结构。reflect.StructField 为您提供字段上的名称、顺序、类型和结构标记。

值Values

除了检查变量的类型之外,您还可以使用反射来读取、设置或创建值。首先,您需要使用 refVal := reflect.ValueOf(var) 为您的变量创建一个 reflect.Value 实例。如果您希望能够使用反射来修改值,则必须使用 refPtrVal := reflect.ValueOf(&var)获取指向变量的指针。如果你不这样做,你可以使用反射读取值,但你不能修改它。

一旦有了 reflect.Value,就可以使用 Type() 方法获取变量的 reflect.Type

如果你想修改一个值,记住它必须是一个指针,你必须先取消引用指针。您使用 refPtrVal.Elem().Set(newRefVal) 进行更改,传递给 Set() 的值也必须是 reflect.Value

如果你想创建一个新值,你可以使用函数调用 newPtrVal := reflect.New(varType),传入一个 reflect.Type。这将返回一个指针值,然后您可以对其进行修改。如上所述使用 Elem().Set()

最后,您可以通过调用 Interface() 方法返回到普通变量。该方法返回一个 interface{} 类型的值。如果您创建了一个指针以便可以修改值,则需要使用 Elem().Interface() 取消引用反射指针。在这两种情况下,您都需要将空接口转换为实际类型才能使用它。

type Foo struct {
	A int `tag1:"First Tag" tag2:"Second Tag"`
	B string
}

func main() {
	greeting := "hello"
	f := Foo{A: 10, B: "Salutations"}

	gVal := reflect.ValueOf(greeting)
	// not a pointer so all we can do is read it
	fmt.Println(gVal.Interface())

	gpVal := reflect.ValueOf(&greeting)
	// it’s a pointer, so we can change it, and it changes the underlying variable
	gpVal.Elem().SetString("goodbye")
	fmt.Println(greeting)

	fType := reflect.TypeOf(f)
	fVal := reflect.New(fType)
	fVal.Elem().Field(0).SetInt(20)
	fVal.Elem().Field(1).SetString("Greetings")
	f2 := fVal.Elem().Interface().(Foo)
	fmt.Printf("%+v, %d, %s\n", f2, f2.A, f2.B)
}

reflect.Valuereflect.Type 都提供了 Elem() 方法。

其中reflect.Value.Elem() 会返回interface对象v指向的值或指针。当v的类型不是interface或者指针时,程序会panic,当v为该类型的零值时该函数会返回nil

reflect.Type.Elem() 返回该类型元素的类型。当类型是Array, Chan, Map, Ptr, Slice之外的类型时,程序会panic。

示例

基础示例

动态调用struct方法

获取方法信息

func main()  {
	// 声明一个 Person 接口,并用 Hero 作为接收器
		var person Person = &Hero{}
	// 获取接口Person的类型对象
	typeOfPerson := reflect.TypeOf(person)
	// 打印Person的方法类型和名称
	for i := 0 ; i < typeOfPerson.NumMethod(); i++{
		fmt.Printf("method is %s, type is %s, kind is %s.\n", typeOfPerson.Method(i).Name, typeOfPerson.Method(i).Type, typeOfPerson.Method(i).Type.Kind())
	}
	method, _ := typeOfPerson.MethodByName("Run")
	fmt.Printf("method is %s, type is %s, kind is %s.\n", method.Name, method.Type, method.Type.Kind())
	}

动态调用

type M struct{}
type In struct{}
type Out struct{}

func (m *M) Example(in In) Out {
	return Out{}
}
func main() {
	v := reflect.ValueOf(&M{})
	m := v.MethodByName("Example")
	in := m.Type().In(0)
	out := m.Type().Out(0)
	fmt.Println(in, out)
       
	inVal := reflect.New(in).Elem()
        // 可以将 inVal 转为interface后进行赋值之类的操作……
	rtn := m.Call([]reflect.Value{inVal})
	fmt.Println(rtn[0])
}

不通过Make创建slice、Map

除了创建内置和用户定义类型的实例外,您还可以使用反射来创建通常需要 make 函数的实例。您可以使用 reflect.MakeSlicereflect.MakeMapreflect.MakeChan 函数制作切片、贴图或通道。在所有情况下,您都提供了一个reflect.Type并取回了一个可以使用反射操作的 reflect.Value,或者您可以分配回一个标准变量。

func main() {
	// declaring these vars, so I can make a reflect.Type
	intSlice := make([]int, 0)
	mapStringInt := make(map[string]int)

	// here are the reflect.Types
	sliceType := reflect.TypeOf(intSlice)
	mapType := reflect.TypeOf(mapStringInt)

	// and here are the new values that we are making
	intSliceReflect := reflect.MakeSlice(sliceType, 0, 0)
	mapReflect := reflect.MakeMap(mapType)

	// and here we are using them
	v := 10
	rv := reflect.ValueOf(v)
	intSliceReflect = reflect.Append(intSliceReflect, rv)
	intSlice2 := intSliceReflect.Interface().([]int)
	fmt.Println(intSlice2)

	k := "hello"
	rk := reflect.ValueOf(k)
	mapReflect.SetMapIndex(rk, rv)
	mapStringInt2 := mapReflect.Interface().(map[string]int)
	fmt.Println(mapStringInt2)
}

检查struct是否实现接口

例如下面的定义

type shape interface {
    getNumSides() int
    getArea() int
}

type square struct {
    len int
}

func (s square) getNumSides() int {
    return 4
}

常规做法 可以使用var _ shape = (*square)(nil)来进行判断,但是某些情况下我们需要使用反射来实现,具体代码如下:

func IsShaperImpl(checkTarget interface{}) bool {
	c := reflect.TypeOf(checkTarget)
	modelType := reflect.TypeOf((*shape)(nil)).Elem()
	return c.Implements(modelType)
}
fmt.Println(IsShaperImpl(&square{}))
// 打印 false

通过Field offset修改struct字段值

	type B_struct struct {
		B_int    int
		B_string string
		B_slice  []string
		B_map    map[int]string
	}
	type A_struct struct {
		A_int   int
		A_float float32
		A_bool  bool
		A_BPtr  *B_struct
		A_Bean  B_struct
	}
	// 在64位机器中,int占8个字节,float64占8个字节,
	//bool占1个字节,指针ptr占8个字节,string的底层是stringheader占用16个字节  
	//slice的底层结构是sliceheader,map底层结构未知,但是占用8个字节  
	//在结构体中会进行字节对齐  
	//比如在bool后面跟一个ptr,bool就会对齐为8个字节
	fmt.Println("total size of A:", reflect.TypeOf(A_struct{}).Size()) 
	fmt.Println("total size of B:", reflect.TypeOf(B_struct{}).Size())
	var type_A = reflect.TypeOf(A_struct{})
	var type_B = reflect.TypeOf(B_struct{})
	var A_bean = A_struct{}
	var start_ptr = uintptr(unsafe.Pointer(&A_bean))
	// 设置A的第一个int型成员变量
	*((*int)(unsafe.Pointer(start_ptr + type_A.Field(0).Offset))) = 100
	fmt.Println("after set int of A: ", A_bean)
	// 设置A的第二个float32成员变量
	*((*float32)(unsafe.Pointer(start_ptr + type_A.Field(1).Offset))) = 55.5
	fmt.Println("after set float32 of A: ", A_bean)
	// 设置A的第三个bool变量
	*((*bool)(unsafe.Pointer(start_ptr + type_A.Field(2).Offset))) = true
	fmt.Println("after set bool of A:", A_bean)
	// 设置A的第四个ptr变量
	var first_B = &B_struct{B_int: 1024, B_string: "hello", B_slice: []string{"lalla", "biubiu"}, B_map: map[int]string{1: "this is a one", 2: "this is a two"}}
	*((**B_struct)(unsafe.Pointer(start_ptr + type_A.Field(3).Offset))) = first_B
	fmt.Println("after set A_BPtr of A:", A_bean, "and A_bean.A_BPtr:", A_bean.A_BPtr)
	// A的第五个变量是一个B_struct结构体变量,所以可以继续通过偏移来设置
	//A的第五个变量中的第一个int变量
	*((*int)(unsafe.Pointer(start_ptr + type_A.Field(4).Offset + type_B.Field(0).Offset))) = 2048
	fmt.Println("after set B_int of A_Bbean of A:", A_bean)
	// A的第五个变量中的第二个string变量
	*((*string)(unsafe.Pointer(start_ptr + type_A.Field(4).Offset + type_B.Field(1).Offset))) = "world"
	fmt.Println("after set B_string of A_Bbean of A:", A_bean)
	// A的第五个变量中的第三个slice变量
	*((*[]string)(unsafe.Pointer(start_ptr + type_A.Field(4).Offset + type_B.Field(2).Offset))) = []string{"hehe", "heihei"}
	fmt.Println("after set B_slice of A_Bbean of A:", A_bean)
	// A的第六个变量中的第三个slice变量
	*((*map[int]string)(unsafe.Pointer(start_ptr + type_A.Field(4).Offset + type_B.Field(3).Offset))) = map[int]string{3: "this is three", 4: "this is four",}
	fmt.Println("after set B_map of A_Bbean of A:", A_bean)

创建函数

反射不只是让你创造新的地方来存储数据。您可以使用反射来使用 reflect.MakeFunc 函数创建新函数。这个函数需要我们想要创建的函数的 reflect.Type 和一个闭包,它的输入参数是 []reflect.Value 类型,其输出参数也是 []reflect.Value 类型。这是一个简单的示例,它为传递给它的任何函数创建一个计时包装器:

func MakeTimedFunction(f interface{}) interface{} {
	rf := reflect.TypeOf(f)
	if rf.Kind() != reflect.Func {
		panic("expects a function")
	}
	vf := reflect.ValueOf(f)
	wrapperF := reflect.MakeFunc(rf, func(in []reflect.Value) []reflect.Value {
		start := time.Now()
		out := vf.Call(in)
		end := time.Now()
		fmt.Printf("calling %s took %v\n", runtime.FuncForPC(vf.Pointer()).Name(), end.Sub(start))
		return out
	})
	return wrapperF.Interface()
}

func timeMe() {
	fmt.Println("starting")
	time.Sleep(1 * time.Second)
	fmt.Println("ending")
}

func timeMeToo(a int) int {
	fmt.Println("starting")
	time.Sleep(time.Duration(a) * time.Second)
	result := a * 2
	fmt.Println("ending")
	return result
}

func main() {
	timed := MakeTimedFunction(timeMe).(func())
	timed()
	timedToo := MakeTimedFunction(timeMeToo).(func(int) int)
	fmt.Println(timedToo(2))
}

动态创建struct

在 Go 中使用反射还可以做一件事。您可以在运行时通过将一部分 reflect.StructField 实例传递给 reflect.StructOf 函数来创建全新的结构。这个有点奇怪;我们正在创建一个新类型,但我们没有它的名称,所以你不能真正把它变成一个“正常”的变量。您可以创建一个新实例并使用Interface() 将值放入 interface{} 类型的变量中,但是如果要在其上设置任何值,则需要使用反射。

func MakeStruct(vals ...interface{}) interface{} {
	var sfs []reflect.StructField
	for k, v := range vals {
		t := reflect.TypeOf(v)
		sf := reflect.StructField{
			Name: fmt.Sprintf("F%d", (k + 1)),
			Type: t,
		}
		sfs = append(sfs, sf)
	}
	st := reflect.StructOf(sfs)
	so := reflect.New(st)
	return so.Interface()
}

func main() {
	s := MakeStruct(0, "", []int{})
	// this returned a pointer to a struct with 3 fields:
	// an int, a string, and a slice of ints
	// but you can’t actually use any of these fields
	// directly in the code; you have to reflect them
	sr := reflect.ValueOf(s)

	// getting and setting the int field
	fmt.Println(sr.Elem().Field(0).Interface())
	sr.Elem().Field(0).SetInt(20)
	fmt.Println(sr.Elem().Field(0).Interface())

	// getting and setting the string field
	fmt.Println(sr.Elem().Field(1).Interface())
	sr.Elem().Field(1).SetString("reflect me")
	fmt.Println(sr.Elem().Field(1).Interface())

	// getting and setting the []int field
	fmt.Println(sr.Elem().Field(2).Interface())
	v := []int{1, 2, 3}
	rv := reflect.ValueOf(v)
	sr.Elem().Field(2).Set(rv)
	fmt.Println(sr.Elem().Field(2).Interface())
}

进阶应用

动态创建通道

发送通道

var k = reflect.TypeOf(0)
fmt.Println(reflect.ChanOf(reflect.SendDir, k))
// 输出 chan<- int

接收通道

ta := reflect.ArrayOf(5, reflect.TypeOf(123))
tc := reflect.ChanOf(reflect.RecvDir, ta)
fmt.Println(tc)
// 输出 <-chan [5]int

一个复杂点的例子 参考

package main

import (
	"reflect"
	"strings"
)

func makeChannel(t reflect.Type, chanDir reflect.ChanDir, buffer int) reflect.Value {
	ctype := reflect.ChanOf(chanDir, t)
	return reflect.MakeChan(ctype, buffer)
}

type T interface{}

type Query struct {
	input reflect.Value
}

func (q *Query) Apply(f T) *Query {
	value := reflect.ValueOf(f)
	if value.Kind() != reflect.Func {
		panic("Apply() parameter must be a function")
	}

	rtype := value.Type().Out(0)
	output := makeChannel(rtype, reflect.BothDir, 0)
	go func() {
		var elem reflect.Value
		for ok := true; ok; {
			if elem, ok = q.input.Recv(); ok {
				result := value.Call([]reflect.Value{elem})
				output.Send(result[0])
			}
		}
		output.Close()
	}()
	return &Query{output}
}

func From(array T) *Query {
	value := reflect.ValueOf(array)
	if value.Kind() != reflect.Slice {
		panic("From() parameter must be a slice")
	}

	etype := value.Type().Elem()
	output := makeChannel(etype, reflect.BothDir, 0)
	go func() {
		for i := 0; i != value.Len(); i++ {
			output.Send(value.Index(i))
		}
		output.Close()
	}()
	return &Query{output}
}

func (q *Query) Items() <-chan T {
	output := make(chan T)
	go func() {
		for ok := true; ok; {
			var elem reflect.Value
			if elem, ok = q.input.Recv(); ok {
				output <- elem.Interface()
			}
		}
		close(output)
	}()
	return output
}

func (q *Query) StringItems() <-chan string {
	output := make(chan string)
	go func() {
		for elem := range q.Items() {
			output <- elem.(string)
		}
		close(output)
	}()
	return output
}

func main() {
	t := []int{0, 1, 2}
	m := map[int]string{0: "zero", 1: "one", 2: "two"}
	for elem := range From(t).Apply(func(x int) string { return m[x] }).Apply(strings.ToUpper).StringItems() {
		println(elem)
	}
}

动态创建通道SelectCase

摘自极客时间专栏《Go语言并发编程实战》的一段代码:


func main() {
    var ch1 = make(chan int, 10)
    var ch2 = make(chan int, 10)

    // 创建SelectCase
    var cases = createCases(ch1, ch2)

    // 执行10次select
    for i := 0; i < 10; i++ {
        chosen, recv, ok := reflect.Select(cases)
        if recv.IsValid() { // recv case
            fmt.Println("recv:", cases[chosen].Dir, recv, ok)
        } else { // send case
            fmt.Println("send:", cases[chosen].Dir, ok)
        }
    }
}

func createCases(chs ...chan int) []reflect.SelectCase {
    var cases []reflect.SelectCase


    // 创建recv case
    for _, ch := range chs {
        cases = append(cases, reflect.SelectCase{
            Dir:  reflect.SelectRecv,
            Chan: reflect.ValueOf(ch),
        })
    }

    // 创建send case
    for i, ch := range chs {
        v := reflect.ValueOf(i)
        cases = append(cases, reflect.SelectCase{
            Dir:  reflect.SelectSend,
            Chan: reflect.ValueOf(ch),
            Send: v,
        })
    }

    return cases
}

参考文档