内容简介:大多数时候,Go中的变量,类型和函数非常简单直接。当需要一个类型、变量或者是函数时,可以直接定义它们:但是有时你希望在运行时使用变量的在编写程序时还不存在的信息。比如你正在尝试将文件或网络请求中的数据映射到变量中。或者你想构建一个适用于不同类型的工具。在这种情况下,你需要使用反射。反射使您能够在运行时检查类型。它还允许您在运行时检查,修改和创建变量,函数和结构体。Go中的反射是基于三个概念构建的:类型,种类和值(Types Kinds Values)。标准库中的
什么是反射
大多数时候,Go中的变量,类型和函数非常简单直接。当需要一个类型、变量或者是函数时,可以直接定义它们:
type Foo struct { A int B string } var x Foo func DoSomething(f Foo) { fmt.Println(f.A, f.B) }
但是有时你希望在运行时使用变量的在编写程序时还不存在的信息。比如你正在尝试将文件或网络请求中的数据映射到变量中。或者你想构建一个适用于不同类型的工具。在这种情况下,你需要使用反射。反射使您能够在运行时检查类型。它还允许您在运行时检查,修改和创建变量,函数和结构体。
Go中的反射是基于三个概念构建的:类型,种类和值(Types Kinds Values)。标准库中的 reflect
包提供了 Go 反射的实现。
反射变量类型
首先让我们看一下类型。你可以使用反射来调用函数 varType := reflect.TypeOf(var)
来获取变量 var
的类型。这将返回类型为 reflect.Type
的变量,该变量具有获取定义时变量的类型的各种信息的方法集。下面我们来看一下常用的获取类型信息的方法。
我们要看的第一个方法是 Name()
。这将返回变量类型的名称。某些类型(例如切片或指针)没有名称,此方法会返回空字符串。
下一个方法,也是我认为第一个真正非常有用的方法是 Kind()
。Type是由Kind组成的---Kind 是切片,映射,指针,结构,接口,字符串,数组,函数,int或其他某种原始类型的抽象表示。要理解Type和Kind之间的差异可能有些棘手,但是请你以这种方式来思考。如果定义一个名为Foo的结构体,则Kind为struct,类型为Foo。
使用反射时要注意的一件事:反射包中的所有内容都假定你知道自己在做什么,并且如果使用不正确,许多函数和方法调用都会引起 panic
。例如,如果你在 reflect.Type
上调用与当前类型不同的类型关联的方法,您的代码将会 panic
。
如果变量是指针,映射,切片,通道或数组变量,则可以使用 varType.Elem()
找出指向或包含的值的类型。
如果变量是结构体,则可以使用反射来获取结构体中的字段数,并从每个字段上获取 reflect.StructField
结构体。 reflection.StructField
为您提供了字段的名称,标号,类型和结构体标签。其中标签信息对应 reflect.StructTag
类型的字符串,并且它提供了Get方法用于解析和根据特定key提取标签信息中的子串。
下面是一个简单的示例,用于输出各种变量的类型信息:
type Foo struct { A int `tag1:"First Tag" tag2:"Second Tag"` B string } func main() { sl := []int{1, 2, 3} greeting := "hello" greetingPtr := &greeting f := Foo{A: 10, B: "Salutations"} fp := &f slType := reflect.TypeOf(sl) gType := reflect.TypeOf(greeting) grpType := reflect.TypeOf(greetingPtr) fType := reflect.TypeOf(f) fpType := reflect.TypeOf(fp) examiner(slType, 0) examiner(gType, 0) examiner(grpType, 0) examiner(fType, 0) examiner(fpType, 0) } func examiner(t reflect.Type, depth int) { fmt.Println(strings.Repeat("\t", depth), "Type is", t.Name(), "and kind is", t.Kind()) switch t.Kind() { case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice: fmt.Println(strings.Repeat("\t", depth+1), "Contained type:") examiner(t.Elem(), depth+1) case reflect.Struct: for i := 0; i < t.NumField(); i++ { f := t.Field(i) fmt.Println(strings.Repeat("\t", depth+1), "Field", i+1, "name is", f.Name, "type is", f.Type.Name(), "and kind is", f.Type.Kind()) if f.Tag != "" { fmt.Println(strings.Repeat("\t", depth+2), "Tag is", f.Tag) fmt.Println(strings.Repeat("\t", depth+2), "tag1 is", f.Tag.Get("tag1"), "tag2 is", f.Tag.Get("tag2")) } } } }
变量的类型输出如下:
Type is and kind is slice Contained type: Type is int and kind is int Type is string and kind is string Type is and kind is ptr Contained type: Type is string and kind is string Type is Foo and kind is struct Field 1 name is A type is int and kind is int Tag is tag1:"First Tag" tag2:"Second Tag" tag1 is First Tag tag2 is Second Tag Field 2 name is B type is string and kind is string Type is and kind is ptr Contained type: Type is Foo and kind is struct Field 1 name is A type is int and kind is int Tag is tag1:"First Tag" tag2:"Second Tag" tag1 is First Tag tag2 is Second Tag Field 2 name is B type is string and kind is string
Run in go playground: https://play.golang.org/p/lZ9...
使用反射创建新实例
除了检查变量的类型外,还可以使用反射来读取,设置或创建值。首先,需要使用 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()
方法从 reflect.Value
回到普通变量值。由于Go没有泛型,因此变量的原始类型会丢失;该方法返回类型为 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) }
他们的输出如下:
hello goodbye {A:20 B:Greetings}, 20, Greetings
Run in go playground https://play.golang.org/p/PFc...
反射创建引用类型的实例
除了生成内置类型和用户定义类型的实例之外,还可以使用反射来生成通常需要make函数的实例。可以使用 reflect.MakeSlice
, reflect.MakeMap
和 reflect.MakeChan
函数制作切片,Map或通道。在所有情况下,都提供一个 reflect.Type
,然后获取一个 reflect.Value
,可以使用反射对其进行操作,或者可以将其分配回一个标准变量。
func main() { // 定义变量 intSlice := make([]int, 0) mapStringInt := make(map[string]int) // 获取变量的 reflect.Type sliceType := reflect.TypeOf(intSlice) mapType := reflect.TypeOf(mapStringInt) // 使用反射创建类型的新实例 intSliceReflect := reflect.MakeSlice(sliceType, 0, 0) mapReflect := reflect.MakeMap(mapType) // 将创建的新实例分配回一个标准变量 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) }
使用反射创建函数
反射不仅仅可以为存储数据创造新的地方。还可以使用 reflect.MakeFunc
函数使用 reflect
来创建新函数。该函数期望我们要创建的函数的 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)) }
你可以在goplayground运行代码 https://play.golang.org/p/QZ8... 并看到输出如下:
starting ending calling main.timeMe took 1s starting ending calling main.timeMeToo took 2s 4
反射是每个Go开发人员都应了解并学会的强大工具。但是使用他们可以用来做什么呢?在下一篇博客文章中,我将探讨现有库中反射的一些用法,并使用反射来创建一些新的东西。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- .NET/C# 使用反射注册事件
- 使用反射实现简易的 RPC 框架
- 结合案例使用 Java 注解和反射
- 使用Spring的注释和反射让代码更精简
- Go 译文之如何使用反射 part2
- 使用ImpromptuInterface反射库方便的创建自定义DfaGraphWriter
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Introduction to Computation and Programming Using Python
John V. Guttag / The MIT Press / 2013-7 / USD 25.00
This book introduces students with little or no prior programming experience to the art of computational problem solving using Python and various Python libraries, including PyLab. It provides student......一起来看看 《Introduction to Computation and Programming Using Python》 这本书的介绍吧!