Golang反射-上篇

語言: CN / TW / HK

1、反射的定義

It’s a great source of confusion ~ (引用自官方部落格)

反射是指在執行時動態的訪問和修改任意型別物件的結構和成員,在 go 語言中提供 reflect 包提供反射的功能,每一個變數都有兩個屬性:型別 Type 和值 Value

反射能夠自描述自控制

例如 python 的反射:根據字串執行函式,根據字串匯入包

go 是靜態語言,反射就是 go 提供的一種機制,在編譯時不知道型別的情況下可以做如下的事情

  • 更新變數
  • 執行時檢視值
  • 呼叫方法
  • 對他們的佈局進行操作

使用反射的兩個經典場景

  • 你編寫的這個函式,還不知道傳給你的型別具體是什麼,可能是還沒約定好,也可能是傳入的型別很多

  • 希望通過使用者的輸入來決定呼叫哪個函式(根據字串呼叫方法),動態執行函式

2、反射的基礎資料型別

3、Type

reflect.Type 是一個介面型別,用於獲取變數型別相關的資訊,可通過 reflect.TypeOf 函式獲取某個變數的型別資訊

原始碼 go/src/reflect/type.go

type Type interface {
	Align() int
	FieldAlign() int
	Method(int) Method  // 第 i 個方法
	MethodByName(string) (Method, bool)  // 根據名稱獲取方法
	NumMethod() int  // 方法的個數
	Name() string  // 獲取結構體名稱
	PkgPath() string  // 包路徑
	Size() uintptr  // 佔用記憶體的大小
	String() string  // 獲取字串表述
	Kind() Kind // 資料型別
	Implements(u Type) bool  // 判斷是否實現了某介面
	AssignableTo(u Type) bool  // 能否賦給另外一種型別
	ConvertibleTo(u Type) bool  // 能否轉換為另外一種型別
	Comparable() bool
	Bits() int
	ChanDir() ChanDir
	IsVariadic() bool
	Elem() Type  // 解析指標(指標型別轉為普通型別)
	Field(i int) StructField  // 第i個成員
	FieldByIndex(index []int) StructField  // 根據index路徑獲取巢狀成員
	FieldByName(name string) (StructField, bool)  // 根據名稱獲取成員
	FieldByNameFunc(match func(string) bool) (StructField, bool)
	In(i int) Type
	Key() Type
	Len() int  // 容器的長度
	NumField() int
	NumIn() int  // 輸出引數的個數
	NumOut() int  // 返回引數的個數
	Out(i int) Type

	common() *rtype
	uncommon() *uncommonType
}

4、Value

reflect.Value 是一個結構體型別,用於獲取變數值的資訊,可通過 reflect.ValueOf 函式獲取修改原始資料型別(某個變數)的值資訊

原始碼 go/src/reflect/value.go

type Value struct {
   // 代表的資料型別
	typ *rtype
	// 指向原始資料的指標
	ptr unsafe.Pointer
}

5、反射三大定律

interface 型別有個 valuetype 對,而反射就是檢查 interface 的這個 value , type 對的

具體一點說就是 Go 提供一組方法提取 interfacevalue ,提供另一組方法提取 interfacetype

  • reflect.Type 提供一組介面處理 interface 的型別,即 value , type 中的 type
  • reflect.Value 提供一組介面處理 interface 的值,即 value , type 中的 value

5.1 反射第一定律

反射第一定律:反射可以將 interface 型別變數轉換成反射物件

如何通過反射獲取一個變數的值和型別

package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    t := reflect.TypeOf(x)  //t is reflext.Type
    fmt.Println("type:", t)
    fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
    v := reflect.ValueOf(x) //v is reflext.Value
    fmt.Println("value:", v)
}

程式輸出

type: float64
kind is float64: true
value: 3.4

反射是針對 interface 型別變數的,其中 TypeOf()ValueOf() 接受的引數都是 interface{} 型別的,也即 x 值是被轉成了 interface 傳入的

5.2 反射第二定律

反射第二定律:反射可以將反射物件還原成 interface 物件

之所以叫’反射’,反射物件與 interface 物件是可以互相轉化的

package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x) //v is reflext.Value
    var y float64 = v.Interface().(float64)
    fmt.Println("value:", y)
}

物件 x 轉換成反射物件 vv 又通過 Interface() 介面轉換成 interface 物件, interface 物件通過 .(float64) 型別斷言獲取 float64 型別的值

5.3 反射第三定律

反射第三定律:反射物件可修改, value 值必須是可設定的

通過反射可以將 interface 型別變數轉換成反射物件,可以使用該反射物件設定其持有的值

package main
import (
    "reflect"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x)
    v.SetFloat(7.1) // Error: will panic.
}

通過反射物件v設定新值,會出現 panic

panic: reflect: reflect.Value.SetFloat using unaddressable value

錯誤原因即是 v 是不可修改的。

反射物件是否可修改取決於其所儲存的值,回想一下函式傳參時是傳值還是傳址就不難理解上例中為何失敗了。

上面例子傳入 reflect.ValueOf() 函式的其實是 x 的值,而非 x 本身。即通過 v 修改其值是無法影響 x 的,也即是無效的修改,所以 golang 會報錯

想到此處,即可明白,如果構建 v 時使用 x 的地址就可實現修改了,但此時 v 代表的是指標地址,我們要設定的是指標所指向的內容,也即我們想要修改的是 *v 。 通過 v 修改 x 的值?

reflect.Value 提供了 Elem() 方法,可以獲得指標向指向的 value

package main
import (
"reflect"
    "fmt"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(&x)
    v.Elem().SetFloat(7.1)
    fmt.Println("x :", v.Elem().Interface())
}

輸出為:

x : 7.1

6、反射常用的API

6.1 獲取type型別

typeUser := reflect.TypeOf(&User{}) //通過TypeOf()得到Type型別
fmt.Println(typeUser)                      //*User
fmt.Println(typeUser.Elem())               //User
fmt.Println(typeUser.Name())               //空字串
fmt.Println(typeUser.Elem().Name())        //User,不帶包名的類名稱
fmt.Println(typeUser.Kind())               //ptr
fmt.Println(typeUser.Elem().Kind())        //struct
fmt.Println(typeUser.Kind() == reflect.Ptr)
fmt.Println(typeUser.Elem().Kind() == reflect.Struct)

6.2 獲取Field資訊

typeUser := reflect.TypeOf(User{}) //需要用struct的Type,不能用指標的Type
fieldNum := typeUser.NumField()           //成員變數的個數
for i := 0; i < fieldNum; i++ {
	field := typeUser.Field(i)
	fmt.Printf("%d %s offset %d anonymous %t type %s exported %t json tag %s\n", i,
		field.Name,            //變數名稱
		field.Offset,          //相對於結構體首地址的記憶體偏移量,string型別會佔據16個位元組
		field.Anonymous,       //是否為匿名成員
		field.Type,            //資料型別,reflect.Type型別
		field.IsExported(),    //包外是否可見(即是否以大寫字母開頭)
		field.Tag.Get("json")) //獲取成員變數後面``裡面定義的tag
//可以通過FieldByName獲取Field
if nameField, ok := typeUser.FieldByName("Name"); ok {
	fmt.Printf("Name is exported %t\n", nameField.IsExported())
}
//也可以根據FieldByIndex獲取Field
thirdField := typeUser.FieldByIndex([]int{2}) //引數是個slice,因為有struct巢狀的情況
fmt.Printf("third field name %s\n", thirdField.Name)
}

6.3 獲取method資訊

typeUser := reflect.TypeOf(common.User{})
methodNum := typeUser.NumMethod() //成員方法的個數。接收者為指標的方法【不】包含在內
for i := 0; i < methodNum; i++ {
	method := typeUser.Method(i)
	fmt.Println(method.Type)  // 會輸出函式完整的簽名,其中輸入引數會將結構體本身也作為輸入引數,因此引數個數多一個
	fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())
}
fmt.Println()
if method, ok := typeUser.MethodByName("Examine2"); ok {  // 根據方法名獲取
	fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())
}
typeUser2 := reflect.TypeOf(&common.User{})
methodNum = typeUser2.NumMethod() //成員方法的個數。接收者為指標或值的方法都包含在內,也就是說值實現的方法指標也實現了(反之不成立)
for i := 0; i < methodNum; i++ {
	method := typeUser2.Method(i)
	fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())
}

6.4 獲取函式資訊

typeFunc := reflect.TypeOf(Add) //獲取函式型別
fmt.Printf("is function type %t\n", typeFunc.Kind() == reflect.Func)
argInNum := typeFunc.NumIn()   //輸入引數的個數
argOutNum := typeFunc.NumOut() //輸出引數的個數
for i := 0; i < argInNum; i++ {
	argTyp := typeFunc.In(i)
	fmt.Printf("第%d個輸入引數的型別%s\n", i, argTyp)
}
for i := 0; i < argOutNum; i++ {
	argTyp := typeFunc.Out(i)
	fmt.Printf("第%d個輸出引數的型別%s\n", i, argTyp)
}

6.5 賦值和轉換關係

  • type1.AssignableTo(type2) // type1代表的型別是否可以賦值給type2代表的型別
  • type1.ConvertibleTo(type2)) // type1代表的型別是否可以轉換成type2代表的型別
  • java的反射可以獲取繼承關係,而go語言不支援繼承,所以必須是相同的型別才能AssignableTo和ConvertibleTo

示例

u := reflect.TypeOf(User{})
t := reflect.TypeOf(Student{}) //Student內部嵌套了User
u2 := reflect.TypeOf(User{})

//false false
fmt.Println(t.AssignableTo(u))  //t代表的型別是否可以賦值給u代表的型別
fmt.Println(t.ConvertibleTo(u)) //t代表的型別是否可以轉換成u代表的型別

//false false
fmt.Println(u.AssignableTo(t))
fmt.Println(u.ConvertibleTo(t))

//true true
fmt.Println(u.AssignableTo(u2))
fmt.Println(u.ConvertibleTo(u2))

6.6 是否實現介面

//通過reflect.TypeOf((*<interface>)(nil)).Elem()獲得介面型別。因為People是個介面不能建立例項,所以把nil強制轉為*common.People型別
typeOfPeople := reflect.TypeOf((*common.People)(nil)).Elem()  // 可以將nil理解成People指標的一個例項
fmt.Printf("typeOfPeople kind is interface %t\n", typeOfPeople.Kind() == reflect.Interface)
t1 := reflect.TypeOf(common.User{})
t2 := reflect.TypeOf(&common.User{})
//User的值型別實現了介面,則指標型別也實現了介面;但反過來不行(把Think的接收者改為*User試試)
fmt.Printf("t1 implements People interface %t\n", t1.Implements(typeOfPeople))  // false
fmt.Printf("t2 implements People interface %t\n", t2.Implements(typeOfPeople))  // true

6.7 value和其他型別的互換

//原始型別轉為Value
iValue := reflect.ValueOf(1)
sValue := reflect.ValueOf("hello")
userPtrValue := reflect.ValueOf(&common.User{
	Id:     7,
	Name:   "傑克遜",
	Weight: 65,
	Height: 1.68,
})
fmt.Println(iValue)       //1
fmt.Println(sValue)       //hello
fmt.Println(userPtrValue) //&{7 傑克遜  65 1.68}
//Value轉為Type
iType := iValue.Type()
sType := sValue.Type()
userType := userPtrValue.Type()
//在Type和相應Value上呼叫Kind()結果一樣的
fmt.Println(iType.Kind() == reflect.Int, iValue.Kind() == reflect.Int, iType.Kind() == iValue.Kind())                   //true true
fmt.Println(sType.Kind() == reflect.String, sValue.Kind() == reflect.String, sType.Kind() == sValue.Kind())             //true true
fmt.Println(userType.Kind() == reflect.Ptr, userPtrValue.Kind() == reflect.Ptr, userType.Kind() == userPtrValue.Kind()) //true true true

//指標Value和非指標Value互相轉換
userValue := userPtrValue.Elem()                    //Elem() 指標Value轉為非指標Value
fmt.Println(userValue.Kind(), userPtrValue.Kind())  //struct ptr
userPtrValue3 := userValue.Addr()                   //Addr() 非指標Value轉為指標Value
fmt.Println(userValue.Kind(), userPtrValue3.Kind()) //struct ptr

//轉為原始型別
//通過Interface()函式把Value轉為interface{},再從interface{}強制型別轉換,轉為原始資料型別
//或者在Value上直接呼叫Int()、String()等一步到位
fmt.Printf("origin value iValue is %d %d\n", iValue.Interface().(int), iValue.Int())
fmt.Printf("origin value sValue is %s %s\n", sValue.Interface().(string), sValue.String())
user := userValue.Interface().(common.User)
fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user.Id, user.Name, user.Weight, user.Height)
user2 := userPtrValue.Interface().(*common.User)
fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user2.Id, user2.Name, user2.Weight, user2.Height)

6.8 value判斷空值的三種情況

pointerchannelfuncinterfacemapslice 的預先宣告都是 nil

var i interface{} //介面沒有指向具體的值
v := reflect.ValueOf(i)
fmt.Printf("v持有值 %t, type of v is Invalid %t\n", v.IsValid(), v.Kind() == reflect.Invalid)  // false

var user *common.User = nil
v = reflect.ValueOf(user) //Value指向一個nil
if v.IsValid() {
	fmt.Printf("v持有的值是nil %t\n", v.IsNil()) //呼叫IsNil()前先確保IsValid(),否則會panic  // true
}

var u common.User //只宣告,裡面的值都是0值
v = reflect.ValueOf(u)
if v.IsValid() {
	fmt.Printf("v持有的值是對應型別的0值 %t\n", v.IsZero()) //呼叫IsZero()前先確保IsValid(),否則會panic  // true
}

參考: http://go.dev/blog/laws-of-reflection

See you ~