Go語言中的變量是分爲兩部分的:
- 類型信息:預先定義好的元信息。
- 值信息:程序運行過程中可動態變化的。
反射概念
反射是指在程序運行期對程序本身進行訪問和修改的能力。程序在編譯時,變量被轉換爲內存地址,變量名不會被編譯器寫入到可執行部分。在運行程序時,程序無法獲取自身的信息。
支持反射的語言可以在程序編譯期將變量的反射信息,如字段名稱、類型信息、結構體信息等整合到可執行文件中,並給程序提供接口訪問反射信息,這樣就可以在程序運行期獲取類型的反射信息,並且有能力修改它們。
Go程序在運行期使用reflect包訪問程序的反射信息。
refect包介紹
在Go語言的反射機制中,任何接口值都由是一個具體類型和具體類型的值兩部分組成的(我們在上一篇接口的博客中有介紹相關概念)。 在Go語言中反射的相關功能由內置的reflect包提供,任意接口值在反射中都可以理解爲由reflect.Type
和reflect.Value
兩部分組成,並且reflect
包提供了reflect.TypeOf
和reflect.ValueOf
兩個函數來獲取任意對象的Value
和Type
。
TypeOf和Valueof
在Go語言中,使用reflect.TypeOf()
函數可以獲得任意值的類型對象(reflect.Type
),程序通過類型對象可以訪問任意值的類型信息。
package main
import (
"fmt"
"reflect"
)
type node struct {
num int
next *node
}
func testRefect(intf interface{}) {
typeof := reflect.TypeOf(intf) //
val := reflect.ValueOf(intf)
fmt.Printf("%v\t%v\n", typeof, val)
}
func main() {
testRefect(node{})
testRefect(&node{})
testRefect(10)
}
main.node {0 <nil>}
*main.node &{0 <nil>}
int 10
type name和type kind
因爲在Go語言中我們可以使用type關鍵字構造很多自定義類型,而種類(Kind)就是指底層的類型,但在反射中,當需要區分指針、結構體等大品種的類型時,就會用到種類(Kind)。 舉個例子,我們定義了兩個指針類型和兩個結構體類型,通過反射查看它們的類型(Type)和種類(Kind)。
package main
import (
"fmt"
"reflect"
)
type myInt int32
type dog struct{
name string
age int
}
func testReflect(intf interface{}){
t:=reflect.TypeOf(intf)
fmt.Printf("Name:%v\tKind:%v\n",t.Name(),t.Kind())
}
func main(){
var x myInt
var y int32
testReflect(x)
testReflect(y)
testReflect(dog{})
testReflect(&dog{})
}
Name:myInt Kind:int32
Name:int32 Kind:int32
Name:dog Kind:struct
Name: Kind:ptr
Go語言的反射中像數組、切片、Map、指針等類型的變量,它們的.Name()都是返回空。
通過反射獲取值
reflect.Value類型提供的獲取原始值的方法如下:
方法 | 說明 |
---|---|
Interface() interface {} | 將值以 interface{} 類型返回,可以通過類型斷言轉換爲指定類型 |
Int() int64 | 將值以 int 類型返回,所有有符號整型均可以此方式返回 |
Uint() uint64 | 將值以 uint 類型返回,所有無符號整型均可以此方式返回 |
Float() float64 | 將值以雙精度(float64)類型返回,所有浮點數(float32、float64)均可以此方式返回 |
Bool() bool | 將值以 bool 類型返回 |
Bytes() []bytes | 將值以字節數組 []bytes 類型返回 |
String() string | 將值以字符串類型返回 |
package main
import (
"fmt"
"reflect"
)
func testReflect(intf interface{}){
val:=reflect.ValueOf(intf)
kind:=val.Kind()
switch kind{
case reflect.Int64:
fmt.Printf("%v\n",int64(val.Int()))
case reflect.Float64:
fmt.Printf("%v\n",float64(val.Float()))
}
}
func main(){
var x float64=3.14
var y int64 =520
testReflect(x)
testReflect(y)
}
通過反射修改值
想要在函數中通過反射修改變量的值,必須傳遞變量地址才能修改變量值。而反射中使用專有的Elem()方法來獲取指針對應的值。
package main
import (
"fmt"
"reflect"
)
func testReflect(intf interface{}){
val:=reflect.ValueOf(intf)
kind:=val.Elem().Kind()//反射中使用 Elem()方法獲取指針對應的值
if reflect.Int64==kind{
val.Elem().SetInt(1314)
}
}
func main(){
var x int64 =520
testReflect(&x)//必須傳地址
fmt.Println(x)
}
isNil()和isValid()
IsNil()
常被用於判斷指針是否爲空;IsValid()
常被用於判定返回值是否有效。
IsNil()
報告v持有的值是否爲nil。v持有的值的分類必須是通道、函數、接口、映射、指針、切片之一;否則IsNil函數會導致panic。
IsValid()
返回v是否持有一個值。如果v是Value零值會返回假,此時v除了IsValid、String、Kind之外的方法都會導致panic。
package main
import (
"fmt"
"reflect"
)
func main() {
// *int類型空指針
var a *int
fmt.Println("空指針判斷IsNil:", reflect.ValueOf(a).IsNil())
x := 10
fmt.Println("非空指針判斷Nil:", reflect.ValueOf(&x).IsNil())
// 實例化一個匿名結構體
b := struct {
x int
y int
}{10, 20}
fmt.Println("查詢存在的結構體成員:", reflect.ValueOf(b).FieldByName("x").IsValid())
fmt.Println("查詢不存在的結構體成員:",\
reflect.ValueOf(b).FieldByName("abc").IsValid())
// map
c := map[string]int{}
// 嘗試從map中查找一個不存在的鍵
fmt.Println("map中不存在的鍵:", reflect.ValueOf(c).MapIndex(\
reflect.ValueOf("名字")).IsValid())
}
空指針判斷IsNil: true
非空指針判斷Nil: false
查詢存在的結構體成員: true
查詢不存在的結構體成員: false
map中不存在的鍵: false
結構體反射
reflect.Type中與獲取結構體成員相關的的方法如下表所示。
方法 | 說明 |
---|---|
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) | 根據方法名返回該類型方法 |
結構體反射示例
當我們使用反射得到一個結構體數據之後可以通過索引依次獲取其字段信息,也可以通過字段名去獲取指定的字段信息。
package main
import (
"fmt"
"reflect"
)
type dog struct {
name string `json:"名字"`
age int `json:"年齡"`
}
func main() {
hua := dog{
"laifu",
10,
}
Type := reflect.TypeOf(hua)
fmt.Println(Type.Kind(), " ", Type.Name())
for i := 0; i < Type.NumField(); i++ {
field := Type.Field(i)
fmt.Printf("name:%v\ttype:%v\tindex:%v\tjson tag:%v\n",\
field.Name, field.Type, field.Index, field.Tag.Get("json"))
}
}
struct dog
name:name type:string index:[0] json tag:名字
name:age type:int index:[1] json tag:年齡
結構體反射示例
當我們使用反射得到一個結構體數據之後可以通過索引依次獲取其字段信息,也可以通過字段名去獲取指定的字段信息。
package main
import (
"fmt"
"reflect"
)
type student struct {
Name string `json:"name"`
Score int `json:"score"`
}
// 給student添加兩個方法 Study和Sleep(注意首字母大寫)
func (s student) Study() string {
msg := "好好學習,天天向上。"
fmt.Println(msg)
return msg
}
func (s student) Sleep() string {
msg := "好好睡覺,快快長大。"
fmt.Println(msg)
return msg
}
func printMethod(x interface{}) {
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
fmt.Println(t.NumMethod())
for i := 0; i < v.NumMethod(); i++ {
methodType := v.Method(i).Type()
fmt.Printf("method name:%s\n", t.Method(i).Name)
fmt.Printf("method:%s\n", methodType)
// 通過反射調用方法傳遞的參數必須是 []reflect.Value 類型
var args = []reflect.Value{}
v.Method(i).Call(args)
}
}
func main() {
printMethod(&student{})
}