1 反射的基本概念
- 反射可以在運行時動態的獲取變量的各種信息,比如變量的類型(type)、類別
- 如果是結構體變量,還可以獲取到結構體本身的信息(包括結構體字段、方法)
- 通過反射,可以修改變量的值,可以調用關聯的方法
- 使用反射,需要
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
}
反射常用應用場景:
- 不知道接口調用哪個函數,根據傳入參數在運行時確定調用的具體接口,這種需要對函數或方法反射。
- 對結構體序列化時,如果結構體有指定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 注意事項
- reflect.Value.Kind是獲取變量的類別,返回的是一個常量
- Type是類型,Kind是類別,Type和Kind可能是相同的,也可能是不同的
比如:var num int = 10 num的Type是int,Kind也是int
比如:var stu Student stu的Type是pkg1.Student, Kind是struct pkg2.Student - 通過反射可以讓變量在interface{}和reflect.Value之間互相轉換
- 使用反射的方式來獲取變量的值(並返回對應的類型),要求數據類型匹配,比如x是int,那麼就應該使用reflect.Value(x).Int(),爲不能直接使用,否則會報panic
- 通過反射來修改變量,注意當時用SetXxx方法來設置需要通過對應的指針類型來完成,這樣才能改變傳入的變量的值,同時需要使用到reflect.Value.Elem()方法