Go教程第二十五篇:Go中的反射

Go中的反射

本文是《Go系列教程》的第二十六篇文章。

反射是Go的高級特性之一。我將盡力講解地簡單些。

本教程具有下面幾個(gè)章節(jié):

  • 什么是反射?

  • 考察一個(gè)變量,并且尋找它的類型的需要是什么?

  • 反射包

    . reflect.Type 和 reflect.Value

    . reflect.Kind

    . NumField() 和 Field() methods

    . Int() 和 String() methods

  • 完整的程序

  • 應(yīng)該使用反射嗎?

我們將對這些內(nèi)容一個(gè)個(gè)地加以討論。

什么是反射?

反射指的是一個(gè)程序可以在運(yùn)行時(shí)檢查變量以及它的值并查找他們的類型。你可能不太理解這是什么意思,不過,沒事。在讀完本文章之后,你會有一個(gè)清晰的理解。來跟著我一探究竟。

檢查變量并查找其類型的需要是什么?

許多人在學(xué)習(xí)反射的時(shí)候,都有一個(gè)問題,那就是為什么我們需要在運(yùn)行時(shí)期檢查變量,查找它的類型呢。我們程序中的每一個(gè)變量都是由我們自己定義的。我們在編譯期就知道它的類型。確實(shí),在大多數(shù)情況下是這樣,但并不是所有時(shí)候都是如此。

我們寫個(gè)簡單的程序,來解釋一下。

package main

import (
    "fmt"
)

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

在上面這個(gè)程序中,i的類型在編譯時(shí)期都已經(jīng)知曉了,我們在一行代碼中,又把它打印了出來。這沒有什么難以琢磨的地方。

現(xiàn)在,我們來理解一下,在運(yùn)行期間獲知一個(gè)變量的類型的必要。我們寫個(gè)簡單的函數(shù),該函數(shù)會接收一個(gè)結(jié)構(gòu)體參數(shù),并用它創(chuàng)建一個(gè)SQL插入語句。

考慮一下,下面這個(gè)程序。

package main

import (
    "fmt"
)

type order struct {
    ordId      int
    customerId int
}

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

我們需要寫一個(gè)函數(shù),它接收一個(gè)結(jié)構(gòu)體o作為參數(shù),并返回下面這個(gè)SQL語句。

insert into order values(1234, 567)

這個(gè)函數(shù)很好寫,我們來寫一下。

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函數(shù)使用o的ordId字段和customerId字段創(chuàng)建了一個(gè)插入語句。程序的輸出如下:

insert into order values(1234, 567)

此時(shí),我們讓這個(gè)函數(shù)更高級一點(diǎn)。如果我們想讓這個(gè)函數(shù)可以接收任何的結(jié)構(gòu)體,并根據(jù)結(jié)構(gòu)體生成SQL語句該怎么辦。我用程序來解釋一下。

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

}

我們的目標(biāo)是完成createQuery函數(shù),讓它可以接收任何的結(jié)構(gòu)體作為參數(shù),并基于結(jié)構(gòu)體的字段生成相應(yīng)的SQL語句。
例如,我們把下面這個(gè)結(jié)構(gòu)體作為參數(shù)傳遞過去。

o := order {
    ordId: 1234,
    customerId: 567
}

那么,我們的createQuery函數(shù)就應(yīng)該返回:

insert into order values (1234, 567)

類似地,如果我們傳遞了下面這個(gè)結(jié)構(gòu)體:

e := employee {
       name: "Naveen",
       id: 565,
       address: "Science Park Road, Singapore",
       salary: 90000,
       country: "Singapore",
   }

它應(yīng)該返回:

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

既然createQuery函數(shù)應(yīng)該接受任意的結(jié)構(gòu)體參數(shù)的話,那也就意味著,它接收一個(gè)接口作為參數(shù)。簡單起見,我們只處理含有string和int類型的字段。

createQuery可以處理所有的結(jié)構(gòu)體,那么編寫此函數(shù)的唯一的方式就是在運(yùn)行時(shí)檢查此結(jié)構(gòu)體的類型,并找到它都包含哪些字段,據(jù)此創(chuàng)建SQL語句。這時(shí)候,反射就十分有用了。
下一步,我們就要學(xué)習(xí)下如何使用反射包來實(shí)現(xiàn)這個(gè)需求。

反射包

Go的反射包實(shí)現(xiàn)了運(yùn)行時(shí)反射。反射包可用于識別底層的具體數(shù)據(jù)類型,以及一個(gè)接口變量的值。而這正是我們所需要的。createQuery函數(shù)會接收一個(gè)接口參數(shù),基于接口的值以及它的具體類型,我們會創(chuàng)建對應(yīng)的SQL語句。反射包正好能幫助我們做到這一點(diǎn)。

在寫我們的程序之前,我們要先了解一下反射包中的幾個(gè)類以及相關(guān)的方法。我們先一個(gè)個(gè)看下。

reflect.Type 和reflect.Value

reflect.Type可以代表interface{}的具體類型,reflect.Value可以代表它的值。有倆個(gè)函數(shù) reflect.TypeOf()和reflect.ValueOf()他們各自返回了reflect.Type和reflect.Value。
我們來寫段程序理解下這倆個(gè)類型。

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)

}

上面的這個(gè)程序中,createQuery函數(shù)接收了一個(gè)interface{}作為參數(shù)。reflect.TypeOf(q)接收一個(gè)interface{}參數(shù),并返回它的此接口的具體數(shù)據(jù)類型。同樣, reflect.ValueOf 接收了一個(gè)interface{}作為參數(shù),也返回了此接口參數(shù)的實(shí)際值。
程序輸出如下:

Type  main.order
Value  {456 56}

從上面的輸出中,可以看到程序可以打印出接口的對應(yīng)的實(shí)際類型以及實(shí)際值。

reflect.Kind

反射包中還有一個(gè)重要的類,那就是Kind。

反射包中的Kind和Type看著很像,其實(shí)他們還是有些不同,我們從下面這個(gè)程序中就可以看出來。

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

我想現(xiàn)在你這時(shí)已經(jīng)很清楚地知道了這倆個(gè)的不同之處。Type代表的是接口的實(shí)際類型,這里即:main.Order,而Kind代表的是類型的種類,此處即為:struct。

NumField()和Field()方法

NumFiled()方法會返回結(jié)構(gòu)體中的字段的個(gè)數(shù),而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是一個(gè)struct,因?yàn)镹umFiled方法只對strcut有用。程序的其余部分都比較好理解。程序的輸出如下:

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

Int()和String()方法

Int方法和String()方法可用以提取int64和string的reflect.Value的值。

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)

}

在上面的程序中,我們提取了int64的reflect.Value的值,以及String的reflect.Value的值。程序輸出如下:

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

完整的程序

現(xiàn)在我們已經(jīng)知道了如何使用反射,那么我們就來完成我們此前的程序。

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)

}

程序的輸出如下:

insert into order values(456, 56)
insert into employee values("Naveen", 565, "Coimbatore", 90000, "India")
unsupported type

應(yīng)該使用反射嗎?

我們已經(jīng)展示了如何在實(shí)際中使用反射?,F(xiàn)在有一個(gè)問題。我們應(yīng)該使用反射嗎?這里我引用一下Rob Pike的格言:

Clear is better than clever. Reflection is never clear.

反射很強(qiáng)大,也是Go的一個(gè)高級的特性。用它的時(shí)候需要特別小心。如果使用反射的話,我們寫的代碼就不是那么好理解,好維護(hù)。我們應(yīng)盡力避免使用反射,除非必要時(shí)。

感謝您的閱讀,請留下您珍貴的反饋和評論。Have a good Day!

備注
本文系翻譯之作原文博客地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容