一、反射基礎
反射是指在運行時動態地獲取變量的類型(type)和值(value),並且可以修改變量的值或調用其方法。在golang中,通過reflect包實現反射功能。
package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 1.234
// 獲取變量的類型和值
value := reflect.ValueOf(num)
kind := reflect.TypeOf(num).Kind()
fmt.Println("type:", kind, "| value:", value)
// 嘗試修改變量的值
// This will panic at runtime!
value.SetFloat(3.1415926)
fmt.Println("num after modify:", num)
}
運行上面示例代碼,輸出結果為:
type: float64 | value: 1.234
panic: reflect.Value.SetFloat using unaddressable value
可以看到,雖然可以獲取變量num的類型和值,但是修改變量的值會引發panic異常,因為變量num的地址是不可尋址的(unaddressable)。
二、反射中的Kind和Type
反射中的Kind和Type是兩個重要的概念。
Kind表示變量的基礎類型,它是一個枚舉類型(reflect.Kind),包括了所有基礎類型及數組、結構體等複雜類型。
Type表示變量的靜態類型,它描述的是變量的完整類型信息,包括類型名、包名、方法集等,並且與Kind不同,Type是可以尋址的。
可以通過reflect.Value.Interface()方法獲取變量的靜態類型。下面的例子程序演示了Kind和Type的使用方法。
package main
import (
"fmt"
"reflect"
)
type MyStruct struct {
Name string
}
func main() {
var num float64 = 1.234
str := "string"
arr := [...]int{1, 2, 3, 4, 5}
s := MyStruct{"struct"}
values := []interface{}{num, str, arr, &s}
for _, value := range values {
kind := reflect.ValueOf(value).Kind()
tp := reflect.TypeOf(value)
fmt.Printf("value :%v\t| kind: %v\t| type:%v\n", value, kind, tp)
}
}
運行上面的代碼,輸出:
value :1.234 | kind: float64 | type:float64
value :string | kind: string | type:string
value :[5]int | kind: array | type:[5]int
value :&{struct} | kind:ptr | type:*main.MyStruct
可以看到,不同類型變量的Kind和Type是不同的。
三、反射修改值
使用反射可以方便地獲取已知類型變量的值和類型信息,同時也可以修改其值或調用方法。
在golang中,使用反射修改變量值時,必須滿足以下幾個條件:
- 變量必須是可尋址的(addressable)
- 變量必須是可修改的(settable)
下面的示例代碼演示了如何使用反射修改變量值。由於在golang中,只有通過指針才能將變量的地址暴露出來並滿足可修改性,因此示例代碼中所有變量都用指針類型定義。
package main
import (
"fmt"
"reflect"
)
type MyStruct struct {
Name string
}
func main() {
num1 := 2
s := MyStruct{"struct"}
num2 := &num1
p := &s
values := []interface{}{num2, p}
for _, value := range values {
val := reflect.ValueOf(value).Elem()
if val.CanSet() {
switch val.Kind() {
case reflect.Int:
val.Set(reflect.ValueOf(10))
case reflect.Ptr:
elem := val.Elem()
switch elem.Kind() {
case reflect.Struct:
field := elem.FieldByName("Name")
if field.Kind() == reflect.String {
field.SetString("new name")
}
}
}
}
fmt.Println(value)
}
}
運行上面的代碼,輸出:
10
&{new name}
可以看到,通過反射可以方便地修改變量的值。記住,要可修改,必須要可尋址。
四、反射獲取方法和調用方法
使用反射可以很方便地獲取變量的方法集並調用其方法。
在golang中,使用反射獲取方法集時,需要使用reflect.Type來描述類型信息,並且僅通過類型信息可能不足以描述完整的方法集,因此反射還提供了reflect.ValueOf和reflect.MethodByName等函數來獲取方法和調用。
下面示例代碼演示了如何使用反射獲取方法和調用方法。
package main
import (
"fmt"
"reflect"
)
type MyStruct struct {
Name string
}
func (s *MyStruct) SetName(name string) {
s.Name = name
}
func (s *MyStruct) GetName() string {
return s.Name
}
func main() {
s := MyStruct{"struct"}
fmt.Println(s)
val := reflect.ValueOf(&s)
setValue := val.MethodByName("SetName")
getName := val.MethodByName("GetName")
args := []reflect.Value{reflect.ValueOf("new name")}
setValue.Call(args)
name := getName.Call([]reflect.Value{})
fmt.Println(name[0].String())
fmt.Println(s)
}
運行上面的代碼,輸出:
{struct}
new name
{new name}
可以看到,使用反射獲取方法集並調用方法是非常方便的。
五、反射和接口的關係
golang中的接口類型(interface{})是非常重要的數據類型,所有自定義的接口類型都需要實現該類型,實現原理就是動態類型和動態值之間的關係。
使用反射可以很方便地操作接口類型的數據。下面的示例代碼演示了如何使用反射獲取接口類型的動態類型和值。
package main
import (
"fmt"
"reflect"
)
type MyInterface interface {
SayHello(name string) string
}
type MyStruct struct {
Name string
}
func (s *MyStruct) SayHello(name string) string {
return fmt.Sprintf("Hello %s, I'm %s.", name, s.Name)
}
func main() {
s := MyStruct{"struct"}
var myInt MyInterface = &s
// 獲取接口的動態類型和值
val := reflect.ValueOf(myInt).Elem()
tp := reflect.TypeOf(myInt).Elem()
method := val.MethodByName("SayHello")
args := []reflect.Value{reflect.ValueOf("name")}
result := method.Call(args)
fmt.Println(tp.Name())
fmt.Println(result[0].String())
}
運行上面的代碼,輸出:
MyInterface
Hello name, I'm struct.
可以看到,使用反射可以很方便地獲取接口類型的信息,方法集和調用方法。
原創文章,作者:JGPZL,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/333901.html