golang教程之反射

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wyy626562203/article/details/83411703

反射

原文:https://golangbot.com/reflection/

在这里插入图片描述
反射是Go的高级主题之一。

什么是反射?

反射是程序在运行时检查其变量和值并找到其类型的能力。你可能不明白这意味着什么,但没关系。在本教程结束时,您将清楚地了解反射。

检查变量并找到其类型需要什么?

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

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

package main

import (  
    "fmt"
)

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

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

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

思考以下程序,

package main

import (  
    "fmt"
)

type order struct {  
    ordId      int
    customerId int
}

func main() {  
    o := order{
        ordId:      1234,
        customerId: 567,
    }
    fmt.Println(o)
}

我们需要编写一个函数,它将上面程序中的struct 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))
}

第12行中的createQuery函数使用oordIdcustomerId字段创建插入查询。 该程序将输出,

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

}

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

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

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{}作为参数。为简单起见,我们只处理包含stringint类型字段的结构,但这可以扩展为任何类型。

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

反射包

反射包在Go中实现运行时反射。反射包有助于识别底层具体类型和interface {}变量的值。这正是我们所需要的。 createQuery函数采用interface {}参数,需要根据interface {}参数的具体类型和值创建查询。这正是反射包有助于实现的目的。

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

reflect.Type和reflect.Value

interface{}的具体类型由reflect.Type表示,底层值由reflect.Value表示。有两个函数reflect.TypeOf()reflect.ValueOf(),它们分别返回reflect.Typereflect.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)

}

在上面的程序中,createQuery函数以 interface{} 作为参数。 函数reflect.TypeOf将 interface{} 作为参数,并返回包含传递的 interface{} 参数的具体类型的reflect.Type。 类似,reflect.ValueOf函数interface {}作为参数并返回reflect.Value,其中包含传递的interface {}参数的基础值。

以上程序打印,

Type  main.order  
Value  {456 56}  

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

reflect.Kind

反射包中有一个重要的类型名为Kind

反射包中的KindType可能看起来相似,但它们之间存在差异,这将从下面的程序中清楚地看出。

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)

}

上面的程序输出,

Type  main.order  
Kind  struct 

我想你现在会清楚两者之间的差异。 Type表示 interface{}的实际类型,在这种情况下,main.OrderKind表示类型的特定种类。 在这种情况下,它是一个结构。

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

在上面的程序中,我们首先检查q是否是 Kind struct ,因为NumField方法仅适用于struct。该程序输出,

Number of fields 2  
Field:0 type:reflect.Value value:456  
Field:1 type:reflect.Value value:56  

Int()和String()方法

IntString方法有助于将reflect.Value分别提取为int64string

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)

}

在上面的程序中,我们将reflect.Value提取为int64并在第13行中把它作为字符串提取出来。 这个程序打印,

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)

}

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

第28行检查当前字段是否为reflect.Int,如果是这种情况,我们使用Int()方法将该字段的值提取为int64if else语句用于处理边缘情况。 请添加日志以了解为何需要它。 类似的逻辑用于提取第34行中的字符串。

我们还添加了一些检查,以防止在将不支持的类型传递给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关于使用反射来回答这个问题的谚语。

Clear is better than clever.Reflection is never clear.

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

猜你喜欢

转载自blog.csdn.net/wyy626562203/article/details/83411703
今日推荐