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!
備注
本文系翻譯之作原文博客地址