Golang反射-下篇

語言: CN / TW / HK

本文是Golang反射-上篇的續篇內容,主要介紹反射實際的一些使用

1、判斷型別interface.Type

利用型別斷言來判斷資料型別的用法如下

package main

import "fmt"

func main()  {
	var s interface{} = "abc"
	switch s.(type) {
	case string:
		fmt.Println("s.type=string")
	case int:
		fmt.Println("s.type=int")
	case bool:
		fmt.Println("s.type=bool")
	default:
		fmt.Println("未知的型別")
	}
}

上述型別判斷的問題

  • 型別判斷會寫很多,程式碼很長
  • 型別還會增刪,不靈活

如果使用反射獲取變數內部的資訊

  • reflect包提供ValueOf和TypeOf
  • reflect.ValueOf:獲取輸入介面中資料的值,如果為空返回0
  • reflect.TypeOf:獲取輸入介面中值的型別,如果為空返回nil
  • TypeOf能傳入所有型別,是因為所有的型別都實現了空介面
package main

import (
	"fmt"
	"reflect"
)

func main()  {
	var s interface{} = "abc"
	//TypeOf會返回目標的物件
	reflectType:=reflect.TypeOf(s)
	reflectValue:=reflect.ValueOf(s)
	fmt.Printf("[typeof:%v]\n", reflectType)  // string
	fmt.Printf("[valueof:%v]\n", reflectValue)  // abc
}

2、自定義struct的反射

自定義struct的相關操作

  • 對於成員變數

    • 先獲取interface的reflect.Type,然後遍歷NumField
    • 再通過reflect.Type的Field獲取欄位名及型別
    • 最後通過Field的interface獲取對應的value
  • 對於方法

    • 先獲取interface的reflect.Type,然後遍歷NumMethod
    • 再通過reflect.Type的t.Method獲取真實的方法名
    • 最後通過Name和Type獲取方法的型別和值

注意點

  • 用於對未知型別進行遍歷探測其Field,抽象成一個函式
  • go語言裡面struct成員變數小寫,在反射的時候直接panic()
  • 結構體方法名小寫是不會panic的,反射值也不會被檢視到
  • 指標方法是不能被反射檢視到的
package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string
	Age  int
}

type Student struct {
	Person     // 匿名結構體巢狀
	StudentId  int
	SchoolName string
	Graduated  bool
	Hobbies    []string
	//panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
	//hobbies    []string
	Label      map[string]string
}

func (s *Student) GoHome() {
	fmt.Printf("回家了,sid:%d\n", s.StudentId)
}

//func (s Student) GoHome() {
//	fmt.Printf("回家了,sid:%d\n", s.StudentId)
//}

func (s Student) GotoSchool() {
	fmt.Printf("上學了,sid:%d\n", s.StudentId)
}

func (s *Student) graduated() {
	fmt.Printf("畢業了,sid:%d\n", s.StudentId)
}

//func (s Student) Ggraduated() {
//	fmt.Printf("畢業了,sid:%d\n", s.StudentId)
//}

func reflectProbeStruct(s interface{}) {
	// 獲取目標物件
	t := reflect.TypeOf(s)
	fmt.Printf("物件的型別名稱 %s\n", t.Name())
	// 獲取目標物件的值型別
	v := reflect.ValueOf(s)
	// 遍歷獲取成員變數
	for i := 0; i < t.NumField(); i++ {
		// Field 代表物件的欄位名
		key := t.Field(i)
		value := v.Field(i).Interface()
		// 欄位
		if key.Anonymous {
			fmt.Printf("匿名欄位 第 %d 個欄位,欄位名 %s, 欄位型別 %v, 欄位的值 %v\n", i+1, key.Name, key.Type, value)
		} else {
			fmt.Printf("命名欄位 第 %d 個欄位,欄位名 %s, 欄位型別 %v, 欄位的值 %v\n", i+1, key.Name, key.Type, value)
		}
	}
	// 列印方法
	for i := 0; i < t.NumMethod(); i++ {
		m := t.Method(i)
		fmt.Printf("第 %d 個方法,方法名 %s, 方法型別 %v\n", i+1, m.Name, m.Type)
	}
}

func main() {
	s := Student{
		Person: Person{
			"geek",
			24,
		},
		StudentId:  123,
		SchoolName: "Beijing University",
		Graduated:  true,
		Hobbies:    []string{"唱", "跳", "Rap"},
		//hobbies:    []string{"唱", "跳", "Rap"},
		Label:      map[string]string{"k1": "v1", "k2": "v2"},
	}
	p := Person{
		Name: "張三",
		Age:  100,
	}
	reflectProbeStruct(s)
	reflectProbeStruct(p)
	/*
	物件的型別名稱 Student
	匿名欄位 第 1 個欄位,欄位名 Person, 欄位型別 main.Person, 欄位的值 {geek 24}
	命名欄位 第 2 個欄位,欄位名 StudentId, 欄位型別 int, 欄位的值 123
	命名欄位 第 3 個欄位,欄位名 SchoolName, 欄位型別 string, 欄位的值 Beijing University
	命名欄位 第 4 個欄位,欄位名 Graduated, 欄位型別 bool, 欄位的值 true
	命名欄位 第 5 個欄位,欄位名 Hobbies, 欄位型別 []string, 欄位的值 [唱 跳 Rap]
	命名欄位 第 6 個欄位,欄位名 Label, 欄位型別 map[string]string, 欄位的值 map[k1:v1 k2:v2]
	第 1 個方法,方法名 GotoSchool, 方法型別 func(main.Student)
	物件的型別名稱 Person
	命名欄位 第 1 個欄位,欄位名 Name, 欄位型別 string, 欄位的值 張三
	命名欄位 第 2 個欄位,欄位名 Age, 欄位型別 int, 欄位的值 100
	 */
}

3、結構體標籤和反射

  • json的標籤解析出json
  • yaml的標籤解析出yaml
  • xorm、gorm的標籤標識資料庫db欄位
  • 自定義標籤
  • 原理是t.Field.Tag.Lookup("標籤名")

示例

package main

import (
	"encoding/json"
	"fmt"
	"gopkg.in/yaml.v2"
	"io/ioutil"
)

type Person struct {
	Name string `json:"name" yaml:"yaml_name"`
	Age  int    `json:"age" yaml:"yaml_age"`
	City string `json:"city" yaml:"yaml_city"`
	//City string `json:"-" yaml:"yaml_city"` // 忽略json:"-"
}

// json解析
func jsonWork() {
	// 物件Marshal成字串
	p := Person{
		Name: "geek",
		Age:  24,
		City: "Beijing",
	}
	data, err := json.Marshal(p)
	if err != nil {
		fmt.Printf("json.marshal.err: %v\n", err)
	}
	fmt.Printf("person.marshal.res: %v\n", string(data))

	// 從字串解析成結構體
	p2str := `{
	"name": "張三",
	"age": 38,
	"city": "山東"
	}`
	var p2 Person
	err = json.Unmarshal([]byte(p2str), &p2)
	if err != nil {
		fmt.Printf("json.unmarshal.err: %v\n", err)
		return
	}
	fmt.Printf("person.unmarshal.res: %v\n", p2)
}

// yaml解析
func yamlWork() {
	filename := "a.yaml"
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Printf("ioutil.ReadFile.err: %v\n", err)
		return
	}
	p := &Person{}
	//err = yaml.Unmarshal([]byte(content), p)
	err = yaml.UnmarshalStrict([]byte(content), p)  // 解析嚴格,考慮多餘欄位,忽略欄位等
	if err != nil {
		fmt.Printf("yaml.UnmarshalStrict.err: %v\n", err)
		return
	}
	fmt.Printf("yaml.UnmarshalStrict.res: %v\n", p)
}

func main() {
	jsonWork()
	/*
		person.marshal.res: {"name":"geek","age":24,"city":"Beijing"}
		person.unmarshal.res: {張三 38 山東}
	*/
	yamlWork()
	/*
		yaml.UnmarshalStrict.res: &{李四 18 Shanghai}
	 */
}

解析的yaml內容

yaml_name: 李四
yaml_age: 18
yaml_city: Shanghai
  • 自定義標籤格式解析
package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string `aa:"name"`
	Age  int    `aa:"age"`
	City string `aa:"city"`
}

// CustomParse 自定義解析
func CustomParse(s interface{}) {
	// TypeOf type型別
	r:=reflect.TypeOf(s)
	value := reflect.ValueOf(s)
	for i:=0;i<r.NumField();i++{
		field:=r.Field(i)
		key:=field.Name
		if tag, ok:=field.Tag.Lookup("aa");ok{
			if tag == "-"{
				continue
			}
			fmt.Printf("找到了aa標籤, key: %v, value: %v, tag: %s\n", key, value.Field(i), tag)
		}
	}
}

func main() {
	p := Person{
		Name: "geek",
		Age:  24,
		City: "Beijing",
	}
	CustomParse(p)
	/*
	找到了aa標籤, key: Name, value: geek, tag: name
	找到了aa標籤, key: Age, value: 24, tag: age
	找到了aa標籤, key: City, value: Beijing, tag: city
	 */
}

4、反射呼叫函式

valueFunc := reflect.ValueOf(Add) //函式也是一種資料型別
typeFunc := reflect.TypeOf(Add)
argNum := typeFunc.NumIn()            //函式輸入引數的個數
args := make([]reflect.Value, argNum) //準備函式的輸入引數
for i := 0; i < argNum; i++ {
	if typeFunc.In(i).Kind() == reflect.Int {
		args[i] = reflect.ValueOf(3) //給每一個引數都賦3
	}
}
sumValue := valueFunc.Call(args) //返回[]reflect.Value,因為go語言的函式返回可能是一個列表
if typeFunc.Out(0).Kind() == reflect.Int {
	sum := sumValue[0].Interface().(int) //從Value轉為原始資料型別
	fmt.Printf("sum=%d\n", sum)
}

5、反射呼叫方法

示例

user := User{
	Id:     7,
	Name:   "傑克遜",
	Weight: 65.5,
	Height: 1.68,
}
valueUser := reflect.ValueOf(&user)              //必須傳指標,因為BMI()在定義的時候它是指標的方法
bmiMethod := valueUser.MethodByName("BMI")       //MethodByName()通過Name返回類的成員變數
resultValue := bmiMethod.Call([]reflect.Value{}) //無引數時傳一個空的切片
result := resultValue[0].Interface().(float32)
fmt.Printf("bmi=%.2f\n", result)

//Think()在定義的時候用的不是指標,valueUser可以用指標也可以不用指標
thinkMethod := valueUser.MethodByName("Think")
thinkMethod.Call([]reflect.Value{})

valueUser2 := reflect.ValueOf(user)
thinkMethod = valueUser2.MethodByName("Think")
thinkMethod.Call([]reflect.Value{})

過程

  • 首先通過reflect.ValueOf(p1) 獲取得到反射型別物件
  • reflect.ValueOf(p1).MethodByName需 要傳入準確的方法名稱(名稱不對會panic: reflect: call of reflect.Value.Call on zero Value),MethodByName代表註冊
  • []reflect.Value 這是最終需要呼叫方法的引數,無引數傳空切片
  • call呼叫
package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name   string
	Age    int
	Gender string
}

func (p Person) ReflectCallFuncWithArgs(name string, age int) {
	fmt.Printf("呼叫的是帶引數的方法, args.name: %s, args.age: %d, p.name: %s, p.age: %d\n",
		name,
		age,
		p.Name,
		p.Age,
	)
}

func (p Person) ReflectCallFuncWithNoArgs() {
	fmt.Printf("呼叫的是不帶引數的方法\n")
}

func main() {
	p1 := Person{
		Name:   "geek",
		Age:    24,
		Gender: "男",
	}
	// 1.首先通過reflect.ValueOf(p1)獲取得到反射值型別
	getValue := reflect.ValueOf(p1)
	// 2.帶引數的方法呼叫
	methodValue1 := getValue.MethodByName("ReflectCallFuncWithArgs")
	// 引數是reflect.Value的切片
	args1 := []reflect.Value{reflect.ValueOf("張三"), reflect.ValueOf(30)}
	methodValue1.Call(args1)
	// 3.不帶引數的方法呼叫
	methodValue2 := getValue.MethodByName("ReflectCallFuncWithNoArgs")
	// 引數是reflect.Value的切片
	args2 := make([]reflect.Value, 0)
	methodValue2.Call(args2)
	/*
	呼叫的是帶引數的方法, args.name: 張三, args.age: 30, p.name: geek, p.age: 24
	呼叫的是不帶引數的方法
	 */
}

6、反射建立值

6.1 反射建立struct

t := reflect.TypeOf(User{})
value := reflect.New(t) //根據reflect.Type建立一個物件,得到該物件的指標,再根據指標提到reflect.Value
value.Elem().FieldByName("Id").SetInt(10)
value.Elem().FieldByName("Name").SetString("宋江")
value.Elem().FieldByName("Weight").SetFloat(78.)
value.Elem().FieldByName("Height").SetFloat(168.4)
user := value.Interface().(*User) //把反射型別轉成go原始資料型別
fmt.Printf("id=%d name=%s weight=%.1f height=%.1f\n", user.Id, user.Name, user.Weight, user.Height)

6.2 反射建立slice

var slice []User
sliceType := reflect.TypeOf(slice)
sliceValue := reflect.MakeSlice(sliceType, 1, 3) //reflect.MakeMap、reflect.MakeSlice、reflect.MakeChan、reflect.MakeFunc
sliceValue.Index(0).Set(reflect.ValueOf(User{
	Id:     8,
	Name:   "李達",
	Weight: 80,
	Height: 180,
}))
users := sliceValue.Interface().([]User)
fmt.Printf("1st user name %s\n", users[0].Name)

6.3 反射建立map

var userMap map[int]*User
mapType := reflect.TypeOf(userMap)
// mapValue:=reflect.MakeMap(mapType)
mapValue := reflect.MakeMapWithSize(mapType, 10) //reflect.MakeMap、reflect.MakeSlice、reflect.MakeChan、reflect.MakeFunc

user := &common.User{
	Id:     7,
	Name:   "傑克遜",
	Weight: 65.5,
	Height: 1.68,
}
key := reflect.ValueOf(user.Id)
mapValue.SetMapIndex(key, reflect.ValueOf(user))                    //SetMapIndex 往map裡新增一個key-value對
mapValue.MapIndex(key).Elem().FieldByName("Name").SetString("令狐一刀") //MapIndex 根據Key取出對應的map
userMap = mapValue.Interface().(map[int]*User)
fmt.Printf("user name %s %s\n", userMap[7].Name, user.Name)

7、反射修改值

反射修改值要求必須是指標型別

修改值的操作:pointer.Elem().Setxxx()

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var num float64 = 3.14
	fmt.Printf("原始值 %f\n", num)
	// 通過reflect.ValueOf獲取num中的value,必須是指標才可以修改值
	//pointer := reflect.ValueOf(num)  // 直接傳值會panic
	pointer := reflect.ValueOf(#)
	newValue := pointer.Elem()
	// 賦新的值
	newValue.SetFloat(5.66)
	fmt.Printf("新的值 %f\n", num)
}

7.1 反射修改struct

user := User{
	Id:     7,
	Name:   "傑克遜",
	Weight: 65.5,
	Height: 1.68,
}
valueUser := reflect.ValueOf(&user)
// valueS.Elem().SetInt(8)//會panic
valueUser.Elem().FieldByName("Weight").SetFloat(68.0) //FieldByName()通過Name返回類的成員變數。不能在指標Value上呼叫FieldByName
addrValue := valueUser.Elem().FieldByName("addr")
if addrValue.CanSet() {
	addrValue.SetString("北京")
} else {
	fmt.Println("addr是未匯出成員,不可Set") //以小寫字母開頭的成員相當於是私有成員
}

7.2 反射修改slice

下面示例,間接的實現了append功能

users := make([]*User, 1, 5) //len=1,cap=5

sliceValue := reflect.ValueOf(&users) //準備通過Value修改users,所以傳users的地址
if sliceValue.Elem().Len() > 0 {      //取得slice的長度
	sliceValue.Elem().Index(0).Elem().FieldByName("Name").SetString("哈哈哈")
	// u0 := users[0]
	fmt.Printf("1st user name change to %s\n", users[0].Name)
}

sliceValue.Elem().SetCap(3) //新的cap必須位於原始的len到cap之間
sliceValue.Elem().SetLen(2)
//呼叫reflect.Value的Set()函式修改其底層指向的原始資料
sliceValue.Elem().Index(1).Set(reflect.ValueOf(&User{
	Id:     8,
	Name:   "geek",
	Weight: 80,
	Height: 180,
}))
fmt.Printf("2nd user name %s\n", users[1].Name)

7.3 反射修改map

u1 := &User{
	Id:     7,
	Name:   "傑克遜",
	Weight: 65.5,
	Height: 1.68,
}
u2 := &User{
	Id:     8,
	Name:   "傑克遜",
	Weight: 65.5,
	Height: 1.68,
}
userMap := make(map[int]*User, 5)
userMap[u1.Id] = u1

mapValue := reflect.ValueOf(&userMap)                                                         //準備通過Value修改userMap,所以傳userMap的地址
mapValue.Elem().SetMapIndex(reflect.ValueOf(u2.Id), reflect.ValueOf(u2))                      //SetMapIndex 往map裡新增一個key-value對
mapValue.Elem().MapIndex(reflect.ValueOf(u1.Id)).Elem().FieldByName("Name").SetString("令狐一刀") //MapIndex 根據Key取出對應的map
for k, user := range userMap {
	fmt.Printf("key %d name %s\n", k, user.Name)
}

See you ~