Go系列教程之反射的用法
来源:脚本之家
时间:2023-01-07 12:14:18 155浏览 收藏
本篇文章向大家介绍《Go系列教程之反射的用法》,主要包括反射,具有一定的参考价值,需要的朋友可以参考一下。
反射是 Go 语言的高级主题之一。我会尽可能让它变得简单易懂。
本教程分为如下小节。
- 什么是反射?
- 为何需要检查变量,确定变量的类型?
- reflect 包
- reflect.Type 和 reflect.Value
- reflect.Kind
- NumField() 和 Field() 方法
- Int() 和 String() 方法
- 完整的程序
- 我们应该使用反射吗?
让我们来逐个讨论这些章节。
什么是反射?
反射就是程序能够在运行时检查变量和值,求出它们的类型。你可能还不太懂,这没关系。在本教程结束后,你就会清楚地理解反射,所以跟着我们的教程学习吧。
为何需要检查变量,确定变量的类型?
在学习反射时,所有人首先面临的疑惑就是:如果程序中每个变量都是我们自己定义的,那么在编译时就可以知道变量类型了,为什么我们还需要在运行时检查变量,求出它的类型呢?没错,在大多数时候都是这样,但并非总是如此。
我来解释一下吧。下面我们编写一个简单的程序。
package main import ( "fmt" ) func main() { i := 10 fmt.Printf("%d %T", i, i) }
在 playground 上运行
在上面的程序中,i 的类型在编译时就知道了,然后我们在下一行打印出 i。这里没什么特别之处。
现在了解一下,需要在运行时求得变量类型的情况。假如我们要编写一个简单的函数,它接收结构体作为参数,并用它来创建一个 SQL 插入查询。
考虑下面的程序:
package main import ( "fmt" ) type order struct { ordId int customerId int } func main() { o := order{ ordId: 1234, customerId: 567, } fmt.Println(o) }
在 playground 上运行
在上面的程序中,我们需要编写一个函数,接收结构体变量 o 作为参数,返回下面的 SQL 插入查询。
insert into order values(1234, 567)
这个函数写起来很简单。我们现在编写这个函数。
package main import ( "fmt" ) type order struct { ordId int customerId int } func createQuery(o order) string { i := fmt.Sprintf("insert into order values(%d, %d)", o.ordId, o.customerId) return i } func main() { o := order{ ordId: 1234, customerId: 567, } fmt.Println(createQuery(o)) }
在 playground 上运行
在第 12 行,createQuery 函数用 o 的两个字段(ordId 和 customerId),创建了插入查询。该程序会输出:
insert into order values(1234, 567)
现在我们来升级这个查询生成器。如果我们想让它变得通用,可以适用于任何结构体类型,该怎么办呢?我们用程序来理解一下。
package main type order struct { ordId int customerId int } type employee struct { name string id int address string salary int country string } func createQuery(q interface{}) string { } func main() { }
我们的目标就是完成 createQuery 函数(上述程序中的第 16 行),它可以接收任何结构体作为参数,根据结构体的字段创建插入查询。
例如,如果我们传入下面的结构体:
o := order { ordId: 1234, customerId: 567 }
createQuery 函数应该返回:
insert into order values (1234, 567)
类似地,如果我们传入:
e := employee { name: "Naveen", id: 565, address: "Science Park Road, Singapore", salary: 90000, country: "Singapore", }
该函数会返回:
insert into employee values("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore")
由于 createQuery 函数应该适用于任何结构体,因此它接收 interface{} 作为参数。为了简单起见,我们只处理包含 string 和 int 类型字段的结构体,但可以扩展为包含任何类型的字段。
createQuery 函数应该适用于所有的结构体。因此,要编写这个函数,就必须在运行时检查传递过来的结构体参数的类型,找到结构体字段,接着创建查询。这时就需要用到反射了。在本教程的下一步,我们将会学习如何使用 reflect 包来实现它。
reflect 包
在 Go 语言中,reflect 实现了运行时反射。reflect 包会帮助识别 interface{} 变量的底层具体类型和具体值。这正是我们所需要的。createQuery 函数接收 interface{} 参数,根据它的具体类型和具体值,创建 SQL 查询。这正是 reflect 包能够帮助我们的地方。
在编写我们通用的查询生成器之前,我们首先需要了解 reflect 包中的几种类型和方法。让我们来逐个了解。
reflect.Type 和 reflect.Value
reflect.Type 表示 interface{} 的具体类型,而 reflect.Value 表示它的具体值。reflect.TypeOf() 和 reflect.ValueOf() 两个函数可以分别返回 reflect.Type 和 reflect.Value。这两种类型是我们创建查询生成器的基础。我们现在用一个简单的例子来理解这两种类型。
package main import ( "fmt" "reflect" ) type order struct { ordId int customerId int } func createQuery(q interface{}) { t := reflect.TypeOf(q) v := reflect.ValueOf(q) fmt.Println("Type ", t) fmt.Println("Value ", v) } func main() { o := order{ ordId: 456, customerId: 56, } createQuery(o) }
在 playground 上运行
在上面的程序中,第 13 行的 createQuery 函数接收 interface{} 作为参数。在第 14 行,reflect.TypeOf 接收了参数 interface{},返回了reflect.Type,它包含了传入的 interface{} 参数的具体类型。同样地,在第 15 行,reflect.ValueOf 函数接收参数 interface{},并返回了 reflect.Value,它包含了传来的 interface{} 的具体值。
上述程序会打印:
Type main.order
Value {456 56}
从输出我们可以看到,程序打印了接口的具体类型和具体值。
relfect.Kind
reflect 包中还有一个重要的类型:Kind。
在反射包中,Kind 和 Type 的类型可能看起来很相似,但在下面程序中,可以很清楚地看出它们的不同之处。
package main import ( "fmt" "reflect" ) type order struct { ordId int customerId int } func createQuery(q interface{}) { t := reflect.TypeOf(q) k := t.Kind() fmt.Println("Type ", t) fmt.Println("Kind ", k) } func main() { o := order{ ordId: 456, customerId: 56, } createQuery(o) }
在 playground 上运行
上述程序会输出:
Type main.order
Kind struct
我想你应该很清楚两者的区别了。Type 表示 interface{} 的实际类型(在这里是 main.Order),而 Kind 表示该类型的特定类别(在这里是 struct)。
NumField() 和 Field() 方法
NumField() 方法返回结构体中字段的数量,而 Field(i int) 方法返回字段 i 的 reflect.Value。
package main import ( "fmt" "reflect" ) type order struct { ordId int customerId int } func createQuery(q interface{}) { if reflect.ValueOf(q).Kind() == reflect.Struct { v := reflect.ValueOf(q) fmt.Println("Number of fields", v.NumField()) for i := 0; i在 playground 上运行
在上面的程序中,因为 NumField 方法只能在结构体上使用,我们在第 14 行首先检查了 q 的类别是 struct。程序的其他代码很容易看懂,不作解释。该程序会输出:
Number of fields 2
Field:0 type:reflect.Value value:456
Field:1 type:reflect.Value value:56Int() 和 String() 方法
Int 和 String 可以帮助我们分别取出 reflect.Value 作为 int64 和 string。
package main import ( "fmt" "reflect" ) func main() { a := 56 x := reflect.ValueOf(a).Int() fmt.Printf("type:%T value:%v\n", x, x) b := "Naveen" y := reflect.ValueOf(b).String() fmt.Printf("type:%T value:%v\n", y, y) }在 playground 上运行
在上面程序中的第 10 行,我们取出 reflect.Value,并转换为 int64,而在第 13 行,我们取出 reflect.Value 并将其转换为 string。该程序会输出:
type:int64 value:56
type:string value:Naveen完整的程序
现在我们已经具备足够多的知识,来完成我们的查询生成器了,我们来实现它把。
package main import ( "fmt" "reflect" ) type order struct { ordId int customerId int } type employee struct { name string id int address string salary int country string } func createQuery(q interface{}) { if reflect.ValueOf(q).Kind() == reflect.Struct { t := reflect.TypeOf(q).Name() query := fmt.Sprintf("insert into %s values(", t) v := reflect.ValueOf(q) for i := 0; i在 playground 上运行
在第 22 行,我们首先检查了传来的参数是否是一个结构体。在第 23 行,我们使用了 Name() 方法,从该结构体的 reflect.Type 获取了结构体的名字。接下来一行,我们用 t 来创建查询。
在第 28 行,case 语句 检查了当前字段是否为 reflect.Int,如果是的话,我们会取到该字段的值,并使用 Int() 方法转换为 int64。if else 语句用于处理边界情况。请添加日志来理解为什么需要它。在第 34 行,我们用来相同的逻辑来取到 string。
我们还作了额外的检查,以防止 createQuery 函数传入不支持的类型时,程序发生崩溃。程序的其他代码是自解释性的。我建议你在合适的地方添加日志,检查输出,来更好地理解这个程序。
该程序会输出:
insert into order values(456, 56) insert into employee values("Naveen", 565, "Coimbatore", 90000, "India") unsupported type至于向输出的查询中添加字段名,我们把它留给读者作为练习。请尝试着修改程序,打印出以下格式的查询。
insert into order(ordId, customerId) values(456, 56)我们应该使用反射吗?
我们已经展示了反射的实际应用,现在考虑一个很现实的问题。我们应该使用反射吗?我想引用 Rob Pike 关于使用反射的格言,来回答这个问题。
清晰优于聪明。而反射并不是一目了然的。
反射是 Go 语言中非常强大和高级的概念,我们应该小心谨慎地使用它。使用反射编写清晰和可维护的代码是十分困难的。你应该尽可能避免使用它,只在必须用到它时,才使用反射。
本教程到此结束。希望你们喜欢。祝你愉快。希望对大家的学习有所帮助,也希望大家多多支持golang学习网。
终于介绍完啦!小伙伴们,这篇关于《Go系列教程之反射的用法》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布Golang相关知识,快来关注吧!
-
174 收藏
-
246 收藏
-
353 收藏
-
126 收藏
-
395 收藏
-
450 收藏
-
330 收藏
-
193 收藏
-
271 收藏
-
414 收藏
-
179 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习