学习使用Go反射的用法示例
作者:网管叨bi叨 发布时间:2024-04-25 15:25:49
什么是反射
大多数时候,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/lZ97yAUHxX
使用反射创建新实例
除了检查变量的类型外,还可以使用反射来读取,设置或创建值。首先,需要使用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/PFcEYfZqZ8
反射创建引用类型的实例
除了生成内置类型和用户定义类型的实例之外,还可以使用反射来生成通常需要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/QZ8ttFZzGx并看到输出如下:
starting
ending
calling main.timeMe took 1s
starting
ending
calling main.timeMeToo took 2s
4
反射是每个Go开发人员都应了解并学会的强大工具。但是使用他们可以用来做什么呢?在下一篇博客文章中,我将探讨现有库中反射的一些用法,并使用反射来创建一些新的东西。
来源:https://segmentfault.com/a/1190000021619810


猜你喜欢
- MySQL是一个非常流行的小型关系型数据库管理系统,2008年1月16号被Sun公司收购。目前MySQL被广泛地应用在Internet上的中
- 显示图像: Image img = Image.From
- 本文实例为大家分享了梅尔倒谱系数实现代码,供大家参考,具体内容如下""" @author: zoutai@fi
- 描述:输入一个大于0的整数n,输出1到n的全排列:例如:n=3,输出[[3, 2, 1], [2, 3, 1], [2, 1, 3], [3
- 我看见朋友可以把数据库的记录输出到页面表格上去,觉得很有用。这是怎么做的啊?见下:dbtable.asp<html><he
- MaxDB是MySQL AB公司通过SAP认证的数据库。MaxDB数据库服务器补充了MySQL AB产品系列。某些MaxDB特性在MySQL
- 按照本文操作和体会,会对sql优化有个基本最简单的了解,其他深入还需要更多资料和实践的学习: 1. 建表: 代码如下:creat
- 环境springboot、mybatisPlus、mysql8mysql8(部署在1核2G的服务器上,很卡,所以下面的数据条数用5000,太
- 一、什么是模块容器 -> 数据的封装函数 -> 语句的封装类 -> 方法和属性的封装模块 -> 模块就是程序,模块就
- 在SQL Server 2005中,它的另外一个强大的新特点是数据库快照。数据库快照是一个数据库的只读副本,它是数据库所有数据的映射,由快照
- 前言光流flow特征中包含了一个视频当中运动相关的信息,在视频动作定位当中光流特征使用的比较多,所以记录一下提取光流特征的方法。使用的方法是
- 前几天遇到一个问题,需要把网页中的一部分内容挑出来,于是找到了urllib和HTMLParser两个库.urllib可以将网页爬下来,然后交
- 安装完python之后,我们可以做两件事情,1.将安装目录中的Doc目录下的python331.chm使用手册复制到桌面上,方便学习和查阅2
- 在DOS界面运行python的py文件我用的Notepad++编写代码,编写完后需要在DOS界面运行打开DOS界面按键盘上的WIN+R,输入
- Python中生成器和迭代器的区别(代码在Python3.5下测试):Num01–>迭代器定义:对于list、string、tuple
- 1. 背景在软件需求、开发、测试过程中,有时候需要使用一些测试数据,针对这种情况,我们一般要么使用已有的系统数据,要么需要手动制造一些数据。
- Windows环境下python的安装与使用一、python如何运行程序首先说一下python解释器,它是一种让其他程序运行起来的程序。当你
- 直接分析,如原矩阵如下(1): (1) 我们要截取的矩阵(取其一三行,和三四列数据构成矩阵)为如下(2): (2)错
- 具体代码和实现方法见下:第一个办法,这个程序可以进行万亿以下的货币金额转换(够用的了吧),其中汉字与数字均按一位计:Function&nbs
- 什么是并发安全?在高并发场景下,进程、线程(协程)可能会发生资源竞争,导致数据脏读、脏写、死锁等问题,为了避免此类问题的发生,就有了并发安全