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
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...