反射是指在程式執行過程中對程式本身進行訪問和修改的能力。程式在編譯時,變數被轉換為記憶體位址,變數名不會被編譯器寫入到可執行部分。在執行程式時,程式無法獲取自身資訊。
支援反射的語言可以在程式編譯期將變數的反射資訊,如欄位名稱、型別資訊、結構體資訊等整合到可執行檔案中,並給程式提供介面的訪問反射資訊,這樣就可以在程式執行期獲取型別的反射資訊,並且有能力修改他們。
go語言程式在執行期使用reflect包訪問程式的反射資訊。
使用:空介面可以儲存任意型別的變數,通過反射在執行時動態獲取乙個變數的型別資訊和值資訊。
reflect
包
在go語言的反射機制中,任何介面值都是由乙個具體型別和具體型別的值兩部分組成的。在go語言中反射的相關功能由內建的reflect包提供,任意介面在反射中都可以理解為由reflect.type
和reflect.value
兩部分組成,並且reflect包提供了(型別)reflect.typeof()
和type.valueof()
(值)兩個函式來獲取任意物件的value和type。
typeof
go語言中,使用reflect.typeof()
函式可以獲得任意值型別的物件(reflect.type),程式通過型別物件可以訪問任意值的型別資訊。
type name 和 type kind
在反射中關於型別還劃分為兩種:型別 type
和種類 kind
。在go語言中我們可以使用type關鍵字構造很多自定義型別,而種類(kind)
就是指底層的型別,但在反射中,當需要區分指標、結構體等大品種的型別時,就會使用種類(kind)
package main
import
("fmt"
"reflect"
)type cat struct
func
reflecttype
(x inte***ce
)func
main()
reflecttype
(cat)
// type: main.cat
// type name: cat, type kind: struct
}
valueof
reflect.valueof()
返回的是reflect.value
型別,包含了原始值的資訊。reflect.value
與原始值直接可以互換。
reflect.value
型別提供的獲取原始值的方法如下
方法說明
inte***ce() inte***ce{}
將值以inte***ce{}型別返回,可以通過型別斷言轉化為相應的型別
int() int64
將值以int型別返回,所有有符號整型均以此形式返回
unit() unit64
將值以uint型別返回,所有無符號整型均以此方式返回
float() float64
將值以雙精度float64型別返回,所有浮點數均以次方式返回
bool() bool
將值以boo型別返回
bytes() bytes
將值以位元組陣列bytes型別返回
string() string
將值以字串型別返回
package main
import
("fmt"
"reflect"
)func
reflectvalue
(x inte***ce)}
func
main()
通過反射設定變數的值
想要在函式中通過反射修改變數的值,需要注意函式傳遞的是值拷貝,必須借助變數位址才能修改變數的值。而反射中使用專有的elem()
方法來獲取指標對應的值。
package main
import
("fmt"
"reflect"
)func
reflectvalueint
(x inte***ce)}
func
reflectvalueptr
(x inte***ce)}
func
main()
isnil()func
(v value)
isnil()
bool
// 報告持有的值是否為nil。v持有的值的分類必須為通道、函式、介面、對映、指標、切片之一;否則報panic
isvalid()func
(v value)
isvalid()
bool
// 返回v是否持有乙個值。如果v是value零值會返回假,此時v除了isvalid、string、kind之外的方法都會報panic
isnil()
常用於判斷指標是否為空;isvalid()
常用於判定返回值是否有效。
func
main()
// 嘗試從結構體中查詢"abc"字段
fmt.
println
("不存在結構體成員:"
, reflect.
valueof
(b).
fieldbyname
("abc").
isvalid()
)// 嘗試從結構體中查詢"abc方法"
fmt.
println
("不存在結構體方法:"
, reflect.
valueof
(b).
methodbyname
("abc").
isvalid()
)// map
c :=
map[
string
]int
// 嘗試從map中查詢乙個不存在的鍵
fmt.
println
("map中不存在的鍵:", reflect.
valueof
(c).
mapindex
(reflect.
value
("abc"))
.isvalid()
)}
結構體反射
任意值通過reflect.typeof()
獲得反射物件資訊後,如果它的型別是結構體,可以通過反射值物件reflect.type
的numfield
和fileld()
方法獲得結構體成員的詳細資訊。
方法說明
field(i int) structfield
根據索引,返回索引對應的結構體字段的資訊
numfield() int
返回結構體成員欄位的數量
fieldbyname(name string) (structfield, bool)
根據給定字串返回字串對應的結構體字段的資訊
fieldbyindex(index int) structfield
多層成員訪問時,根據int提供的每個結構體的字段索引,返回欄位的資訊
fieldbynamefunc(match func(string) bool) (structfield, bool)
根據傳入的函式匹配需要的字段
nummethod() int
返回該型別的方法集中方法的數目
method(int) method
返回該型別方法集中的第i的方法
methodbyname(string) (method, bool)
根據方法名返回該型別方法集中的方法
structfield型別
structfield
型別用來描述結構體中的乙個欄位的資訊。
structfield的定義如下:
type structfield struct
例:
package main
import
("fmt"
"reflect"
)type student struct
func
main()
t := reflect.
typeof
(stu)
fmt.
println
(t.name()
, t.
kind()
)// student struct
// 通過for迴圈遍歷結構體的所有字段資訊
for i:=
0; i < t.
numfield()
; i++
// 通過欄位名獲取指定結構體欄位資訊
if scorefiled, ok := t.
fieldbyname
("score"
); ok
}
反射的利弊
反射是乙個強大並富有表現力的工具,能讓我們寫出更靈活的**。但是反射不能被濫用,原因:
基於反射的**是極其脆弱的,反射中的型別錯誤會在真正執行時才會引發panic,那很可能在**寫完後的很長時間之後
大量使用反射的**通常難以理解
反射的效能十分低下,基於反射實現的**通常比正常的**執行速度慢一到兩個數量級
Go語言反射之值反射
反射不僅可以獲取值的型別資訊,還可操作變數的值。使用reflect.value型別操作變數的值。v make int,10 valuev reflect.valueof v fmt.println valuev.isvalid true fmt.println valuev.isnil false ...
GO語言練習 反射
列舉幾個反射的例子 1 簡單型別反射,2 複雜型別反射,3 對反射回來的資料的可修改屬性 1 簡單型別反射 1.1 package main import fmt reflect func main 1.2 執行結果 go run reflect.go type float64 kind is fl...
Go語言反射3
任意值通過 reflect.typeof 獲得反射物件資訊後,如果他的型別是結構體,可以通過反射值物件 reflect.type 的numfield 和field 方法獲得結構體成員的詳細方法。reflect.type 中與獲取結構體成員相關的方法如下表所示 方法說明 field i int str...