Go 中的反射

news/2024/10/25 11:21:32/

本教程有以下部分。

  • 什么是反射?
  • 需要检查变量并查找其类型吗?
  • 反射包
    • 反射类型和反射值
    • 反射.Kind
    • NumField() 和 Field() 方法
    • Int() 和 String() 方法
  • 完整的程序
  • 是否应该使用反射?

现在让我们一一讨论这些部分。

什么是反射?

反射是程序在运行时检查其变量和值并查找其类型的能力。您可能不明白这意味着什么,但没关系。在本教程结束时,您将对反射有一个清晰的了解,所以请继续关注我。

需要检查变量并查找其类型吗?

在学习反射时,任何人遇到的第一个问题是,当程序中的每个变量都是由我们定义并且我们在编译时本身知道其类型时,为什么我们甚至需要在运行时检查变量并查找其类型。嗯,大多数时候都是如此,但并非总是如此。

让我解释一下我的意思。我们来写一个简单的程序。

package mainimport ("fmt"
)func main() {i := 10fmt.Printf("%d %T", i, i)
}

在上面的程序中,i 的类型在编译时已知,我们在下一行中打印它。这里没什么神奇的。

现在让我们了解在运行时了解变量类型的必要性。假设我们想要编写一个简单的函数,它将接受一个结构体作为参数,并使用它创建一个 SQL 插入查询。

考虑以下程序,

package mainimport ("fmt"
)type order struct {ordId      intcustomerId int
}func main() {o := order{ordId:      1234,customerId: 567,}fmt.Println(o)
}

Run in playground

我们需要编写一个函数,将上面程序中的o结构体作为参数并返回以下 SQL 插入查询,

insert into order values(1234, 567)

这个函数写起来很简单。我们现在就这样做吧。

package mainimport ("fmt"
)type order struct {ordId      intcustomerId 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))
}

Run in playground

第 12 行中的函数createQuery。 使用 order创建插入。该程序将输出

insert into order values(1234, 567)

现在让我们将查询创建器提升到一个新的水平。如果我们想概括我们的查询创建器并使其适用于任何结构,该怎么办?让我解释一下使用程序的含义。

package maintype order struct {ordId      intcustomerId int
}type employee struct {name stringid intaddress stringsalary intcountry string
}func createQuery(q interface{}) string {
}func main() {}

我们的目标是完成上述程序第 16 行中的createQuery函数,以便它将 any 作为参数并基于结构字段创建插入查询。

例如,如果我们传递下面的结构

1o := order {
2	ordId: 1234,
3	customerId: 567
4}

我们的createQuery函数应该返回,

insert into order values (1234, 567)

同样如果我们通过

1 e := employee {
2        name: "Naveen",
3        id: 565,
4        address: "Science Park Road, Singapore",
5        salary: 90000,
6        country: "Singapore",
7    }

它应该返回,

insert into employee values("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore")

由于该createQuery函数应该适用于任何结构,因此它采用**interface{}**作为参数。为简单起见,我们将仅处理包含类型字段的结构stringint但这可以扩展到任何类型。

createQuery函数应该适用于任何结构。编写此函数的唯一方法是检查运行时传递给它的结构参数的类型,找到其字段,然后创建查询。这就是反射有用的地方。在本教程的后续步骤中,我们将学习如何使用该reflect包来实现这一目标。

反射包

Reflect包实现了 Go 中的运行时反射。**Reflect 包有助于识别底层的具体类型和interface{}**变量的值。这正是我们所需要的。该createQuery函数接受一个interface{}参数,并且需要根据参数的具体类型和值创建查询interface{}。这正是 Reflect 包可以帮助完成的事情。

在编写通用查询生成器程序之前,我们需要首先了解 Reflect 包中的一些类型和方法。让我们一一看看。

反射类型和反射值

具体类型由reflect.Typeinterface{}表示,底层值由reflect.Value表示 。有两个函数reflect.TypeOf()和reflect.ValueOf()分别返回和。这两种类型是创建查询生成器的基础。让我们写一个简单的例子来理解这两种类型。

package mainimport ("fmt""reflect"
)type order struct {ordId      intcustomerId 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)}

Run in playground

在上面的程序中,第 13 行中的 createQuery 函数将 interface{} 作为参数。在第 14 行中的 TypeOf 将 interface{} 作为参数并返回 reflect。包含传递的 interface{} 参数的具体类型的类型。同样第 15 行中的 ValueOf 函数将 interface{} 作为参数并返回 reflect。Value 包含传递的 interface{} 参数的基础值。

上面的程序打印出,

Type  main.order
Value  {456 56}

从输出中,我们可以看到程序打印了接口的具体类型和值。

反射.Kind

反射包中有一种更重要的类型,称为Kind。

反射包中的类型KindType可能看起来相似,但它们有一个区别,从下面的程序中可以清楚地看出。

package mainimport ("fmt""reflect"
)type order struct {ordId      intcustomerId 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)}

Run in playground

上面的程序输出,

Type  main.order
Kind  struct

我想您现在应该清楚两者之间的区别了。Type表示interface{}的实际类型,在本例中为main.Order,并Kind表示该类型的具体种类。在本例中,它是一个struct

NumField() 和 Field() 方法

NumField() 方法返回结构中的字段数,Field(i int) 方法返回第 th 个字段的字段数。

package mainimport ("fmt""reflect"
)type order struct {ordId      intcustomerId 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 < v.NumField(); i++ {fmt.Printf("Field:%d type:%T value:%v\n", i, v.Field(i), v.Field(i))}}}
func main() {o := order{ordId:      456,customerId: 56,}createQuery(o)
}

Run in playground

在上面的程序中,在第 14 行中,我们首先检查 of 是否为 a,因为该方法仅适用于结构。程序的其余部分是不言自明的。该程序输出

Number of fields 2
Field:0 type:reflect.Value value:456
Field:1 type:reflect.Value value:56
Int() 和 String() 方法

Int和String方法分别帮助将 提取reflect.Valueint64string

 1package main23import (4	"fmt"5	"reflect"6)78func main() {9	a := 56
10	x := reflect.ValueOf(a).Int()
11	fmt.Printf("type:%T value:%v\n", x, x)
12	b := "Naveen"
13	y := reflect.ValueOf(b).String()
14	fmt.Printf("type:%T value:%v\n", y, y)
15
16}

Run in playground

该程序打印

type:int64 value:56
type:string value:Naveen

完整的程序

现在我们已经有了足够的知识来完成我们的查询生成器,让我们继续吧。

package mainimport ("fmt""reflect"
)type order struct {ordId      intcustomerId int
}type employee struct {name    stringid      intaddress stringsalary  intcountry 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 < v.NumField(); i++ {switch v.Field(i).Kind() {case reflect.Int:if i == 0 {query = fmt.Sprintf("%s%d", query, v.Field(i).Int())} else {query = fmt.Sprintf("%s, %d", query, v.Field(i).Int())}case reflect.String:if i == 0 {query = fmt.Sprintf("%s\"%s\"", query, v.Field(i).String())} else {query = fmt.Sprintf("%s, \"%s\"", query, v.Field(i).String())}default:fmt.Println("Unsupported type")return}}query = fmt.Sprintf("%s)", query)fmt.Println(query)return}fmt.Println("unsupported type")
}func main() {o := order{ordId:      456,customerId: 56,}createQuery(o)e := employee{name:    "Naveen",id:      565,address: "Coimbatore",salary:  90000,country: "India",}createQuery(e)i := 90createQuery(i)}

Run in playground

在第 22 行中,我们首先检查传递的参数是否为 .在第 23 行中,我们使用该方法从其中获取结构的名称。在下一行中,我们使用并开始创建查询。

28行 检查当前字段是否为reflect.Int,如果是的话,我们int64使用该Int()方法提取该字段的值。if else语句用于处理边缘情况。请添加日志以了解为什么需要它。类似的逻辑用于提取string行号。

我们还添加了检查,以防止程序在将不受支持的类型传递给函数时崩溃。程序的其余部分是不言自明的。我建议在适当的位置添加日志并检查其输出以更好地理解此程序。

该程序打印,

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)

是否应该使用反射?

在展示了反思的实际用途之后,现在出现了真正的问题。你应该使用反射吗?我想引用罗伯·派克关于使用反射的谚语来回答这个问题。

清晰胜于聪明。 反思从来都不是清晰的。

反射是 Go 中一个非常强大和先进的概念,应该谨慎使用。使用反射编写清晰且可维护的代码非常困难。应尽可能避免使用,并且仅在绝对必要时才使用。

本教程到此结束。希望你喜欢它。祝你有美好的一天。


http://www.ppmy.cn/news/1200025.html

相关文章

办公套件全家桶 Office2019 mac中文版新功能

office 2019 mac是 Microsoft office 应用程序套件的最新版本。它包括流行的软件&#xff0c;例如 Microsoft Word、Excel、PowerPoint 和 Outlook&#xff0c;office 2019 比其前身有许多新功能和改进&#xff0c;包括增强的协作工具、与 OneDrive 和 SharePoint 等云服务的更…

由一个自动化脚本运维展开的思考

今天分享一个思路&#xff0c;如何通过脚本集中管理程序的启停。减少人工的介入。 例子 好的&#xff0c;这里有一个基本的shell脚本示例&#xff0c;你可以根据你的具体需求进行修改。 启动脚本&#xff08;start.sh&#xff09;&#xff1a; #!/bin/bash ./test_server_1…

ACTIVE_MQ学习

ActiveMq学习①___入门概述https://blog.csdn.net/qq_45905724/article/details/131796502 ActiveMq学习②__安装与控制台https://blog.csdn.net/qq_45905724/article/details/133893214 ActiveMq学习③___Java编码实现ActiveMQ通讯https://blog.csdn.net/qq_45905724/articl…

每个程序员都应该自己写一个的:socket包装类

每个程序员都应该有自己的网络类。 下面是我自己用的socket类&#xff0c;支持所有我自己常用的功能&#xff0c;支持windows和unix/linux。 目录 客户端 服务端 非阻塞 获取socket信息 完整代码 客户端 作为socket客户端&#xff0c;只需要如下几个功能&#xff1a; //…

SM5102 3.7V 锂电池转干电池充放管理芯片

SM5102 3.7V 锂电池转干电池充放管理芯片 简介 &#xff1a; SM5102 是一款锂电池充放电管理专用芯片。充电工作时, 可以为 3.7V 锂电池进行充电&#xff0c;电流最高可配置1A。放电工作时&#xff0c;采用开关频率 1MHz同步降压转换器进行放电&#xff0c;放电电流可以达到3…

CATIA环境编辑器用不了时创建项目快捷方式

CATIA环境编辑器用不了时创建项目快捷方式 一、参考适用情况示例二、 解决步骤(一) 先正确放置winb_64部署包(二) 添加环境文件(三) 修改加入的环境文件(四) 复制本机CATIA快捷方式后重命名(五) 修改快捷方式目标的值 一、参考适用情况示例 二、 解决步骤 (一) 先正确放置winb…

分享与SOHO朋友聊天的一些经验

昨天跟一个同是SOHO的朋友见面聊了一下&#xff0c;她说自己的困境是不知道如何提升工作效率&#xff0c;感觉一个人自律性太差了&#xff0c;甚至有时候客户来找都提不起精神来跟进&#xff0c;需要抱团&#xff0c;希望能一起相互鼓励一下&#xff0c;问我有没有什么方法&…

Mechanize

Mechanize是一个Python库&#xff0c;用于模拟浏览器行为&#xff0c;实现自动化网页操作和数据提取。它提供了一种简单而方便的方式来处理表单提交、点击链接、浏览网页和提取数据等操作。 使用Mechanize库&#xff0c;您可以编写脚本来自动登录网站、提交表单、爬取网页内容…