天天看點

15. Go的反射

Go中的反射

反射

有時我們需要寫一個函數,這個函數有能力統一處理各種值類型,而這些類型可能無法共享同一個接口,也可能布局未知,也有可能這個類型在我們設計函數時還不存在,這個時候我們就可以用到反射。

空接口可以存儲任意類型的變量,那我們如何知道這個空接口儲存資料的類型是什麼?

值是什麼呢?

  • 可以使用類型斷言
  • 可以使用反射實作,也就是在程式運作時動态的擷取一個變量的類型資訊和值資訊。

把結構體序列化成json字元串,自定義結構體Tab标簽的時候就用到了反射

後面所說的ORM架構,底層就是用到了反射技術

ORM:對象關系映射(Object Relational Mapping,簡稱 ORM)是通過使用描述對象和資料庫之間的映射的中繼資料,将面向對象語言程式中的對象自動持久化到關系資料庫中。

反射的基本介紹

反射是指在程式運作期間對程式本身進行通路和修改的能力。正常情況程式在編譯時,變量被轉換為記憶體位址,變量名不會被編譯器寫入到可執行部分。在運作程式時,程式無法擷取自身的資訊。支援反射的語言可以在程式編譯期将變量的反射資訊,如字段名稱、類型資訊、結構體資訊等整合到可執行檔案中,并給程式提供接口通路反射資訊,這樣就可以在程式運作期擷取類型的反射資訊,并且有能力修改它們。

Go可以實作的功能

  • 反射可以在程式運作期間動态的擷取變量的各種資訊,比如變量的類型類别
  • 如果是結構體,通過反射還可以擷取結構體本身的資訊,比如結構體的字段、結構體的方法。
  • 通過反射,可以修改變量的值,可以調用關聯的方法

Go語言中的變量是分為兩部分的:

  • 類型資訊:預先定義好的元資訊。
  • 值資訊:程式運作過程中可動态變化的。

在Go語言的反射機制中,任何接口值都由是一個具體類型和具體類型的值兩部分組成的。

在Go語言中反射的相關功能由内置的reflect包提供,任意接口值在反射中都可以了解為由 reflect.Type 和 reflect.Value兩部分組成,并且reflect包提供了reflect.TypeOf和reflect.ValueOf兩個重要函數來擷取任意對象的Value 和 Type

reflect.TypeOf()擷取任意值的類型對象

在Go 語言中,使用reflect.TypeOf()函數可以接受任意interface}參數,可以獲得任意值的類型對象(reflect.Type),程式通過類型對象可以通路任意值的類型資訊。

通過反射擷取空接口的類型

func reflectFun(x interface{})  {
    v := reflect.TypeOf(x)
    fmt.Println(v)
}
func main() {
    reflectFun(10)
    reflectFun(10.01)
    reflectFun("abc")
    reflectFun(true)
}      

type name 和 type Kind

在反射中關于類型還劃分為兩種:類型(Type)和種類(Kind)。因為在Go語言中我們可以使用type關鍵字構造很多自定義類型,而種類(Kid)就是指底層的類型,但在反射中,當需要區分指針、結構體等大品種的類型時,就會用到種類(Kind)。舉個例子,我們定義了兩個指針類型和兩個結構體類型,通過反射檢視它們的類型和種類。

Go 語言的反射中像數組、切片、Map、指針等類型的變量,它們的.Name()都是傳回空。

v := reflect.TypeOf(x)
fmt.Println("類型 ", v)
fmt.Println("類型名稱 ", v.Name())
fmt.Println("類型種類 ", v.Kind())      

我們之前可以通過類型斷言來實作空接口類型的數相加操作

func reflectValue(x interface{}) {
    b,_ := x.(int)
    var num = 10 + b
    fmt.Println(num)
}      

到現在的話,我們就可以使用reflect.TypeOf來實作了

func reflectValue2(x interface{}) {
    // 通過反射來擷取變量的原始值
    v := reflect.ValueOf(x)
    fmt.Println(v)
    // 擷取到V的int類型
    var n = v.Int() + 12
    fmt.Println(n)
}      

同時我們還可以通過switch來完成

// 通過反射來擷取變量的原始值
v := reflect.ValueOf(x)
// 擷取種類
kind := v.Kind()
switch kind {
    case reflect.Int:
    fmt.Println("我是int類型")
    case reflect.Float64:
    fmt.Println("我是float64類型")
    default:
    fmt.Println("我是其它類型")
}      

reflect.ValueOf

reflect.ValueOf() 傳回的是reflect.Value類型,其中包含了原始值的值資訊,reflect.Value與原始值之間可以互相轉換

reflect.value類型提供的擷取原始值的方法如下

方法 說明
interface{} 将值以interface{}類型傳回,可以通過類型斷言轉換為指定類型
Int() int64 将值以int類型傳回,所有有符号整型均可以此方式傳回
Uint() uint64 将值以uint類型傳回,所有無符号整型均可以以此方式傳回
Float() float64 将值以雙精度(float 64)類型傳回,所有浮點數(float 32、float64)均可以以此方式傳回

結構體反射

與結構體相關的方法

任意值通過reflect.Typeof)獲得反射對象資訊後,如果它的類型是結構體,可以通過反射值對象(reflect.Type)的NumField()和Field()方法獲得結構體成員的詳細資訊。

reflect.Type中與擷取結構體成員相關的的方法如下表所示。

方法 說明
Field(i int)StructField 根據索引,傳回索引對應的結構體字段的資訊
NumField() int 傳回結構體成員字段數量
FieldByName(name string)(StructField, bool) 根據給定字元串傳回字元串賭赢的結構體字段資訊
FieldByIndex(index []int)StructField 多層成員通路時,根據[] int 提供的每個結構
// 學生結構體
type Student4 struct {
    Name string `json: "name"`
    Age int `json: "age"`
    Score int `json: "score"`
}

func (s Student4)GetInfo()string  {
    var str = fmt.Sprintf("姓名:%v 年齡:%v 成績:%v", s.Name, s.Age, s.Score)
    return str
}
func (s *Student4)SetInfo(name string, age int, score int)  {
    s.Name = name
    s.Age = age
    s.Score = score
}
func (s Student4)PrintStudent()  {
    fmt.Println("列印學生")
}
// 列印結構體中的字段
func PrintStructField(s interface{})  {
    t := reflect.TypeOf(s)
    // 判斷傳遞過來的是否是結構體
    if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
        fmt.Println("請傳入結構體類型!")
        return
    }

    // 通過類型變量裡面的Field可以擷取結構體的字段
    field0 := t.Field(0) // 擷取第0個字段
    fmt.Printf("%#v \n", field0)
    fmt.Println("字段名稱:", field0.Name)
    fmt.Println("字段類型:", field0.Type)
    fmt.Println("字段Tag:", field0.Tag.Get("json"))

    // 通過類型變量裡面的FieldByName可以擷取結構體的字段中
    field1, ok := t.FieldByName("Age")
    if ok {
        fmt.Println("字段名稱:", field1.Name)
        fmt.Println("字段類型:", field1.Type)
        fmt.Println("字段Tag:", field1.Tag)
    }

    // 通過類型變量裡面的NumField擷取該結構體有幾個字段
    var fieldCount = t.NumField()
    fmt.Println("結構體有:", fieldCount, " 個屬性")

    // 擷取結構體屬性對應的值
    v := reflect.ValueOf(s)
    nameValue := v.FieldByName("Name")
    fmt.Println("nameValue:", nameValue)

}
func main() {

    student := Student4{
        "張三",
        18,
        95,
    }
    PrintStructField(student)
}      
// 列印執行方法
func PrintStructFn(s interface{})  {
    t := reflect.TypeOf(s)
    // 判斷傳遞過來的是否是結構體
    if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
        fmt.Println("請傳入結構體類型!")
        return
    }
    // 通過類型變量裡面的Method,可以擷取結構體的方法
    method0 := t.Method(0)
    // 擷取第一個方法, 這個是和ACSII相關
    fmt.Println(method0.Name)

    // 通過類型變量擷取這個結構體有多少方法
    methodCount := t.NumMethod()
    fmt.Println("擁有的方法", methodCount)

    // 通過值變量 執行方法(注意需要使用值變量,并且要注意參數)
    v := reflect.ValueOf(s)
    // 通過值變量來擷取參數
    v.MethodByName("PrintStudent").Call(nil)

    // 手動傳參
    var params []reflect.Value
    params = append(params, reflect.ValueOf("張三"))
    params = append(params, reflect.ValueOf(23))
    params = append(params, reflect.ValueOf(99))
    // 執行setInfo方法
    v.MethodByName("SetInfo").Call(params)

    // 通過值變量來擷取參數
    v.MethodByName("PrintStudent").Call(nil)
}