1. 程式人生 > >GO語言使用之Reflect(反射)

GO語言使用之Reflect(反射)

一、從案列場景引入反射

定義了兩個函式test1和test2,定義一個介面卡函式用作統一處理介面:
(1) 定義了兩個函式

test1 := func(v1 int, v2 int) {
    t.Log(v1, v2)
}
test2 := func(v1 int, v2 int, s string) {
    t.Log(v1, v2, s)
}

(2) 定義一個介面卡函式用作統一處理介面, 其大致結構如下:

bridge := func(call interface{}, args ...interface{}) {
 //內容       
}
//實現呼叫test1對應的函式
bridge(test1, 1, 2) //實現呼叫test2對應的函式 bridge(test2, 1, 2, "test2")

(3) 要求使用反射機制完成

package test
import (
    "testing"
     "reflect"       
)
/*反射的最佳實踐*/


/*
3) 定義了兩個函式test1和test2,定義一個介面卡函式用作統一處理介面【瞭解】:
(1) 定義了兩個函式
test1 := func(v1 int, v2 int) {
    t.Log(v1, v2)
}
test2 := func(v1 int, v2 int, s string) {
    t.Log(v1, v2, s)
}
(2) 定義一個介面卡函式用作統一處理介面, 其大致結構如下:
bridge := func(call interface{}, args ...interface{}) {
 //內容       
}
//實現呼叫test1對應的函式
bridge(test1, 1, 2) 
//實現呼叫test2對應的函式
bridge(test2, 1, 2, "test2")

(3) 要求使用反射機制完成 
*/
func TestReflectFunc(t *testing.T) { call1 := func(v1 int, v2 int) { t.Log(v1, v2) } call2 := func(v1 int, v2 int, s string) { t.Log(v1, v2, s) } var ( function reflect.Value inValue []reflect.Value n int ) bridge := func
(call interface{}, args ...interface{}) { n = len(args) inValue = make([]reflect.Value, n) for i := 0; i < n; i++ { inValue[i] = reflect.ValueOf(args[i]) } function = reflect.ValueOf(call) function.Call(inValue) } bridge(call1, 1, 2) bridge(call2, 1, 2, "test2") }

二、反射的基本介紹

1、反射可以在執行時動態獲取變數的各種資訊, 比如變數的型別(type),類別(kind)
2、 如果是結構體變數,還可以獲取到結構體本身的資訊(包括結構體的欄位、方法)
3、通過反射,可以修改變數的值,可以呼叫關聯的方法。
4、使用反射,需要 import (“reflect”)
5、 示意圖

這裡寫圖片描述

三、反射的應用場景

1、反射常見應用場景有以下兩種
不知道介面呼叫哪個函式,根據傳入引數在執行時確定呼叫的具體介面,這種需要對函式或方法反射。例如以下這種橋接模式, 比如我前面提出問題。

第一個引數funcPtr以介面的形式傳入函式指標,函式引數args以可變引數的形式傳入,bridge函式中可以用反射來動態執行funcPtr函式
2、對結構體序列化時,如果結構體有指定Tag, 也會使用到反射生成對應的字串。
這裡寫圖片描述

四、反射重要的函式和概念

1、reflect.TypeOf(變數名),獲取變數的型別,返回reflect.Type型別
2、reflect.ValueOf(變數名),獲取變數的值,返回reflect.Value型別reflect.Value 是一個結構體型別。【看文件】, 通過reflect.Value,可以獲取到關於該變數的很多資訊
3、變數、interface{} 和 reflect.Value是可以相互轉換的,這點在實際開發中,會經常使用到。

變數 interface{} 和reflect.Value()是可以相互轉換的,
變數 interface{} 和valueOf()之間的轉換
這裡寫圖片描述
這裡寫圖片描述

五、快速入門的案例

反射普通變數和結構體

//反射操作方法-基本資料型別float64
func reflectOper(b interface{})  {
    //通過反射獲取的傳入的變數的 type , kind, 值
    //1. 先獲取到 reflect.Type
    rTye := reflect.TypeOf(b)
    fmt.Println("rtype=",rTye)
    rKind := rTye.Kind()
    fmt.Println("rKind=",rKind)
    //2. 獲取到 reflect.Value
    rVal := reflect.ValueOf(b)
    fmt.Println("rVal=",rVal)
    // 將 reflect.Value 轉成 interface{}
    iV := rVal.Interface()
    fmt.Println("iV=",iV)
    //將 interface{} 通過斷言轉成需要的型別
    num := iV.(float64)
    fmt.Println("num=",num)
}

type Student struct{
    Name string
    Age int

}

//反射操作方法-結構體
func reflectOPerStruct(b interface{})  {
    //通過反射獲取的傳入的變數的 type , kind, 值
    //1. 先獲取到 reflect.Type
    rTyp := reflect.TypeOf(b)
    fmt.Println("rType=", rTyp)

    //2. 獲取到 reflect.Value
    rVal := reflect.ValueOf(b)

    fmt.Println("rVal=", rVal)

    //下面我們將 rVal 轉成 interface{}
    iV := rVal.Interface()
    fmt.Printf("iv=%v iv type=%T \n", iV, iV)
    //將 interface{} 通過斷言轉成需要的型別

    stu, ok := iV.(Student)
    if ok {
        fmt.Printf("stu.Name=%v\n", stu.Name)
    }

}

func RefelctDemo(){
    // var num float64 = 12.0
    // reflectOper(num)//對普通變數進行反射
    stu :=Student{
        Name : "張三",
        Age : 30,
    }
    reflectOPerStruct(stu)//對結構體進行反射
}

測試結果:

e:\GoProject\src\go_code\grammar\reflect\code\main>go run main.go
rType= utils.Student
rVal= {張三 30}
iv={張三 30} iv type=utils.Student
stu.Name=張三

e:\GoProject\src\go_code\grammar\reflect\code\main>

六、 使用細節和注意事項

1、reflect.Value.Kind,獲取變數的類別,返回的是一個常量
這裡寫圖片描述
2、Type 和 Kind 的區別
Type是型別, Kind是類別, Type 和 Kind 可能是相同的,也可能是不同的.
比如: var num int = 10 num的Type是int , Kind也是int
比如: var stu Student stu的Type是 pkg1.Student , Kind是struct
3、通過反射可以在讓變數在 interface{} 和Reflect.Value 之間相互轉換,

4、使用反射的方式來獲取變數的值(並返回對應的型別),要求資料型別匹配,比如x是int, 那麼就應該使用 reflect.Value(x).Int(), 而不能使用其它的,否則報panic。
這裡寫圖片描述
這裡寫圖片描述
5、通過反射的來修改變數, 注意當使用SetXxx方法來設定需要通過對應的指標型別來完成, 這樣才能改變傳入的變數的值, 同時需要使用到reflect.Value.Elem()方法
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
七、反射最佳實踐
1、使用反射來遍歷結構體的欄位,呼叫結構體的方法,並獲取結構體標籤的值

import (
    "reflect"
    "fmt"
)

/*反射的最佳實踐*/
// 1、使用反射來遍歷結構體的欄位,呼叫結構體的方法,並獲取結構體標籤的值
/*
分析:
1、結構體 有四個欄位 Name Age   Score Sex  
2、宣告結構體方法 GetSum  Set Print()
3、TestStruct 處理結構體方法和欄位
4、main方法呼叫
*/

type Monster struct{
    Name string `json:"name"`
    Age int      `json:"age"`
    Score float64 `json:"score"`
    Sex string 

}

func (m Monster)Print()  {
    fmt.Println("---start~----")
    fmt.Println(m)
    fmt.Println("---end~----")
}

func (m Monster)GetSum(n1, n2 int) int {
    return n1 + n2
}

func (m Monster)Set(name string, age int, score float64, sex string) Monster {
    m.Name = name
    m.Age = age
    m.Score = score
    m.Sex = sex

    return m
}

func StructOpera(i interface{})  {
    //獲取reflect.Type 型別
    rType := reflect.TypeOf(i)
    //獲取reflect.Value 型別
    rValue := reflect.ValueOf(i)
    //獲取到a對應的類別
    rKind := rValue.Kind()
    //如果傳入的不是struct,就退出
    if rKind != reflect.Struct {
        fmt.Println("expect struct")
        return
    }

    //獲取到該結構體有幾個欄位
    numField := rValue.NumField()

    //變數結構體的所有欄位

    for i := 0; i < numField; i++ {
        fmt.Printf("Field %d: 值為=%v\n", 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)
        }
    }



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


    //方法的排序預設是按照 函式名的排序(ASCII碼)
    rValue.Method(1).Call(nil) //獲取到第二個方法。呼叫它

    //呼叫結構體的第1個方法Method(0)
    //聲明瞭 []reflect.Value
    var params []reflect.Value

    params = append(params,reflect.ValueOf(10))
    params = append(params,reflect.ValueOf(20))

    res := rValue.Method(0).Call(params)//傳入的引數是 []reflect.Value, 返回[]reflect.Value 
    fmt.Println("res=", res[0].Int()) //返回結果, 返回的結果是 []reflect.Value*/

    var params1 []reflect.Value

    params1 = append(params1,reflect.ValueOf("狐狸精"))
    params1 = append(params1,reflect.ValueOf(500))
    params1 = append(params1,reflect.ValueOf(60.0))
    params1 = append(params1,reflect.ValueOf("女"))

    res1 := rValue.Method(2).Call(params1)//傳入的引數是 []reflect.Value, 返回[]reflect.Value 
    fmt.Println("res=", res1[0])

}

func StructOperaDemo()  {
    //建立了一個Monster例項
    var m Monster = Monster{
        Name:  "黃鼠狼精",
        Age:   400,
        Score: 30.8,
    }
    //將Monster例項傳遞給StructOpera函式
    StructOpera(m)  
    fmt.Println("m=",m)
}

測試結果:

Field 0: 值為=黃鼠狼精
Field 0: tag為=name
Field 1: 值為=400
Field 1: tag為=age
Field 2: 值為=30.8
Field 2: tag為=score
Field 3: 值為=
struct has 3 methods
---start~----
{黃鼠狼精 400 30.8 }
---end~----
res= 30
res= {狐狸精 500 60 女}
m= {黃鼠狼精 400 30.8 }

2、使用反射的方式來獲取結構體的tag標籤, 遍歷欄位的值,修改欄位值,呼叫結構體方法(要求:通過傳遞地址的方式完成, 在前面案例上修改即可)

package test


import (
    "encoding/json"
    "fmt"
    "reflect"
)

/*反射的最佳實踐*/


// 3、使用反射的方式來獲取結構體的tag標籤, 遍歷欄位的值,修改欄位值,呼叫結構體方法
// (要求:通過傳遞地址的方式完成, 在前面案例上修改即可)
type Monster struct {
    Name  string `json:"monster_name"`
    Age   int
    Score float32
    Sex   string
}

func (s Monster) Print() {
    fmt.Println("---start----")
    fmt.Println(s)
    fmt.Println("---end----")
}



func TestStruct(a interface{}) {
    tye := reflect.TypeOf(a)
    val := reflect.ValueOf(a)
    kd := val.Kind()
    if kd != reflect.Ptr && val.Elem().Kind() == reflect.Struct {
        fmt.Println("expect struct")
        return
    }

    num := val.Elem().NumField()
    val.Elem().Field(0).SetString("白象精")
    for i := 0; i < num; i++ {
        fmt.Printf("%d %v\n", i, val.Elem().Field(i).Kind())
    }

    fmt.Printf("struct has %d fields\n", num)

    tag := tye.Elem().Field(0).Tag.Get("json")
    fmt.Printf("tag=%s\n", tag)

    numOfMethod := val.Elem().NumMethod()
    fmt.Printf("struct has %d methods\n", numOfMethod)

    val.Elem().Method(0).Call(nil)
}

func StructOperaDemo2() {
    var a Monster = Monster{
        Name:  "黃獅子",
        Age:   408,
        Score: 92.8,
    }

    //先說明一下,Marshal就是通過反射獲取到struct的tag值的...
    result, _ := json.Marshal(a)
    fmt.Println("json result:", string(result))

    TestStruct(&a)
    fmt.Println(a)
}

測試結果:

json result: {"monster_name":"黃獅子","Age":408,"Score":92.8,"Sex":""}
0 string
1 int
2 float32
3 string
struct has 4 fields
tag=monster_name
struct has 1 methods
---start----
{白象精 408 92.8 }
---end----
{白象精 408 92.8 }

3、使用反射操作任意結構體型別:【瞭解】

package test
import (
    "reflect"
    "testing"
)
/*反射的最佳實踐*/
// 5、使用反射操作任意結構體型別:【瞭解】
type user struct {
    UserId string
    Name string

} 

func TestReflectStruct(t *testing.T) {
    var (
        model *user
        sv    reflect.Value
    )
    model = &user{}
    sv = reflect.ValueOf(model)
    t.Log("reflect.ValueOf", sv.Kind().String())
    sv = sv.Elem()
    t.Log("reflect.ValueOf.Elem", sv.Kind().String())
    sv.FieldByName("UserId").SetString("12345678")
    sv.FieldByName("Name").SetString("nickname")
    t.Log("model", model)
}

4、使用反射建立並操作結構體

package test

import (
    "testing"
    "reflect"
)

/*反射的最佳實踐*/
// 6、使用反射建立並操作結構體
type User struct {
    UserId string
    Name string
} 
func TestReflectStructPtr(t *testing.T) {
    var (
        model *User 
        st    reflect.Type
        elem  reflect.Value
    )
    st = reflect.TypeOf(model) //獲取型別 *user
    t.Log("reflect.TypeOf", st.Kind().String()) // ptr
    st = st.Elem() //st指向的型別
    t.Log("reflect.TypeOf.Elem", st.Kind().String()) //struct
    elem = reflect.New(st) //New返回一個Value型別值,該值持有一個指向型別為typ的新申請的零值的指標
    t.Log("reflect.New", elem.Kind().String()) // ptr
    t.Log("reflect.New.Elem", elem.Elem().Kind().String()) //struct
    //model就是建立的user結構體變數(例項)
    model = elem.Interface().(*User) //model 是 *user 它的指向和elem是一樣的.
    elem = elem.Elem() //取得elem指向的值
    elem.FieldByName("UserId").SetString("12345678") //賦值..
    elem.FieldByName("Name").SetString("nickname")
    t.Log("model model.Name", model, model.Name)
}