Golang中反射的常見用法分享
在之前的兩篇文章 《深入理解 go reflect - 反射基本原理》、《深入理解 go reflect - 要不要傳指針》 中, 我們講解了關(guān)于 go 反射的一些基本原理,以及通過(guò)反射對(duì)象修改變量的一些注意事項(xiàng)。 本篇文章將介紹一些常見的反射用法,涵蓋了常見的數(shù)據(jù)類型的反射操作。
根據(jù)類型做不同處理
使用反射很常見的一個(gè)場(chǎng)景就是根據(jù)類型做不同處理,比如下面這個(gè)方法,根據(jù)不同的 Kind 返回不同的字符串表示:
func getType(i interface{}) string {
v := reflect.ValueOf(i)
switch v.Kind() {
case reflect.Bool:
b := "false"
if v.Bool() {
b = "true"
}
return fmt.Sprintf("bool: %s", b)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return fmt.Sprintf("int: %d", v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return fmt.Sprintf("uint: %d", v.Uint())
case reflect.Float32, reflect.Float64:
return fmt.Sprintf("float: %.1f", v.Float())
case reflect.String:
return fmt.Sprintf("string: %s", v.String())
case reflect.Interface:
return fmt.Sprintf("interface: %v", v.Interface())
case reflect.Struct:
return fmt.Sprintf("struct: %v", v.Interface())
case reflect.Map:
return fmt.Sprintf("map: %v", v.Interface())
case reflect.Slice:
return fmt.Sprintf("slice: %v", v.Interface())
case reflect.Array:
return fmt.Sprintf("array: %v", v.Interface())
case reflect.Pointer:
return fmt.Sprintf("pointer: %v", v.Interface())
case reflect.Chan:
return fmt.Sprintf("chan: %v", v.Interface())
default:
return "unknown"
}
}
func TestKind(t *testing.T) {
assert.Equal(t, "int: 1", getType(1))
assert.Equal(t, "string: 1", getType("1"))
assert.Equal(t, "bool: true", getType(true))
assert.Equal(t, "float: 1.0", getType(1.0))
arr := [3]int{1, 2, 3}
sli := []int{1, 2, 3}
assert.Equal(t, "array: [1 2 3]", getType(arr))
assert.Equal(t, "slice: [1 2 3]", getType(sli))
}標(biāo)準(zhǔn)庫(kù) json 中的示例
在標(biāo)準(zhǔn)庫(kù) encoding/json 中,也有類似的場(chǎng)景,比如下面這個(gè)方法,根據(jù)不同的 Kind 做不同的處理:
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
// ... 其他代碼
switch t.Kind() {
case reflect.Bool:
return boolEncoder
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intEncoder
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return uintEncoder
// ...省略其他 case...
default:
return unsupportedTypeEncoder
}
}在進(jìn)行 json 編碼的時(shí)候,因?yàn)椴恢纻魅氲膮?shù)是什么類型,所以需要根據(jù)類型做不同的處理,這里就是使用反射來(lái)做的。 通過(guò)判斷不同的類型,然后返回不同的 encoder。
基本類型的反射
這里說(shuō)的基本類型是:int*、uint*、float*、complex*、bool 這種類型。
通過(guò)反射修改基本類型的值,需要注意的是,傳入的參數(shù)必須是指針類型,否則會(huì) panic:
func TestBaseKind(t *testing.T) {
// 通過(guò)反射修改 int 類型變量的值
a := 1
v := reflect.ValueOf(&a)
v.Elem().SetInt(10)
assert.Equal(t, 10, a)
// 通過(guò)反射修改 uint16 類型變量的值
b := uint16(10)
v1 := reflect.ValueOf(&b)
v1.Elem().SetUint(20)
assert.Equal(t, uint16(20), b)
// 通過(guò)反射修改 float32 類型變量的值
f := float32(10.0)
v2 := reflect.ValueOf(&f)
v2.Elem().SetFloat(20.0)
assert.Equal(t, float32(20.0), f)
}通過(guò)反射修改值的時(shí)候,需要通過(guò) Elem() 方法的返回值來(lái)修改。
數(shù)組類型的反射
通過(guò)反射修改數(shù)組中元素的值,可以使用 Index 方法取得對(duì)應(yīng)下標(biāo)的元素,然后再使用 Set 方法修改值:
func TestArray(t *testing.T) {
// 通過(guò)反射修改數(shù)組元素的值
arr := [3]int{1, 2, 3}
v := reflect.ValueOf(&arr)
// 修改數(shù)組中的第一個(gè)元素
v.Elem().Index(0).SetInt(10)
assert.Equal(t, [3]int{10, 2, 3}, arr)
}chan 反射
我們可以通過(guò)反射對(duì)象來(lái)向 chan 中發(fā)送數(shù)據(jù),也可以從 chan 中接收數(shù)據(jù):
func TestChan(t *testing.T) {
// 通過(guò)反射修改 chan
ch := make(chan int, 1)
v := reflect.ValueOf(&ch)
// 通過(guò)反射對(duì)象向 chan 發(fā)送數(shù)據(jù)
v.Elem().Send(reflect.ValueOf(2))
// 在反射對(duì)象外部從 chan 接收數(shù)據(jù)
assert.Equal(t, 2, <-ch)
}map 反射
通過(guò)反射修改 map 中的值,可以使用 SetMapIndex 方法修改 map 中對(duì)應(yīng)的 key:
func TestMap(t *testing.T) {
// 通過(guò)反射修改 map 元素的值
m := map[string]int{"a": 1}
v := reflect.ValueOf(&m)
// 修改 a 的 key,修改其值為 2
v.Elem().SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(2))
// 外部的 m 可以看到反射對(duì)象的修改
assert.Equal(t, 2, m["a"])
}迭代反射 map 對(duì)象
我們可以通過(guò)反射對(duì)象的 MapRange 方法來(lái)迭代 map 對(duì)象:
func TestIterateMap(t *testing.T) {
// 遍歷 map
m := map[string]int{"a": 1, "b": 2}
v := reflect.ValueOf(m)
// 創(chuàng)建 map 迭代器
iter := v.MapRange()
// 迭代 map 的元素
for iter.Next() {
// a 1
// b 2
fmt.Println(iter.Key(), iter.Value())
}
}slice 反射
通過(guò)反射修改 slice 中的值,可以使用 Index 方法取得對(duì)應(yīng)下標(biāo)的元素,然后再使用 Set* 方法修改值,跟數(shù)組類似:
func TestSlice(t *testing.T) {
// 通過(guò)反射修改 slice 元素的值
sli := []int{1, 2, 3}
v := reflect.ValueOf(&sli)
v.Elem().Index(0).SetInt(10)
assert.Equal(t, []int{10, 2, 3}, sli)
}string 反射
對(duì)于 string 類型,我們可以通過(guò)其反射對(duì)象的 String 方法來(lái)修改其內(nèi)容:
func TestString(t *testing.T) {
// 通過(guò)反射修改字符串的值
s := "hello"
v := reflect.ValueOf(&s)
v.Elem().SetString("world")
assert.Equal(t, "world", s)
}interface/Pointer 反射
對(duì)于 interface 或 Pointer 類型,我們可以通過(guò)其反射對(duì)象的 Elem 方法來(lái)修改其內(nèi)容:
func TestPointer(t *testing.T) {
a := 1
// 接口類型
var i interface{} = &a
v1 := reflect.ValueOf(i)
v1.Elem().SetInt(10)
assert.Equal(t, 10, a)
// 指針類型
var p = &a
v2 := reflect.ValueOf(p)
v2.Elem().SetInt(20)
assert.Equal(t, 20, a)
}這兩種類型,我們都需要通過(guò) Elem 方法來(lái)先獲取其實(shí)際保存的值,然后再修改其值。
結(jié)構(gòu)體的反射
對(duì)于 go 中的結(jié)構(gòu)體,反射系統(tǒng)中為我們提供了很多操作結(jié)構(gòu)體的方法,比如獲取結(jié)構(gòu)體的字段、方法、標(biāo)簽、通過(guò)反射對(duì)象調(diào)用其方法等。
先假設(shè)我們有如下結(jié)構(gòu)體:
type Person struct {
Name string
Age int
sex uint8
}
func (p Person) M1() string {
return "person m1"
}
func (p *Person) M2() string {
return "person m2"
}遍歷結(jié)構(gòu)體字段
我們可以通過(guò) NumField 方法來(lái)獲取結(jié)構(gòu)體的字段數(shù)量,然后通過(guò) Field 方法來(lái)獲取結(jié)構(gòu)體的字段:
func TestStruct1(t *testing.T) {
var p = Person{Name: "Tom", Age: 18, sex: 1}
v := reflect.ValueOf(p)
// string Tom
// int 18
// uint8 1
for i := 0; i < v.NumField(); i++ {
fmt.Println(v.Field(i).Type(), v.Field(i))
}
}根據(jù)名稱或索引獲取結(jié)構(gòu)體字段
我們可以根據(jù)結(jié)構(gòu)體字段的名稱或索引來(lái)獲取結(jié)構(gòu)體的字段:
func TestStruct2(t *testing.T) {
var p = Person{Name: "Tom", Age: 18, sex: 1}
v := reflect.ValueOf(p)
assert.Equal(t, 18, v.Field(1).Interface())
assert.Equal(t, 18, v.FieldByName("Age").Interface())
assert.Equal(t, 18, v.FieldByIndex([]int{1}).Interface())
}修改結(jié)構(gòu)體字段
我們可以通過(guò) Field 方法來(lái)獲取結(jié)構(gòu)體的字段,然后再使用 Set* 方法來(lái)修改其值:
func TestStruct2(t *testing.T) {
var p = Person{Name: "Tom", Age: 18, sex: 1}
v := reflect.ValueOf(&p)
v.Elem().FieldByName("Name").SetString("Jack")
assert.Equal(t, "Jack", p.Name)
}上面因?yàn)?Name 是 string 類型,所以我們使用 SetString 方法來(lái)修改其值,如果是 int 類型,我們可以使用 SetInt 方法來(lái)修改其值,依此類推。
結(jié)構(gòu)體方法調(diào)用
通過(guò)反射對(duì)象來(lái)調(diào)用結(jié)構(gòu)體的方法時(shí),需要注意的是,如果我們需要調(diào)用指針接收者的方法,則需要傳遞地址:
func TestStruct3(t *testing.T) {
var p = Person{Name: "Tom", Age: 18, sex: 1}
// 值接收者(receiver)
v1 := reflect.ValueOf(p)
assert.Equal(t, 1, v1.NumMethod())
// 注意:值接收者沒有 M2 方法
assert.False(t, v1.MethodByName("M2").IsValid())
// 通過(guò)值接收者調(diào)用 M1 方法
results := v1.MethodByName("M1").Call(nil)
assert.Len(t, results, 1)
assert.Equal(t, "person m1", results[0].Interface())
// 指針接收者(pointer receiver)
v2 := reflect.ValueOf(&p)
assert.Equal(t, 2, v2.NumMethod())
// 通過(guò)指針接收者調(diào)用 M1 和 M2 方法
results = v2.MethodByName("M1").Call(nil)
assert.Len(t, results, 1)
assert.Equal(t, "person m1", results[0].Interface())
results = v2.MethodByName("M2").Call(nil)
assert.Len(t, results, 1)
assert.Equal(t, "person m2", results[0].Interface())
}說(shuō)明:
- 結(jié)構(gòu)體參數(shù)是值的時(shí)候,
reflect.ValueOf返回的反射對(duì)象只能調(diào)用值接收者的方法,不能調(diào)用指針接收者的方法。 - 結(jié)構(gòu)體參數(shù)是指針的時(shí)候,
reflect.ValueOf返回的反射對(duì)象可以調(diào)用值接收者和指針接收者的方法。 - 調(diào)用
MethodByName方法時(shí),如果方法不存在,則返回的反射對(duì)象的IsValid方法返回false。 - 調(diào)用
Call方法時(shí),如果沒有參數(shù),傳nil參數(shù)即可。如果方法沒有返回值,則返回的結(jié)果切片為空。 - 調(diào)用
Call方法的參數(shù)是reflect.Value類型的切片,返回值也是reflect.Value類型的切片。
是否實(shí)現(xiàn)接口
對(duì)于這個(gè),其實(shí)有一個(gè)更簡(jiǎn)單的方法,那就是利用接口斷言:
func TestStrunct4_0(t *testing.T) {
type TestInterface interface {
M1() string
}
var p = Person{Name: "Tom", Age: 18, sex: 1}
v := reflect.ValueOf(p)
// v.Interface() 返回的是 interface{} 類型
// v.Interface().(TestInterface) 將 interface{} 類型轉(zhuǎn)換為 TestInterface 類型
v1, ok := v.Interface().(TestInterface)
assert.True(t, ok)
assert.Equal(t, "person m1", v1.M1())
}另外一個(gè)方法是,通過(guò)反射對(duì)象的 Type 方法獲取類型對(duì)象,然后調(diào)用 Implements 方法來(lái)判斷是否實(shí)現(xiàn)了某個(gè)接口:
func TestStruct4(t *testing.T) {
type TestInterface interface {
M1() string
}
var p = Person{Name: "Tom", Age: 18, sex: 1}
typ := reflect.TypeOf(p)
typ1 := reflect.TypeOf((*TestInterface)(nil)).Elem()
assert.True(t, typ.Implements(typ1))
}結(jié)構(gòu)體的 tag
這在序列化、反序列化、ORM 庫(kù)中用得非常多,常見的 validator 庫(kù)也是通過(guò) tag 來(lái)實(shí)現(xiàn)的。 下面的例子中,通過(guò)獲取變量的 Type 就可以獲取其 tag 了:
type Person1 struct {
Name string `json:"name"`
}
func TestStruct5(t *testing.T) {
var p = Person1{Name: "Tom"}
typ := reflect.TypeOf(p)
tag := typ.Field(0).Tag
assert.Equal(t, "name", tag.Get("json"))
}修改結(jié)構(gòu)體未導(dǎo)字段
我們知道,結(jié)構(gòu)體的字段如果首字母小寫,則是未導(dǎo)出的,不能被外部包訪問(wèn)。但是我們可以通過(guò)反射修改它:
func TestStruct6(t *testing.T) {
var p = Person{Name: "Tom", Age: 18, sex: 1}
v := reflect.ValueOf(&p)
// 下面這樣寫會(huì)報(bào)錯(cuò):
// panic: reflect: reflect.Value.SetInt using value obtained using unexported field
// v.Elem().FieldByName("sex").SetInt(0)
ft := v.Elem().FieldByName("sex")
sexV := reflect.NewAt(ft.Type(), unsafe.Pointer(ft.UnsafeAddr())).Elem()
assert.Equal(t, 1, p.sex) // 修改前是 1
sexV.Set(reflect.ValueOf(uint8(0))) // 將 sex 字段修改為 0
assert.Equal(t, 0, p.sex) // 修改后是 0
}這里通過(guò) NewAt 方法針對(duì) sex 這個(gè)未導(dǎo)出的字段創(chuàng)建了一個(gè)指針,然后我們就可以通過(guò)這個(gè)指針來(lái)修改 sex 字段了。
方法的反射
這里說(shuō)的方法包括函數(shù)和結(jié)構(gòu)體的方法。
入?yún)⒑头祷刂?/h3>
reflect 包中提供了 In 和 Out 方法來(lái)獲取方法的入?yún)⒑头祷刂担?/p>
func (p Person) Test(a int, b string) int {
return a
}
func TestMethod(t *testing.T) {
var p = Person{Name: "Tom", Age: 18, sex: 1}
v := reflect.ValueOf(p)
m := v.MethodByName("Test")
// 參數(shù)個(gè)數(shù)為 2
assert.Equal(t, 2, m.Type().NumIn())
// 返回值個(gè)數(shù)為 1
assert.Equal(t, 1, m.Type().NumOut())
// In(0) 是第一個(gè)參數(shù),In(1) 是第二個(gè)參數(shù)
arg1 := m.Type().In(0)
assert.Equal(t, "int", arg1.Name())
arg2 := m.Type().In(1)
assert.Equal(t, "string", arg2.Name())
// Out(0) 是第一個(gè)返回值
ret0 := m.Type().Out(0)
assert.Equal(t, "int", ret0.Name())
}說(shuō)明:
In和Out方法返回的是reflect.Type類型,可以通過(guò)Name方法獲取類型名稱。NumIn和NumOut方法返回的是參數(shù)和返回值的個(gè)數(shù)。reflect.Value類型的MethodByName方法可以獲取結(jié)構(gòu)體的方法。
通過(guò)反射調(diào)用方法
reflect.Value 中對(duì)于方法類型的反射對(duì)象,有一個(gè) Call 方法,可以通過(guò)它來(lái)調(diào)用方法:
func TestMethod2(t *testing.T) {
var p = Person{Name: "Tom", Age: 18, sex: 1}
v := reflect.ValueOf(p)
// 通過(guò)反射調(diào)用 Test 方法
m := v.MethodByName("Test")
arg1 := reflect.ValueOf(1)
arg2 := reflect.ValueOf("hello")
args := []reflect.Value{arg1, arg2}
rets := m.Call(args)
assert.Len(t, rets, 1)
assert.Equal(t, 1, rets[0].Interface())
}說(shuō)明:
Call方法的參數(shù)是[]reflect.Value類型,需要將參數(shù)轉(zhuǎn)換為reflect.Value類型。Call方法的返回值也是[]reflect.Value類型。reflect.Value類型的MethodByName方法可以獲取結(jié)構(gòu)體的方法的反射對(duì)象。- 通過(guò)方法的反射對(duì)象的
Call方法可以實(shí)現(xiàn)調(diào)用方法。
總結(jié)
- 通過(guò)
reflect.Kind可以判斷反射對(duì)象的類型,Kind涵蓋了 go 中所有的基本類型,所以反射的時(shí)候判斷Kind就足夠了。 - 如果要獲取反射對(duì)象的值,需要傳遞指針給
reflect.Value。 - 可以往
chan的反射對(duì)象中發(fā)送數(shù)據(jù),也可以從chan的反射對(duì)象中接收數(shù)據(jù)。 SetMapIndex方法可以修改map中的元素。MapRange方法可以獲取map的迭代器。- 可以通過(guò)
Index方法獲取slice的元素,也可以通過(guò)SetIndex方法修改slice的元素。 - 可以通過(guò)
SetString方法修改string的值。 - 對(duì)于
interface和Pointer類型的反射對(duì)象,可以通過(guò)Elem方法獲取它們的值,同時(shí)也只有通過(guò)Elem獲取到的反射對(duì)象能調(diào)用Set*方法來(lái)修改其指向的對(duì)象。 reflect包中提供了很多操作結(jié)構(gòu)體的功能:如獲取結(jié)構(gòu)體的字段、獲取結(jié)構(gòu)體的方法、調(diào)用結(jié)構(gòu)體的方法等。我們使用一些類庫(kù)的時(shí)候,會(huì)需要通過(guò)結(jié)構(gòu)體的tag來(lái)設(shè)置一些元信息,這些信息只有通過(guò)反射才能獲取。- 我們可以通過(guò)
NewAt來(lái)創(chuàng)建一個(gè)指向結(jié)構(gòu)體未導(dǎo)出字段的反射對(duì)象,這樣就可以修改結(jié)構(gòu)體的未導(dǎo)出字段了。 - 對(duì)于函數(shù)和方法,go 的反射系統(tǒng)也提供了很多功能,如獲取參數(shù)和返回值信息、使用
Call來(lái)調(diào)用函數(shù)和方法等。
到此這篇關(guān)于Golang中反射的常見用法分享的文章就介紹到這了,更多相關(guān)Golang反射內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go-micro微服務(wù)domain層開發(fā)示例詳解
這篇文章主要為大家介紹了go-micro微服務(wù)domain層開發(fā)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
golang進(jìn)程內(nèi)存控制避免docker內(nèi)oom
這篇文章主要為大家介紹了golang進(jìn)程內(nèi)存控制避免docker內(nèi)oom示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
go語(yǔ)言搬磚之go jmespath實(shí)現(xiàn)查詢json數(shù)據(jù)
這篇文章主要為大家介紹了go語(yǔ)言搬磚之go jmespath實(shí)現(xiàn)查詢json數(shù)據(jù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Nunu快速構(gòu)建高效可靠Go應(yīng)用腳手架使用詳解
這篇文章主要為大家介紹了如何使用Nunu快速構(gòu)建高效可靠Go應(yīng)用腳手架詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
詳解Go語(yǔ)言實(shí)現(xiàn)線性查找算法和二分查找算法
線性查找又稱順序查找,它是查找算法中最簡(jiǎn)單的一種。二分查找,也稱折半查找,相比于線性查找,它是一種效率較高的算法。本文將用Go語(yǔ)言實(shí)現(xiàn)這兩個(gè)查找算法,需要的可以了解一下2022-12-12
GO語(yǔ)言實(shí)現(xiàn)文件上傳的示例代碼
這篇文章主要分享一下golang實(shí)現(xiàn)文件上傳的流程和具體代碼,供大家參考,感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助2022-08-08
Go 語(yǔ)言結(jié)構(gòu)實(shí)例分析
在本篇文章里小編給大家整理的是一篇關(guān)于Go 語(yǔ)言結(jié)構(gòu)實(shí)例分析的相關(guān)知識(shí)點(diǎn),有興趣的朋友們可以學(xué)習(xí)下。2021-07-07

