golang的reflection(轉)

来源:https://www.cnblogs.com/TimLiuDream/archive/2018/11/14/9960746.html
-Advertisement-
Play Games

反射reflection 可以大大提高程式的靈活性,使得interface{}有更大的發揮餘地 反射可以使用TypeOf和ValueOf函數從介面中獲取目標對象信息 反射會將匿名欄位作為獨立欄位(匿名欄位的本質) 想要利用反射修改對象狀態,前提是interface.data是settable,即po ...


作者:BGbiao
鏈接:https://www.jianshu.com/p/42c19f88df6c
來源:簡書

反射reflection

  • 可以大大提高程式的靈活性,使得interface{}有更大的發揮餘地
  • 反射可以使用TypeOf和ValueOf函數從介面中獲取目標對象信息
  • 反射會將匿名欄位作為獨立欄位(匿名欄位的本質)
  • 想要利用反射修改對象狀態,前提是interface.data是settable,即pointer-interface
  • 通過反射可以“動態”調用方法

常用的類型、函數和方法

//返回動態類型i的類型,如果i是一個空結構體類型,TypeOf將返回nil
func TypeOf(i interface{}) Type

//Type 介面類型
type Type interface {
    Align() int
    FieldAlign() int
    //指定結構體中方法的下標,返回某個方法的對象,需要註意的是返回的Method是一個獨立的結構體
    Method(int) Method
    /*
    type Method struct {
        Name string
        PkgPath string
        Type Type
        Func Value
        Index int
    }
    */


    MethodByName(string) (Method, bool)

    //返回該結構體類型的方法下標
    NumMethod() int
    //返回類型的名稱,即動態類型i的名稱
    Name() string
    PkgPath() string
    Size() uintptr
    String() string
    Kind() Kind
    Implements(u Type) bool
    AssignableTo(u Type) bool
    ConvertibleTo(u Type) bool
    Comparable() bool
    Bits() int
    ChanDir() ChanDir
    IsVariadic() bool
    Elem() Type
    //返回結構體類型第i個欄位
    Field(i int) StructField
    //StructField結構體
    //type StructField struct {
    // Name string
    // PkgPath string
    // Type Type
    // Tag  StructTag
    // Offset uintptr
    // Index []int
    // Anonymous bool

    //根據結構體欄位索引獲取嵌入欄位的結構體信息
    FieldByIndex(index []int) StructField

    FieldByName(name string) (StructField, bool)
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    In(i int) Type
    Key() Type
    Len() int
    //返回動態類型i(結構體欄位)的欄位總數
    NumField() int
    NumIn() int
    NumOut() int
    Out(i int) Type
}

//返回介面i的一個初始化的新值.ValueOf(nil)返回一個零值
func ValueOf(i interface{}) Value

// Value結構體
type Value struct {

}
// Value結構體的一些方法
// 返回結構體v中的第i個欄位。如果v的類型不是結構體或者i超出了結構體的範圍,則會出現panic
func (v Value) Field(i int) Value

//以介面類型返回v的當前值
func (v Value) Interface() (i interface{})
//等價於.
var i interface{} = (v's underlying value)


//通過反射方式修改結構體對象的一些方法

//返回介面v包含或者指針v包含的值
func (v Value) Elem() Value
//判斷該介面v是否可以被set修改
func (v Value) CanSet() bool

//使用另外一個反射介面去修改反射值
func (v Value) Set(x Value)
//其他不同類型的Set
func (v Value) SetBool(x bool)
func (v Value) SetBytes(x []byte)
func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64)
//設置結構體對象v的長度為n
func (v Value) SetLen(n int)
func (v Value) SetString(x string)


//一些輔助方法
//返回反射結構體的Value的類型.如果v為零值,IsValid將返回false
func (v Value) Kind() Kind
//判斷value是否為有效值,通常用在判斷某個欄位是否在反射體的Value中
func (v Value) IsValid() bool

//Kind常量
type Kind uint
const (
        Invalid Kind = iota
        Bool
        Int
        Int8
        Int16
        Int32
        Int64
        Uint
        Uint8
        Uint16
        Uint32
        Uint64
        Uintptr
        Float32
        Float64
        Complex64
        Complex128
        Array
        Chan
        Func
        Interface
        Map
        Ptr
        Slice
        String
        Struct
        UnsafePointer
)

 

反射的基本操作

通過反射來獲取結構體欄位的名稱以及其他相關信息。

package main

import (
    "fmt"
    "reflect"
)

//定義結構體
type User struct {
    Id   int
    Name string
    Age  int
}

//定義結構體方法
func (u User) Hello() {
    fmt.Println("Hello xuxuebiao")
}

func main() {
    u := User{1, "bgops", 25}
    Info(u)
    u.Hello()
}

//定義一個反射函數,參數為任意類型
func Info(o interface{}) {
    //使用反射類型獲取o的Type,一個包含多個方法的interface
    t := reflect.TypeOf(o)
    //列印類型o的名稱
    fmt.Println("type:", t.Name())

    //使用反射類型獲取o的Value,一個空的結構體
    v := reflect.ValueOf(o)
    fmt.Println("Fields:")

    //t.NumField()列印結構體o的欄位個數(Id,Name,Age共三個)
    for i := 0; i < t.NumField(); i++ {
        //根據結構體的下標i來獲取結構體某個欄位,並返回一個新的結構體
        /**
        type StructField struct {
            Name string
            PkgPath string
            Type      Type
            Tag       StructTag
            Offset    uintptr
            Index     []int
            Anonymous bool
        }
        **/
        f := t.Field(i)

        //使用結構體方法v.Field(i)根據下標i獲取欄位Value(Id,Name,Age)
        //在根據Value的Interface()方法獲取當前的value的值(interface類型)
        val := v.Field(i).Interface()
        fmt.Printf("%6s:%v = %v\n", f.Name, f.Type, val)
    }

    //使用t.NumMethod()獲取所有結構體類型的方法個數(只有Hello()一個方法)
    //介面Type的方法NumMethod() int
    for i := 0; i < t.NumMethod(); i++ {
        //使用t.Method(i)指定方法下標獲取方法對象。返回一個Method結構體
        //Method(int) Method
        m := t.Method(i)
        //列印Method結構體的相關屬性
        /*
        type Method struct {
              Name    string
              PkgPath string
              Type    Type
              Func    Value
              Index   int
        }
        */
        fmt.Printf("%6s:%v\n", m.Name, m.Type)
    }
}

 

輸出

type: User
Fields:
    Id:int = 1
  Name:string = bgops
   Age:int = 25
 Hello:func(main.User)
Hello xuxuebiao

 

註意:我們上面的示例是使用值類型進行進行反射構造的。如果是指針類型的話,我們需要使用reflect.Struct欄位進行判斷介面類型的Kind()方法

if k := t.Kind();k != reflect.Struct {
    fmt.Println("非值類型的反射")
    return
}

 

匿名欄位的反射以及嵌入欄位

註意:反射會將匿名欄位當做獨立的欄位去處理,需要通過類型索引方式使用FieldByIndex方法去逐個判斷

//根據指定索引返回對應的嵌套欄位
FieldByIndex(index []int) StructField

type StructField struct {
    Name    string
    PkgPath string
    Type    Type
    Tag     StructTag
    Offset  uintptr
    Index   []int
    Anonymous   bool //是否為匿名欄位
}
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id   int
    Name string
    Age  int
}

type Manager struct {
    User
    title string
}

func main() {
    //註意匿名欄位的初始化操作
    m := Manager{User: User{1, "biaoge", 24}, title: "hello biao"}
    t := reflect.TypeOf(m)

    fmt.Printf("%#v\n", t.FieldByIndex([]int{0}))
    fmt.Printf("%#v\n", t.FieldByIndex([]int{1}))
    fmt.Printf("%#v\n", t.FieldByIndex([]int{0, 0}))
    fmt.Printf("%#v\n", t.FieldByIndex([]int{0, 1}))

}

輸出:

reflect.StructField{Name:"User", PkgPath:"", Type:(*reflect.rtype)(0x4ac660), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:true}
reflect.StructField{Name:"title", PkgPath:"main", Type:(*reflect.rtype)(0x49d820), Tag:"", Offset:0x20, Index:[]int{1}, Anonymous:false}
reflect.StructField{Name:"Id", PkgPath:"", Type:(*reflect.rtype)(0x49d1a0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x49d820), Tag:"", Offset:0x8, Index:[]int{1}, Anonymous:false}

 

通過反射修改目標對象

通過反射的方式去修改對象的某個值。需要註意的亮點是,首先,需要找到對象相關的名稱,其次需要找到合適的方法去修改相應的值。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    x := 123
    v := reflect.ValueOf(&x)
    v.Elem().SetInt(5256)
    fmt.Println(x)
}

 

輸出:

5256
修改I的時候需要找到對象欄位的名稱;並且判斷類型,使用相對正確的類型修改值
v.FieldByName("Name");f.Kind() == reflect.String {
    f.SetString("test string")
}

判斷是否找到正確的欄位名稱:

f := v.FieldByName("Name1")
//判斷反射對象Value中是否找到Name1欄位
if !f.IsValid() {
    fmt.Println("bad field")
    return
}
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id   int
    Name string
    Age  int
}

//使用反射方式對結構體對象的修改有兩個條件
//1.通過指針
//2.必須是可set的方法
func main() {
    num := 123
    numv := reflect.ValueOf(&num)
    //通過Value的Elem()和SetX()方法可直接對相關的對象進行修改
    numv.Elem().SetInt(666)
    fmt.Println(num)

    u := User{1, "biao", 24}
    uu := reflect.ValueOf(&u)
    //Set()後面的必須是值類型
    //func (v Value) Set(x Value)
    test := User{2, "bgops", 2}
    testv := reflect.ValueOf(test)
    uu.Elem().Set(testv)
    fmt.Println("Change the test to u with Set(x Value)", uu)

    //此時的U已經被上面那個uu通過指針的方式修改了
    Set(&u)
    fmt.Println(u)
}

func Set(o interface{}) {
    v := reflect.ValueOf(o)
    //判斷反射體值v是否是Ptr類型並且不能進行Set操作
    if v.Kind() == reflect.Ptr && ! v.Elem().CanSet() {
        fmt.Println("xxx")
        return
        //初始化對象修改後的返回值(可接受v或v的指針)
    } else {
        v = v.Elem()
    }
    //按照結構體對象的名稱進行查找filed,並判斷類型是否為string,然後進行Set
    if f := v.FieldByName("Name"); f.Kind() == reflect.String {
        f.SetString("BYBY")
    }
}

輸出:

666
Change the test to u with Set(x Value) &{2 bgops 2}
{2 BYBY 2}

通過反射進行動態方法的調用

使用反射的相關知識進行方法的動態調用

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id   int
    Name string
    Age  int
}

func (u User) Hello(name string, id int) {
    fmt.Printf("Hello %s,my name is %s and my id is %d\n", name, u.Name, id)
}

func main() {
    u := User{1, "biaoge", 24}
    fmt.Println("方法調用:")
    u.Hello("xuxuebiao", 121)

    //獲取結構體類型u的Value
    v := reflect.ValueOf(u)
    //根據方法名稱獲取Value中的方法對象
    mv := v.MethodByName("Hello")

    //構造一個[]Value類型的變數,使用Value的Call(in []Value)方法進行動態調用method
    //這裡其實相當於有一個Value類型的Slice,僅一個欄位
    args := []reflect.Value{reflect.ValueOf("xuxuebiao"), reflect.ValueOf(5256)}
    fmt.Println("通過反射動態調用方法:")
    //使用Value的Call(in []Value)方法進行方法的動態調用
    //func (v Value) Call(in []Value) []Value
    //需要註意的是當v的類型不是Func的化,將會panic;同時每個輸入的參數args都必須對應到Hello()方法中的每一個形參上
    mv.Call(args)

}
方法調用:
Hello xuxuebiao,my name is biaoge and my id is 121
通過反射動態調用方法:
Hello xuxuebiao,my name is biaoge and my id is 5256

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 包導入格式 導入模塊時除了使用模塊名進行導入,還可以使用目錄名進行導入。例如,在sys.path路徑下,有一個dir1/dir2/mod.py模塊,那麼在任意位置處都可以使用下麵這種方式導入這個模塊。 一個實際一點的示例,設置PYTHONPATH環境變數為 ,然後在此目錄下創建以上目錄和mod.py ...
  • 在講述fileinput模塊之前,首先說一下python內置的文件API—open()函數以及與其相關的函數。 我這裡主要講講其中四個比較重要和常用的方法,更多的方法,可以參考:菜鳥教程http://www.runoob.com/python/file-methods.html (1)file = ...
  • Python基礎知識(3):基本數據類型之數字 一、基本數據類型 數字Number、字元串String、列表List、元組Tuple、集合Set、字典Dictionary 二、數字 Python3支持int、float、bool、complex,其中只有一種整數類型int。 (1)內置函數type( ...
  • 前言:前面寫了後天管理系統工程搭建以及框架的整合測試,今天寫一下商品列表的分頁查詢 1 需求分析 前臺使用easyui的分頁工具,後臺則使用mybatis分頁插件pagehelper 如上圖所示,打開後臺首頁,點擊查詢商品,按下F12,可以看到easyui的分頁界面會向controller發送兩個數 ...
  • 1、安裝JDK1.7及以上 2、下載解壓sdk並且配置環境變數: ANDROID_HOME:...\adt-bundle-windows-x86_64-20140702\sdk PATH:%ANDROID_HOME%\platform-tools;%ANDROID_HOME%\tools; dos檢 ...
  • 在做項目的時候,發現後臺把Date類型的屬性以json字元串的形式返回,前臺拿不到轉換後的日期格式,始終響應回去的都是long類型時間戳。 查閱資料之後找到解決方法(在springmvc的xml配置文件下): 修改之後運行結果: 還有就是前端提交日期的json,格式為2018-07-26,日期欄位希 ...
  • 1 from collections import Counter 2 3 s = "狗咬我一口,難道我還要去咬狗?" 4 # dic = {} 5 # for el in s: 6 # dic[el] = dic.setdefault(el,0) + 1 7 # print(dic) 8 9 c ...
  • ·字典(dict) 筆記: 字典(映射)成對出現,由鍵及其相應的值組成,鍵-值對稱作項(item),字典是python中唯一內置映射類型。字典中的鍵必須是獨一無二的。 在python 2中進行拷貝需要調用copy模塊;而在python 3 中可以直接使用淺拷貝copy(),當使用深拷貝deepcop ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...