新四季網

如何優化go的反射(GCTT出品Go)

2023-04-16 23:30:41 2

歡迎來到 Golang 系列教程的第 34 篇。

反射是 Go 語言的高級主題之一。我會儘可能讓它變得簡單易懂。

本教程分為如下小節。

什麼是反射?為何需要檢查變量,確定變量的類型?reflect 包reflect.Type 和 reflect.Valuereflect.KindNumField 和 Field 方法Int 和 String 方法完整的程序我們應該使用反射嗎?

讓我們來逐個討論這些章節。

什麼是反射?

反射就是程序能夠在運行時檢查變量和值,求出它們的類型。你可能還不太懂,這沒關係。在本教程結束後,你就會清楚地理解反射,所以跟著我們的教程學習吧。

為何需要檢查變量,確定變量的類型?

在學習反射時,所有人首先面臨的疑惑就是:如果程序中每個變量都是我們自己定義的,那麼在編譯時就可以知道變量類型了,為什麼我們還需要在運行時檢查變量,求出它的類型呢?沒錯,在大多數時候都是這樣,但並非總是如此。

我來解釋一下吧。下面我們編寫一個簡單的程序。

在上面的程序中,i 的類型在編譯時就知道了,然後我們在下一行列印出 i。這裡沒什麼特別之處。

現在了解一下,需要在運行時求得變量類型的情況。假如我們要編寫一個簡單的函數,它接收結構體作為參數,並用它來創建一個 SQL 插入查詢。

考慮下面的程序:

在上面的程序中,我們需要編寫一個函數,接收結構體變量 o 作為參數,返回下面的 SQL 插入查詢。

insert into order values(1234, 567)

這個函數寫起來很簡單。我們現在編寫這個函數。

在第 12 行,createQuery 函數用 o 的兩個欄位(ordId 和 customerId),創建了插入查詢。該程序會輸出:

insert into order values(1234, 567)

現在我們來升級這個查詢生成器。如果我們想讓它變得通用,可以適用於任何結構體類型,該怎麼辦呢?我們用程序來理解一下。

我們的目標就是完成 createQuery 函數(上述程序中的第 16 行),它可以接收任何結構體作為參數,根據結構體的欄位創建插入查詢。

例如,如果我們傳入下面的結構體:

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

createQuery 函數應該返回:

insert into order values (1234, 567)

類似地,如果我們傳入:

該函數會返回:

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

由於 createQuery 函數應該適用於任何結構體,因此它接收 interface{} 作為參數。為了簡單起見,我們只處理包含 string 和 int 類型欄位的結構體,但可以擴展為包含任何類型的欄位。

createQuery 函數應該適用於所有的結構體。因此,要編寫這個函數,就必須在運行時檢查傳遞過來的結構體參數的類型,找到結構體欄位,接著創建查詢。這時就需要用到反射了。在本教程的下一步,我們將會學習如何使用 reflect 包來實現它。

reflect 包

在 Go 語言中,reflect 實現了運行時反射。reflect 包會幫助識別 interface{} 變量的底層具體類型和具體值。這正是我們所需要的。createQuery 函數接收 interface{} 參數,根據它的具體類型和具體值,創建 SQL 查詢。這正是 reflect 包能夠幫助我們的地方。

在編寫我們通用的查詢生成器之前,我們首先需要了解 reflect 包中的幾種類型和方法。讓我們來逐個了解。

reflect.Type 和 reflect.Value

reflect.Type 表示 interface{} 的具體類型,而 reflect.Value 表示它的具體值。reflect.TypeOf 和 reflect.ValueOf 兩個函數可以分別返回 reflect.Type 和 reflect.Value。這兩種類型是我們創建查詢生成器的基礎。我們現在用一個簡單的例子來理解這兩種類型。

在上面的程序中,第 13 行的 createQuery 函數接收 interface{} 作為參數。在第 14 行,reflect.TypeOf 接收了參數 interface{},返回了reflect.Type,它包含了傳入的 interface{} 參數的具體類型。同樣地,在第 15 行,reflect.ValueOf函數接收參數 interface{},並返回了 reflect.Value,它包含了傳來的 interface{} 的具體值。

上述程序會列印:

Type main.orderValue {456 56}

從輸出我們可以看到,程序列印了接口的具體類型和具體值。

relfect.Kind

reflect 包中還有一個重要的類型:Kind

在反射包中,Kind 和 Type 的類型可能看起來很相似,但在下面程序中,可以很清楚地看出它們的不同之處。

上述程序會輸出:

Type main.orderKind struct

我想你應該很清楚兩者的區別了。Type 表示 interface{} 的實際類型(在這裡是 main.Order),而 Kind 表示該類型的特定類別(在這裡是 struct)。

NumField 和 Field 方法

NumField 方法返回結構體中欄位的數量,而 Field(i int) 方法返回欄位 i 的 reflect.Value。

在上面的程序中,因為 NumField 方法只能在結構體上使用,我們在第 14 行首先檢查了 q 的類別是 struct。程序的其他代碼很容易看懂,不作解釋。該程序會輸出:

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

Int 和 String 方法

IntString 可以幫助我們分別取出 reflect.Value 作為 int64 和 string。

在上面程序中的第 10 行,我們取出 reflect.Value,並轉換為 int64,而在第 13 行,我們取出 reflect.Value 並將其轉換為 string。該程序會輸出:

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

完整的程序

現在我們已經具備足夠多的知識,來完成我們的查詢生成器了,我們來實現它把。

在第 22 行,我們首先檢查了傳來的參數是否是一個結構體。在第 23 行,我們使用了 Name 方法,從該結構體的 reflect.Type 獲取了結構體的名字。接下來一行,我們用 t 來創建查詢。

在第 28 行,case 語句 檢查了當前欄位是否為 reflect.Int,如果是的話,我們會取到該欄位的值,並使用 Int 方法轉換為 int64。if else 語句用於處理邊界情況。請添加日誌來理解為什麼需要它。在第 34 行,我們用來相同的邏輯來取到 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)

我們應該使用反射嗎?

我們已經展示了反射的實際應用,現在考慮一個很現實的問題。我們應該使用反射嗎?我想引用 Rob Pike 關於使用反射的格言,來回答這個問題。

清晰優於聰明。而反射並不是一目了然的。

反射是 Go 語言中非常強大和高級的概念,我們應該小心謹慎地使用它。使用反射編寫清晰和可維護的代碼是十分困難的。你應該儘可能避免使用它,只在必須用到它時,才使用反射。

本教程到此結束。希望你們喜歡。祝你愉快。

上一教程 - 「GCTT 出品」Go 系列教程——33. 函數是一等公民(頭等函數)

上一教程 - 讀取文件

歷史文章:

「GCTT 出品」Go 系列教程——1. 介紹與安裝

「GCTT 出品」Go 系列教程——2. Hello World

「GCTT 出品」Go 系列教程——3. 變量

「GCTT 出品」Go 系列教程——4. 類型

「GCTT 出品」Go 系列教程——5. 常量

「GCTT 出品」Go 系列教程——6. 函數(Function)

「GCTT 出品」Go 系列教程——7. 包

Go 系列教程——8. if-else 語句

「GCTT 出品」Go 系列教程——9. 循環

「GCTT 出品」Go 系列教程——10. switch 語句

「GCTT 出品」Go 系列教程——11. 數組和切片

「GCTT 出品」Go 系列教程——12. 可變參數函數

「GCTT 出品」Go 系列教程——13. Maps

「GCTT 出品」Go 系列教程——14. 字符串

「GCTT 出品」Go 系列教程——15. 指針

「GCTT 出品」Go 系列教程——16. 結構體,這一篇就夠

「GCTT 出品」Go 系列教程——17. 超全的方法教程

「GCTT 出品」Go 系列教程——18. 接口(一)

「GCTT 出品」Go 系列教程——19. 接口(二)

「GCTT 出品」Go 系列教程——20. 並發入門

「GCTT 出品」Go 系列教程——21. Go 協程

「GCTT 出品」Go 系列教程——22. 信道(channel)

「GCTT 出品」Go 系列教程——23. 緩衝信道和工作池

「GCTT 出品」Go 系列教程——24. Select

「GCTT 出品」Go 系列教程——25. Mutex

「GCTT 出品」Go 系列教程——26. 結構體取代類

「GCTT 出品」Go 系列教程——27. 組合取代繼承

「GCTT 出品」Go 系列教程——28. 多態

「GCTT 出品」Go 系列教程——29. Defer

「GCTT 出品」Go 系列教程——30. 錯誤處理

「GCTT 出品」Go 系列教程——31. 自定義錯誤

「GCTT 出品」Go 系列教程——32. panic 和 recover

「GCTT 出品」Go 系列教程——33. 函數是一等公民(頭等函數)

,
同类文章
葬禮的夢想

葬禮的夢想

夢見葬禮,我得到了這個夢想,五個要素的五個要素,水火只好,主要名字在外面,職業生涯良好,一切都應該對待他人治療誠意,由於小,吉利的冬天夢想,秋天的夢是不吉利的
找到手機是什麼意思?

找到手機是什麼意思?

找到手機是什麼意思?五次選舉的五個要素是兩名士兵的跡象。與他溝通很好。這是非常財富,它擅長運作,職業是仙人的標誌。單身男人有這個夢想,主要生活可以有人幫忙
我不怎麼想?

我不怎麼想?

我做了什麼意味著看到米飯烹飪?我得到了這個夢想,五線的主要土壤,但是Tu Ke水是錢的跡象,職業生涯更加真誠。他真誠地誠實。這是豐富的,這是夏瑞的巨星
夢想你的意思是什麼?

夢想你的意思是什麼?

你是什​​麼意思夢想的夢想?夢想,主要木材的五個要素,水的跡象,主營業務,主營業務,案子應該抓住魅力,不能疏忽,春天夢想的吉利夢想夏天的夢想不幸。詢問學者夢想
拯救夢想

拯救夢想

拯救夢想什麼意思?你夢想著拯救人嗎?拯救人們的夢想有一個現實,也有夢想的主觀想像力,請參閱週宮官方網站拯救人民夢想的詳細解釋。夢想著敵人被拯救出來
2022愛方向和生日是在[質量個性]中

2022愛方向和生日是在[質量個性]中

[救生員]有人說,在出生88天之前,胎兒已經知道哪天的出生,如何有優質的個性,將走在什麼樣的愛情之旅,將與生活生活有什么生活。今天
夢想切割剪裁

夢想切割剪裁

夢想切割剪裁什麼意思?你夢想切你的手是好的嗎?夢想切割手工切割手有一個真正的影響和反應,也有夢想的主觀想像力。請參閱官方網站夢想的細節,以削減手
夢想著親人死了

夢想著親人死了

夢想著親人死了什麼意思?你夢想夢想你的親人死嗎?夢想有一個現實的影響和反應,還有夢想的主觀想像力,請參閱夢想世界夢想死亡的親屬的詳細解釋
夢想搶劫

夢想搶劫

夢想搶劫什麼意思?你夢想搶劫嗎?夢想著搶劫有一個現實的影響和反應,也有夢想的主觀想像力,請參閱週恭吉夢官方網站的詳細解釋。夢想搶劫
夢想缺乏缺乏紊亂

夢想缺乏缺乏紊亂

夢想缺乏缺乏紊亂什麼意思?你夢想缺乏異常藥物嗎?夢想缺乏現實世界的影響和現實,還有夢想的主觀想像,請看官方網站的夢想組織缺乏異常藥物。我覺得有些東西缺失了