《Go語言四十二章經(jīng)》第二十七章 反射(reflect)
作者:李驍
27.1 反射(reflect)
反射是應(yīng)用程序檢查其所擁有的結(jié)構(gòu)比吭,尤其是類型的一種能力睦擂;這是元編程的一種形式扭吁。每種語言的反射模型都不同,并且有些語言根本不支持反射啊胶。Go語言實(shí)現(xiàn)了反射肃拜,反射機(jī)制就是在運(yùn)行時(shí)動(dòng)態(tài)調(diào)用對(duì)象的方法和屬性,標(biāo)準(zhǔn)庫reflect提供了相關(guān)的功能真朗。在reflect包中此疹,通過reflect.TypeOf(),reflect.ValueOf()分別從類型遮婶、值的角度來描述一個(gè)Go對(duì)象蝗碎。
func TypeOf(i interface{}) Type
type Type interface
func ValueOf(i interface{}) Value
type Value struct
在Go語言的實(shí)現(xiàn)中,一個(gè)interface類型的變量存儲(chǔ)了2個(gè)信息, 一個(gè)<值蹭睡,類型>對(duì)衍菱,<value,type> :
(value, type)
value是實(shí)際變量值赶么,type是實(shí)際變量的類型肩豁。兩個(gè)簡(jiǎn)單的函數(shù),reflect.TypeOf 和 reflect.ValueOf辫呻,返回被檢查對(duì)象的類型和值清钥。
例如,x 被定義為:var x float64 = 3.4放闺,那么 reflect.TypeOf(x) 返回 float64祟昭,reflect.ValueOf(x) 返回 <float64 Value>。實(shí)際上怖侦,反射是通過檢查一個(gè)接口的值篡悟,變量首先被轉(zhuǎn)換成空接口。這從下面兩個(gè)函數(shù)簽名能夠很明顯的看出來:
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
reflect.Type 和 reflect.Value 都有許多方法用于檢查和操作它們匾寝。
Type主要有:
Kind() 將返回一個(gè)常量搬葬,表示具體類型的底層類型
Elem()方法返回指針、數(shù)組艳悔、切片急凰、map、通道的基類型猜年,這個(gè)方法要慎用抡锈,如果用在其他類型上面會(huì)出現(xiàn)panic
Value主要有:
Type() 將返回具體類型所對(duì)應(yīng)的 reflect.Type(靜態(tài)類型)
Kind() 將返回一個(gè)常量,表示具體類型的底層類型
反射可以在運(yùn)行時(shí)檢查類型和變量乔外,例如它的大小床三、方法和 動(dòng)態(tài) 的調(diào)用這些方法。這對(duì)于沒有源代碼的包尤其有用杨幼。
由于反射是一個(gè)強(qiáng)大的工具勿璃,但反射對(duì)性能有一定的影響,除非有必要,否則應(yīng)當(dāng)避免使用或小心使用歧沪。下面代碼針對(duì)int、數(shù)組以及結(jié)構(gòu)體分別使用反射機(jī)制莲组,其中的差異請(qǐng)看注釋。
package main
import (
"fmt"
"reflect"
)
type Student struct {
name string
}
func main() {
var a int = 50
v := reflect.ValueOf(a) // 返回Value類型對(duì)象锹杈,值為50
t := reflect.TypeOf(a) // 返回Type類型對(duì)象,值為int
fmt.Println(v, t, v.Type(), t.Kind())
var b [5]int = [5]int{5, 6, 7, 8}
fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(),reflect.TypeOf(b).Elem()) // [5]int array int
var Pupil Student
p := reflect.ValueOf(Pupil) // 使用ValueOf()獲取到結(jié)構(gòu)體的Value對(duì)象
fmt.Println(p.Type()) // 輸出:Student
fmt.Println(p.Kind()) // 輸出:struct
}
在Go語言中邪码,類型包括 static type和concrete type. 簡(jiǎn)單說 static type是你在編碼是看見的類型(如int、string)咬清,concrete type是實(shí)際的類型,runtime系統(tǒng)看見的類型旧烧。
Type()返回的是靜態(tài)類型,而kind()返回的是concrete type掘剪。上面代碼中平委,在int,數(shù)組以及結(jié)構(gòu)體三種類型情況中夺谁,可以看到kind()廉赔,type()返回值的差異。
通過反射可以修改原對(duì)象
d.CanAddr()方法:判斷它是否可被取地址
d.CanSet()方法:判斷它是否可被取地址并可被修改
通過一個(gè)settable的Value反射對(duì)象來訪問匾鸥、修改其對(duì)應(yīng)的變量值:
package main
import (
"fmt"
"reflect"
)
type Student struct {
name string
Age int
}
func main() {
var a int = 50
v := reflect.ValueOf(a) // 返回Value類型對(duì)象蜡塌,值為50
t := reflect.TypeOf(a) // 返回Type類型對(duì)象,值為int
fmt.Println(v, t, v.Type(), t.Kind(), reflect.ValueOf(&a).Elem())
seta := reflect.ValueOf(&a).Elem() // 這樣才能讓seta保存a的值
fmt.Println(seta, seta.CanSet())
seta.SetInt(1000)
fmt.Println(seta)
var b [5]int = [5]int{5, 6, 7, 8}
fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(), reflect.TypeOf(b).Elem())
var Pupil Student = Student{"joke", 18}
p := reflect.ValueOf(Pupil) // 使用ValueOf()獲取到結(jié)構(gòu)體的Value對(duì)象
fmt.Println(p.Type()) // 輸出:Student
fmt.Println(p.Kind()) // 輸出:struct
setStudent := reflect.ValueOf(&Pupil).Elem()
//setStudent.Field(0).SetString("Mike") // 未導(dǎo)出字段扫腺,不能修改岗照,panic會(huì)發(fā)生
setStudent.Field(1).SetInt(19)
fmt.Println(setStudent)
}
雖然反射可以越過Go語言的導(dǎo)出規(guī)則的限制讀取結(jié)構(gòu)體中未導(dǎo)出的成員,但不能修改這些未導(dǎo)出的成員笆环。因?yàn)橐粋€(gè)struct中只有被導(dǎo)出的字段才是settable的攒至。
在結(jié)構(gòu)體中有tag標(biāo)簽,通過反射可獲取結(jié)構(gòu)體成員變量的tag信息躁劣。
package main
import (
"fmt"
"reflect"
)
type Student struct {
name string
Age int `json:"years"`
}
func main() {
var Pupil Student = Student{"joke", 18}
setStudent := reflect.ValueOf(&Pupil).Elem()
sSAge, _ := setStudent.Type().FieldByName("Age")
fmt.Println(sSAge.Tag.Get("json")) // years
}
程序輸出:
years
27.2 反射結(jié)構(gòu)體
為了完整說明反射的情況迫吐,通過反射一個(gè)結(jié)構(gòu)體類型,綜合來說明账忘。下面例子較為系統(tǒng)地利用一個(gè)結(jié)構(gòu)體志膀,來充分舉例說明反射:
package main
import (
"fmt"
"reflect"
)
// 結(jié)構(gòu)體
type ss struct {
int
string
bool
float64
}
func (s ss) Method1(i int) string { return "結(jié)構(gòu)體方法1" }
func (s *ss) Method2(i int) string { return "結(jié)構(gòu)體方法2" }
var (
structValue = ss{ // 結(jié)構(gòu)體
20,
"結(jié)構(gòu)體",
false,
64.0,
}
)
// 復(fù)雜類型
var complexTypes = []interface{}{
structValue, &structValue, // 結(jié)構(gòu)體
structValue.Method1, structValue.Method2, // 方法
}
func main() {
// 測(cè)試復(fù)雜類型
for i := 0; i < len(complexTypes); i++ {
PrintInfo(complexTypes[i])
}
}
func PrintInfo(i interface{}) {
if i == nil {
fmt.Println("--------------------")
fmt.Printf("無效接口值:%v\n", i)
fmt.Println("--------------------")
return
}
v := reflect.ValueOf(i)
PrintValue(v)
}
func PrintValue(v reflect.Value) {
fmt.Println("--------------------")
// ----- 通用方法 -----
fmt.Println("String :", v.String()) // 反射值的字符串形式
fmt.Println("Type :", v.Type()) // 反射值的類型
fmt.Println("Kind :", v.Kind()) // 反射值的類別
fmt.Println("CanAddr :", v.CanAddr()) // 是否可以獲取地址
fmt.Println("CanSet :", v.CanSet()) // 是否可以修改
if v.CanAddr() {
fmt.Println("Addr :", v.Addr()) // 獲取地址
fmt.Println("UnsafeAddr :", v.UnsafeAddr()) // 獲取自由地址
}
// 獲取方法數(shù)量
fmt.Println("NumMethod :", v.NumMethod())
if v.NumMethod() > 0 {
// 遍歷方法
i := 0
for ; i < v.NumMethod()-1; i++ {
fmt.Printf(" ┣ %v\n", v.Method(i).String())
// if i >= 4 { // 只列舉 5 個(gè)
// fmt.Println(" ┗ ...")
// break
// }
}
fmt.Printf(" ┗ %v\n", v.Method(i).String())
// 通過名稱獲取方法
fmt.Println("MethodByName :", v.MethodByName("String").String())
}
switch v.Kind() {
// 結(jié)構(gòu)體:
case reflect.Struct:
fmt.Println("=== 結(jié)構(gòu)體 ===")
// 獲取字段個(gè)數(shù)
fmt.Println("NumField :", v.NumField())
if v.NumField() > 0 {
var i int
// 遍歷結(jié)構(gòu)體字段
for i = 0; i < v.NumField()-1; i++ {
field := v.Field(i) // 獲取結(jié)構(gòu)體字段
fmt.Printf(" ├ %-8v %v\n", field.Type(), field.String())
}
field := v.Field(i) // 獲取結(jié)構(gòu)體字段
fmt.Printf(" └ %-8v %v\n", field.Type(), field.String())
// 通過名稱查找字段
if v := v.FieldByName("ptr"); v.IsValid() {
fmt.Println("FieldByName(ptr) :", v.Type().Name())
}
// 通過函數(shù)查找字段
v := v.FieldByNameFunc(func(s string) bool { return len(s) > 3 })
if v.IsValid() {
fmt.Println("FieldByNameFunc :", v.Type().Name())
}
}
}
}
程序輸出:
String : <main.ss Value>
Type : main.ss
Kind : struct
CanAddr : false
CanSet : false
NumMethod : 1
┗ <func(int) string Value>
MethodByName : <invalid Value>
=== 結(jié)構(gòu)體 ===
NumField : 4
├ int <int Value>
├ string 結(jié)構(gòu)體
├ bool <bool Value>
└ float64 <float64 Value>
--------------------
String : <*main.ss Value>
Type : *main.ss
Kind : ptr
CanAddr : false
CanSet : false
NumMethod : 2
┣ <func(int) string Value>
┗ <func(int) string Value>
MethodByName : <invalid Value>
--------------------
String : <func(int) string Value>
Type : func(int) string
Kind : func
CanAddr : false
CanSet : false
NumMethod : 0
--------------------
String : <func(int) string Value>
Type : func(int) string
Kind : func
CanAddr : false
CanSet : false
NumMethod : 0
本書《Go語言四十二章經(jīng)》內(nèi)容在github上同步地址:https://github.com/ffhelicopter/Go42
本書《Go語言四十二章經(jīng)》內(nèi)容在簡(jiǎn)書同步地址: http://www.reibang.com/nb/29056963雖然本書中例子都經(jīng)過實(shí)際運(yùn)行熙宇,但難免出現(xiàn)錯(cuò)誤和不足之處,煩請(qǐng)您指出溉浙;如有建議也歡迎交流烫止。
聯(lián)系郵箱:roteman@163.com