APP下载

GoLang -- 反射机制

消息来源:baojiabao.com 作者: 发布时间:2024-11-27

报价宝综合消息GoLang -- 反射机制

反射就是程式能够在执行时检查变数和值,求出它们的型别。

在学习反射时,所有人首先面临的疑惑就是:如果程式中每个变数都是我们自己定义的,那么在编译时就可以知道变数型别了,为什么我们还需要在执行时检查变数,求出它的型别呢?没错,在大多数时候都是这样,但并非总是如此。

package main

import (

"fmt"

)

func main() {

i := 10

fmt.Printf("%d %T", 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)

}

在上面的程式中,我们需要编写一个函式,接收结构体变数 o 作为引数,返回下面的 SQL 插入查询。

这个函式写起来很简单。我们现在编写这个函式。

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))

}

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 行),它可以接收任何结构体作为引数,根据结构体的字段建立插入查询。

• reflect 包

在 Go 语言中,reflect 实现了执行时反射。reflect 包会帮助识别 interface{} 变数的底层具体型别和具体值。这正是我们所需要的。createQuery 函式接收 interface{} 引数,根据它的具体型别和具体值,建立 SQL 查询。这正是 reflect 包能够帮助我们的地方。

在编写我们通用的查询生成器之前,我们首先需要了解 reflect 包中的几种型别和方法。让我们来逐个了解。

(1)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 {

orderId 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{

orderId: 456,

customerId: 56,

}

createQuery(o)

}

在上面的程式中,第 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 的型别可能看起来很相似,但在下面程式中,可以很清楚地看出它们的不同之处。

func createQuery(q interface{}) {

t := reflect.TypeOf(q)

v := reflect.ValueOf(q)

k := t.Kind()

fmt.Println("Type ", t)

fmt.Println("Value ", v)

fmt.Println("Kind ", k)

}

上述程式会输出:

Type main.order

Kind struct

我想你应该很清楚两者的区别了。Type 表示 interface{} 的实际型别(在这里是 main.Order),而 Kind 表示该型别的特定类别(在这里是 struct)。

• NumField() 和 Field() 方法

NumField() 方法返回结构体中字段的数量,而 Field(i int) 方法返回字段 i 的 reflect.Value。

func createQuery(q interface{}) {

if reflect.TypeOf(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 ", i, v.Field(i), v.Field(i))

}

}

}

• Int() 和 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 ", x, x)

b := "Naveen"

y := reflect.ValueOf(b).String()

fmt.Printf("type:%T value:%v ", y, y)

}

在上面程式中的第 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 < 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 := 90

createQuery(i)

}

我们首先检查了传来的引数是否是一个结构体。我们使用了 Name() 方法,从该结构体的 reflect.Type 获取了结构体的名字。接下来一行,我们用 t 来建立查询。

case 语句 检查了当前字段是否为 reflect.Int,如果是的话,我们会取到该字段的值,并使用 Int() 方法转换为 int64。if else 语句用于处理边界情况。之后,我们用来相同的逻辑来取到 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)

2020-07-22 02:52:00

相关文章