golang教程之反射
文章目錄
反射
反射是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
函式使用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() {
}
我們的目標是在第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{}
作為引數。為簡單起見,我們只處理包含string
和int
型別欄位的結構,但這可以擴充套件為任何型別。
createQuery
函式應該適用於任何結構。編寫此函式的唯一方法是檢查在執行時傳遞給它的struct引數的型別,找到它的欄位然後建立查詢。這是反射有用的地方。在本教程的後續步驟中,我們將學習如何使用reflect
包實現此目的。
反射包
反射包在Go中實現執行時反射。反射包有助於識別底層具體型別和interface {}
變數的值。這正是我們所需要的。 createQuery
函式採用interface {}
引數,需要根據interface {}
引數的具體型別和值建立查詢。這正是反射包有助於實現的目的。
在編寫我們的通用查詢生成器程式之前,我們需要首先了解反射包中的一些型別和方法。讓我們逐一看看它們。
reflect.Type和reflect.Value
interface{}
的具體型別由reflect.Type
表示,底層值由reflect.Value
表示。有兩個函式reflect.TypeOf()
和reflect.ValueOf()
,它們分別返回reflect.Type
和reflect.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
。
反射包中的Kind
和 Type
可能看起來相似,但它們之間存在差異,這將從下面的程式中清楚地看出。
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.Order
和Kind
表示型別的特定種類。 在這種情況下,它是一個結構。
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()方法
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\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()
方法將該欄位的值提取為int64
。 if 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中一個非常強大和先進的概念,應該謹慎使用。 使用反射編寫清晰且可維護的程式碼非常困難。 應儘可能避免使用,並且只有在絕對必要時才應使用。