学习使用 Go 的反射

栏目: IT技术 · 发布时间: 4年前

内容简介:大多数时候,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.MakeSlicereflect.MakeMapreflect.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开发人员都应了解并学会的强大工具。但是使用他们可以用来做什么呢?在下一篇博客文章中,我将探讨现有库中反射的一些用法,并使用反射来创建一些新的东西。

学习使用 Go 的反射


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

ACM图灵奖演讲集

ACM图灵奖演讲集

阿申豪斯特 / 苏运霖 / 电子工业出版社 / 2005-4 / 55.0

本书完整地收录了这些演讲,并配之以部分获奖者撰写的后记,旨在反映过去数年来这一领域中发生的变化。对任何一位计算机科学的历史与发展有兴趣的人来说,本书都极具收藏价值。  本文收录了自图灵奖开始颁发的1966年起到1985年这20年间图灵奖获得者在授奖大会上所做演讲的全文。由于在此期间有三次是把奖项同时授予两个人的,而其中有两次两位获奖者分别做了演讲,因此一共收录了22篇演讲稿。本书把这些演讲分为两大......一起来看看 《ACM图灵奖演讲集》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

html转js在线工具
html转js在线工具

html转js在线工具