一看就懂系列之Golang的反射

https://blog.csdn.net/u011957758/article/details/81193806

前言

反射在很多語言中都有其妙用。在計算機科學領域,反射是指一類應用,它們能夠自描述自控制

本文將記錄筆者對於Golang的反射的筆記。

10s後,以下知識點即將靠近:
1.反射的簡介
2.爲什麼使用反射?
3.反射具體能做什麼
4.反射的一些小點

https://www.jianshu.com/p/53adb1e92710

正文

1.反射的簡介

Golang提供了一種機制,在編譯時不知道類型的情況下,可更新變量、運行時查看值調用方法以及直接對他們的佈局進行操作的機制,稱爲反射。

2.爲什麼使用反射?

打個比方,有時候我們需要一個函數可以處理各種類型的值。在不知道類型的情況下,你可能會這麼寫:

// 僞代碼
switch value := value.(type) {
case string:
    // ...一些操作
case int:   
    // ...一些操作  
case cbsStruct: // 自定義的結構體  
    // ...一些操作

// ...
}

有沒發現什麼問題?
這邊存在一個問題:類型很多,這個函數會寫的非常長,而且還可能存在自定的類型,也就是說這個判斷日後可能還要一直改,因爲無法知道未知值到底屬於什麼類型。

無法透視一個未知類型的時候,以上代碼其實不是很合理,這時候就需要有反射來幫忙你處理,反射使用TypeOf和ValueOf函數從接口中獲取目標對象的信息,輕鬆完成目的

3.反射具體能做什麼?

1.獲取變量內部信息

reflect提供了兩種類型來進行訪問接口變量的內容:

類型 作用
reflect.ValueOf() 獲取輸入參數接口中的數據的值,如果爲空則返回0 <- 注意是0
reflect.TypeOf() 動態獲取輸入參數接口中的值的類型,如果爲空則返回nil <- 注意是nil

上代碼

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var name string = "咖啡色的羊駝"

    // TypeOf會返回目標數據的類型,比如int/float/struct/指針等
    reflectType := reflect.TypeOf(name)

    // valueOf返回目標數據的的值,比如上文的"咖啡色的羊駝"
    reflectValue := reflect.ValueOf(name)

    fmt.Println("type: ", reflectType)
    fmt.Println("value: ", reflectValue)
}

輸出:

type:  string
value:  咖啡色的羊駝

更深一層:在以上操作發生的時候,反射將“接口類型的變量”轉爲了“反射的接口類型的變量”,比如上文實際上返回的是reflect.Value和reflect.Type的接口對象。(可以根據ide跟蹤一下相關函數返回類型便知)

2.struct的反射

package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Id   int
    Name string
}

func (s Student) Hello(){
    fmt.Println("我是一個學生")
}

func main() {
    s := Student{Id: 1, Name: "咖啡色的羊駝"}

    // 獲取目標對象
    t := reflect.TypeOf(s)
    // .Name()可以獲取去這個類型的名稱
    fmt.Println("這個類型的名稱是:", t.Name())

    // 獲取目標對象的值類型
    v := reflect.ValueOf(s)
    // .NumField()來獲取其包含的字段的總數
    for i := 0; i < t.NumField(); i++ {
        // 從0開始獲取Student所包含的key
        key := t.Field(i)

        // 通過interface方法來獲取key所對應的值
        value := v.Field(i).Interface()

        fmt.Printf("第%d個字段是:%s:%v = %v \n", i+1, key.Name, key.Type, value)
    }

    // 通過.NumMethod()來獲取Student裏頭的方法
    for i:=0;i<t.NumMethod(); i++ {
        m := t.Method(i)
        fmt.Printf("第%d個方法是:%s:%v\n", i+1, m.Name, m.Type)
    }
}

輸出:

這個類型的名稱是: Student1個字段是:Id:int = 12個字段是:Name:string = 咖啡色的羊駝 
第1個方法是:Hello:func(main.Student)

3.匿名或嵌入字段的反射

package main

import (
    "reflect"
    "fmt"
)

type Student struct {
    Id   int
    Name string
}

type People struct {
    Student // 匿名字段
}

func main() {
    p := People{Student{Id: 1, Name: "咖啡色的羊駝"}}

    t := reflect.TypeOf(p)
    // 這裏需要加一個#號,可以把struct的詳情都給打印出來
    // 會發現有Anonymous:true,說明是匿名字段
    fmt.Printf("%#v\n", t.Field(0))

    // 取出這個學生的名字的詳情打印出來
    fmt.Printf("%#v\n", t.FieldByIndex([]int{0, 1}))

    // 獲取匿名字段的值的詳情
    v := reflect.ValueOf(p)
    fmt.Printf("%#v\n", v.Field(0))
}

輸出:

reflect.StructField{Name:"Student", PkgPath:"", Type:(*reflect.rtype)(0x10aade0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:true}

reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x109f4e0), Tag:"", Offset:0x8, Index:[]int{1}, Anonymous:false}

main.Student{Id:1, Name:"咖啡色的羊駝"}

4.判斷傳入的類型是否是我們想要的類型

package main

import (
    "reflect"
    "fmt"
)

type Student struct {
    Id   int
    Name string
}

func main() {
    s := Student{Id: 1, Name: "咖啡色的羊駝"}
    t := reflect.TypeOf(s)

    // 通過.Kind()來判斷對比的值是否是struct類型
    if k := t.Kind(); k == reflect.Struct {
        fmt.Println("bingo")
    }

    num := 1;
    numType := reflect.TypeOf(num)
    if k := numType.Kind(); k == reflect.Int {
        fmt.Println("bingo")
    }
}

輸出:

bingo
bingo

5.通過反射修改內容

package main

import (
    "reflect"
    "fmt"
)

type Student struct {
    Id   int
    Name string
}

func main() {
    s := &Student{Id: 1, Name: "咖啡色的羊駝"}

    v := reflect.ValueOf(s)

    // 修改值必須是指針類型否則不可行
    if v.Kind() != reflect.Ptr {
        fmt.Println("不是指針類型,沒法進行修改操作")
        return
    }

    // 獲取指針所指向的元素
    v = v.Elem()

    // 獲取目標key的Value的封裝
    name := v.FieldByName("Name")

    if name.Kind() == reflect.String {
        name.SetString("小學生")
    }

    fmt.Printf("%#v \n", *s)


    // 如果是整型的話
    test := 888
    testV := reflect.ValueOf(&test)
    testV.Elem().SetInt(666)
    fmt.Println(test)
}

輸出:

main.Student{Id:1, Name:"小學生"} 
666

6.通過反射調用方法

package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Id   int
    Name string
}

func (s Student) EchoName(name string){
    fmt.Println("我的名字是:", name)
}

func main() {
    s := Student{Id: 1, Name: "咖啡色的羊駝"}

    v := reflect.ValueOf(s)

    // 獲取方法控制權
    // 官方解釋:返回v的名爲name的方法的已綁定(到v的持有值的)狀態的函數形式的Value封裝
    mv := v.MethodByName("EchoName")
    // 拼湊參數
    args := []reflect.Value{reflect.ValueOf("咖啡色的羊駝")}

    // 調用函數
    mv.Call(args)
}

輸出:

我的名字是: 咖啡色的羊駝

4.反射的一些小點

1.使用反射時需要先確定要操作的值是否是期望的類型,是否是可以進行“賦值”操作的,否則reflect包將會毫不留情的產生一個panic。

2.反射主要與Golang的interface類型相關,只有interface類型纔有反射一說。如果有興趣可以看一下TypeOf和ValueOf,會發現其實傳入參數的時候已經被轉爲接口類型了。

// 以下爲截取的源代碼
func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}

func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }
    escapes(i)

    return unpackEface(i)
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章