(十)reflect反射

1 反射的基本概念

  1. 反射可以在運行時動態的獲取變量的各種信息,比如變量的類型(type)、類別
  2. 如果是結構體變量,還可以獲取到結構體本身的信息(包括結構體字段、方法)
  3. 通過反射,可以修改變量的值,可以調用關聯的方法
  4. 使用反射,需要 import "reflect"

reflect.TypeOf(變量名),獲取變量的類型,返回 reflect.Type 類型
reflect.ValueOf(變量名) ,獲取變量的值,返回 reflect.Value 類型,其是一個結構體類型,通過 reflect.Value 可以獲取到關於該變量的很多信息。

變量interface{}reflect.Value是可以相互轉換,reflect.Value再轉成變量的時候需要使用到類型斷言。
在這裏插入圖片描述

type Student struct {}

func test(b interface{}) {
	// (1)將interface{}轉換成 reflect.Value
	rValue := reflect.ValueOf(b)
	// (2)將 reflect.Value 轉換成 interface{}
	iValue := rValue.interface{}
	// (3)將interface{}轉成原來的變量類型,需要使用類型斷言
	v := iValue.(Student)
}
func main() {
	var Stu Student
}

反射常用應用場景:

  1. 不知道接口調用哪個函數,根據傳入參數在運行時確定調用的具體接口,這種需要對函數或方法反射。
  2. 對結構體序列化時,如果結構體有指定Tag,也會使用到反射生成對應的字符串。

2 example

2.1 exa1

反射簡單示例,對(基本數據類型、interface{}、reflect.Value)進行反射的基本操作

package main

import (
	"fmt"
	"reflect"
)

func reflectTest01(b interface{})  {

	// 通過反射獲取的傳入變量的 type、kind、值
	// (1) 先獲取到 reflect.Type
	rType := reflect.TypeOf(b)
	fmt.Printf("rType=%v\n", rType)  // rType=int

	// (2) 獲取到reflect.Value
	rValue := reflect.ValueOf(b)
	fmt.Printf("rValue=%v rValue的類型=%T\n", rValue, rValue)  // rValue=100 rValue的類型=reflect.Value

	// 對rValue進行操作, 不能直接 n2 := 2 + rValue  這樣會報錯,因爲類型不同
	n2 := 2 + rValue.Int()
	fmt.Println("n2=", n2)  // n2= 102

	// (3) 將rValue轉成interface{}
	iV := rValue.Interface()

	// (4) 將 interface{}轉成所需要的類型
	num2 := iV.(int)
	fmt.Println("num2=", num2)  // num2= 100

}

func main() {

	// (1)先定義一個int
	var num int = 100
	reflectTest01(num)

}

2.2 exa2

對結構體進行反射

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name string
	Age int
}

// 對結構體進行反射
func reflectTest01(b interface{})  {
	// 通過反射獲取的傳入變量的 type、kind、值
	// (1) 先獲取到 reflect.Type
	rType := reflect.TypeOf(b)
	fmt.Printf("rType=%v\n", rType)  // rType=main.Student

	// (2) 獲取到reflect.Value
	rValue := reflect.ValueOf(b)
	fmt.Printf("rValue=%v rValue的類型=%T\n", rValue, rValue)  // rValue={tom 20} rValue的類型=reflect.Value
	// 獲取傳入變量的Kind()  Kind的範疇要大於Type
	fmt.Printf("rValue.Kind=%v\n", rValue.Kind())  // rValue.Kind=struct
	kd := rValue.Kind()
	fmt.Printf("kd = %T\n", kd)
	// 也可以通過 rType.Kind()來獲取
	fmt.Printf("rType.kind=%v\n", rType.Kind())   // rType.kind=struct

	// (3) 將rValue轉成interface{}
	iV := rValue.Interface()
	fmt.Printf("iV = %v, iV = %T\n", iV, iV)  // iV = {tom 20}, iV = main.Student

	// (4) 將 interface{}轉成所需要的類型, 可以使用switch來斷言
	//switch expr {
	//
	//}
	stu, ok := iV.(Student)
	if ok {
		fmt.Printf("stu.Name=%v", stu.Name)  //stu.Name=tom
	}
}

func main() {
	stu := Student{
		Name : "tom",
		Age : 20,
	}
	reflectTest01(stu)
}

2.4 exa3

對浮點數類型進行反射

package main

import (
	"fmt"
	"reflect"
)
// 對浮點數類型進行反射
func refectTest01(b interface{})  {

	rType := reflect.TypeOf(b)
	fmt.Printf("rValue.Type() = %v\n", rType)

	rValue := reflect.ValueOf(b)
	fmt.Printf("rValue.Kind() = %v\n", rValue.Kind())

	iV := rValue.Interface()

	num := iV.(float64)
	fmt.Printf("num = %v, num = %T\n", num, num)

}


func main() {

	var num float64 = 3.3
	refectTest01(num)

/*
   rValue.Type() = float64
   rValue.Kind() = float64
   num = 3.3, num = float64
 */
}

2.3 exa4

通過反射修改num、string的值

package main

import (
	"fmt"
	"reflect"
)
// (1)通過反射來修改num int的值

func reflect01(b interface{}) {
	// 獲取reflect.Value
	rValue := reflect.ValueOf(b)

	fmt.Printf("rValue Kind = %v\n", rValue.Kind())

	rValue.Elem().SetInt(20)
}

func main() {

	var num int = 90
	reflect01(&num)
	fmt.Println("num=", num)  // num= 20

	// (2)反射修改字符串的值
	var str string = "tom"
	fs := reflect.ValueOf(&str)
	fs.Elem().SetString("jack")
	fmt.Println("str =", str)  // str = jack


}

2.5 exa5

要求:

  • (1) 編寫一個Cal結構體,有兩個字段 Num1, Num2
  • (2) 編寫GetSub(name string)方法
  • (3) 使用反射遍歷Cal結構體所有的字段信息
  • (4) 使用反射機制完成對GetSub的調用,輸出形式爲:tom完成了減法運算 8 - 3 = 5
package main

import (
	"fmt"
	"reflect"
)

type Cal struct {
	Num1 int
	Num2 int
}

func (c Cal) GetSub(name string) {
	fmt.Printf("%v完成了減法運算 %v - %v = %v", name, c.Num1, c.Num2, c.Num1 - c.Num2)
}

func reflectCal(a interface{})  {
	// rType := reflect.TypeOf(a)
	rValue := reflect.ValueOf(a)

	// 遍歷Cal結構體所有字段的信息
	numOfField := rValue.NumField()
	fmt.Printf("a has %d Fields\n", numOfField)
	for i := 0; i < numOfField; i++ {
		fmt.Printf("file %d 值爲 = %v\n", i, rValue.Field(i))
	}

	// 輸出method
	numOfMethod := rValue.NumMethod()
	fmt.Printf("a has %d Methods\n", numOfMethod)

	var pramas []reflect.Value
	pramas = append(pramas, reflect.ValueOf("tom"))
	rValue.Method(0).Call(pramas)

}
func main() {

	var cal Cal = Cal{
		Num1: 8,
		Num2: 3,
	}

	reflectCal(cal)
	//cal.GetSub("tom")
}

2.6 exa6

使用反射來遍歷結構體的字段,調用結構體的方法,並獲取結構體標籤的值
使用到的兩個反射方法:
1)func (Value) Method: 默認按照方法名排序對應i值,i從0開始
2)func (Value) Call: 傳入參數和返回參數是 []reflect.Value

package main

import (
	"fmt"
	"reflect"
)

type Monster struct {
	Name string `json:"name"`
	Age int `json:"monster_age"`
	Score float64 `json:"成績"`
	Sex string
}
// 方法一:返回兩個數的和
func (s Monster) GetSum(n1, n2 int) int {
	return n1 + n2
}
// 方法二:接收四個值,給s賦值
func (s Monster) Set(name string, age int, score float64, sex string)  {
	s.Name = name
	s.Age = age
	s.Score = score
	s.Sex = sex
}
// 方法三:顯示s的值
func (s Monster) Print() {
	fmt.Println("---start~---")
	fmt.Println(s)
	fmt.Println("---end~---")
}

// 反射結構體
func TestStruct(a interface{}) {
	// 獲取reflect.Type類型
	rType := reflect.TypeOf(a)

	// 獲取reflect.Value類型
	rValue := reflect.ValueOf(a)

	// 獲取a對應的Kind()類別
	kd := rValue.Kind()

	// 如果傳入的不是struct就退出程序
	if kd != reflect.Struct {
		fmt.Println("expect struct")
		return
	}

	// 獲取該結構體有幾個字段
	num := rValue.NumField()
	fmt.Printf("struct has %d fields\n", num)  // 4

	// 獲取結構體的所有字段
	for i := 0; i < 4; i++ {
		fmt.Printf("filed %d: 值爲 =%v", i, rValue.Field(i))

		// 獲取struct的標籤,這裏需要通過reflect.Type來獲取tag標籤的值
		tagVal := rType.Field(i).Tag.Get("json")
		// 如果該字段有tag標籤就顯示出來,否則就不顯示
		if tagVal != "" {
			fmt.Printf("Field %d: tag 爲 %v\n", i, tagVal)
		}
	}

	// 獲取該結構體有多少個方法
	numOfMethod := rValue.NumMethod()
	fmt.Printf("struct has %d methods\n", numOfMethod)  // struct has 3 methods

	// 方法的排序默認是按照 函數名的ASCII碼值 來排序 這裏排序從0開始  方法排序爲 GetSum、Print、Set
	rValue.Method(1).Call(nil)  // 獲取到第二個方法,並調用它
	/*
	---start~---
	{BigHero 400 66.6 }
	---end~---
	*/

	// 調用結構體的第一個方法Method(0),並傳參
	var params []reflect.Value  // 聲明瞭[]reflect.Value
	params = append(params, reflect.ValueOf(10))
	params = append(params, reflect.ValueOf(40))
	res := rValue.Method(0).Call(params)  // 傳入的參數是[]reflect.Value, 返回的是 []reflect.Value
	fmt.Println("res= ", res[0].Int())  // res=  50

}
func main()  {
	// 創建一個Monster實例
	var a Monster = Monster{
		Name: "BigHero",
		Age: 400,
		Score: 66.6,
	}

	// 將Monster實例傳給TestStruct()
	TestStruct(a)
}

2.7 exa7

使用反射來遍歷結構體的字段,調用結構體的方法,並獲取結構體標籤的值,獲取字段的值,並通過地址傳遞的方式去修改字段的值

package main

import (
	"fmt"
	"reflect"
)

type Monster struct {
	Name string `json:"name"`
	Age int `json:"monster_age"`
	Score float64 `json:"成績"`
	Sex string
}
// 方法一:返回兩個數的和
func (s Monster) GetSum(n1, n2 int) int {
	return n1 + n2
}
// 方法二:接收四個值,給s賦值
func (s Monster) Set(name string, age int, score float64, sex string)  {
	s.Name = name
	s.Age = age
	s.Score = score
	s.Sex = sex
}
// 方法三:顯示s的值
func (s Monster) Print() {
	fmt.Println("---start~---")
	fmt.Println(s)
	fmt.Println("---end~---")
}

// 反射結構體
func TestStruct(a interface{}) {
	// 獲取reflect.Type類型
	rType := reflect.TypeOf(a)

	// 獲取reflect.Value類型
	rValue := reflect.ValueOf(a)

	// 獲取a對應的Kind()類別
	fmt.Printf("Kind() = %v\n", rValue.Kind())

	// 修改結構體字段名
	num := rValue.Elem().NumField()
	fmt.Printf("num=%v\n", num)
	rValue.Elem().Field(0).SetString("牛魔王")

	// 獲取結構體的所有字段
	for i := 0; i < num; i++ {
		fmt.Printf("filed %d: 值=%v\n", i, rValue.Elem().Field(i))
	}

	// 獲取struct的標籤,這裏需要通過reflect.Type來獲取tag標籤的值
	tagVal := rType.Elem().Field(0).Tag.Get("json")
	// 如果該字段有tag標籤就顯示出來,否則就不顯示
	if tagVal != "" {
		fmt.Printf("tag 爲 %v\n", tagVal)
	}

	// 獲取該結構體有多少個方法
	numOfMethod := rValue.Elem().NumMethod()
	fmt.Printf("struct has %d methods\n", numOfMethod)  // struct has 3 methods

	// 方法的排序默認是按照 函數名的ASCII碼值 來排序 這裏排序從0開始  方法排序爲 GetSum、Print、Set
	rValue.Elem().Method(1).Call(nil)  // 獲取到第二個方法,並調用它
	/*
		---start~---
		{牛魔王 400 66.6 }
		---end~---
	*/

	// 調用結構體的第一個方法Method(0),並傳參
	var params []reflect.Value  // 聲明瞭[]reflect.Value
	params = append(params, reflect.ValueOf(10))
	params = append(params, reflect.ValueOf(40))
	res := rValue.Elem().Method(0).Call(params)  // 傳入的參數是[]reflect.Value, 返回的是 []reflect.Value
	fmt.Println("res= ", res[0].Int())  // res=  50

}
func main()  {
	// 創建一個Monster實例
	var a Monster = Monster{
		Name: "BigHero",
		Age: 400,
		Score: 66.6,
	}

	// 將Monster實例傳給TestStruct()
	TestStruct(&a)
}

3 注意事項

  1. reflect.Value.Kind是獲取變量的類別,返回的是一個常量
  2. Type是類型,Kind是類別,Type和Kind可能是相同的,也可能是不同的
    比如:var num int = 10 num的Type是int,Kind也是int
    比如:var stu Student stu的Type是pkg1.Student, Kind是struct pkg2.Student
  3. 通過反射可以讓變量在interface{}和reflect.Value之間互相轉換
  4. 使用反射的方式來獲取變量的值(並返回對應的類型),要求數據類型匹配,比如x是int,那麼就應該使用reflect.Value(x).Int(),爲不能直接使用,否則會報panic
  5. 通過反射來修改變量,注意當時用SetXxx方法來設置需要通過對應的指針類型來完成,這樣才能改變傳入的變量的值,同時需要使用到reflect.Value.Elem()方法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章