網站首頁 編程語言 正文
反射
Go語言提供了reflect 包來訪問程序的反射信息;定義了兩個重要的類型Type和Value:
- reflect.TypeOf:獲取任意值的類型對象(reflect.Type);
- reflect.ValueOf:獲得值的反射值對象(reflect.Value);
反射類型Type
Go語言程序中的類型(Type)指的是系統原生數據類型(如 int、string、bool、float32 等),以及使用 type 關鍵字定義的類型;而反射種類(Kind)是指對象的歸屬分類:
type Kind uint const ( Invalid Kind = iota // 非法類型 Bool // 布爾型 Int // 有符號整型 Int8 // 有符號8位整型 Int16 // 有符號16位整型 Int32 // 有符號32位整型 Int64 // 有符號64位整型 Uint // 無符號整型 Uint8 // 無符號8位整型 Uint16 // 無符號16位整型 Uint32 // 無符號32位整型 Uint64 // 無符號64位整型 Uintptr // 指針 Float32 // 單精度浮點數 Float64 // 雙精度浮點數 Complex64 // 64位復數類型 Complex128 // 128位復數類型 Array // 數組 Chan // 通道 Func // 函數 Interface // 接口 Map // 映射 Ptr // 指針 Slice // 切片 String // 字符串 Struct // 結構體 UnsafePointer // 底層指針 )
指針
對指針指向的對象,可通過reflect.Elem() 方法獲取這個指針指向的元素類型(等效于對指針類型變量做了一個*
操作)。
func reflectStruct() { type Cat struct { } aCat := &Cat{} // 獲取結構體實例的反射類型對象 typeOfCat := reflect.TypeOf(aCat) fmt.Printf("ptr name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind()) // 取類型的元素 typeOfCat = typeOfCat.Elem() fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind()) typeOfCat = reflect.TypeOf(*aCat) fmt.Printf("*ptr name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind()) } // ptr name:'' kind:'ptr' // element name: 'Cat', element kind: 'struct' // *ptr name:'Cat' kind:'struct'
結構體
對結構體對象,獲取對象信息后,可通過NumField() 和 Field() 方法獲得結構體成員的詳細信息。
方法 | 說明 |
---|---|
Field(i int) StructField | 根據索引返回索引對應字段的信息 |
NumField() int | 返回結構體成員字段數量 |
FieldByName(name string) (StructField, bool) | 根據給定字符串返回字符串對應的結構體字段的信息,沒有找到時 bool 返回 false |
FieldByIndex(index []int) StructField | 多層成員訪問時,根據 []int 提供的每個結構體的字段索引,返回字段的信息,沒有找到時返回零值 |
FieldByNameFunc(match func(string) bool) (StructField,bool) | 根據匹配函數匹配需要的字段 |
字段信息中含有:
type StructField struct { Name string // 字段名 PkgPath string // 字段在結構體中的路徑 Type Type // 字段的反射類型 reflect.Type Tag StructTag // 字段的結構體標簽 Offset uintptr // 字段在結構體中的相對偏移 Index []int // FieldByIndex中的索引順序 Anonymous bool // 是否為匿名字段 }
獲取字段的名稱與tag:
func reflectStructField() { // 聲明一個空結構體 type Cat struct { Name string // 帶有結構體tag的字段 Type int `json:"type" id:"100"` } aCat := Cat{Name: "mimi", Type: 1} // 獲取結構體實例的反射類型對象 typeOfCat := reflect.TypeOf(aCat) // 遍歷結構體所有成員 for i := 0; i < typeOfCat.NumField(); i++ { fieldType := typeOfCat.Field(i) fmt.Printf("Field-%v: name: %v tag: '%v'\n", i, fieldType.Name, fieldType.Tag) } // 通過字段名, 找到字段類型信息 if catType, ok := typeOfCat.FieldByName("Type"); ok { fmt.Println("Field Tag: ", catType.Tag.Get("json"), catType.Tag.Get("id")) } } // Field-0: name: Name tag: '' // Field-1: name: Type tag: 'json:"type" id:"100"' // Field Tag: type 100
反射值Value
通過下面幾種方法從反射值對象 reflect.Value 中獲取原值
方法名 | 說 明 |
---|---|
Interface() interface {} | 將值以 interface{} 類型返回,可以通過類型斷言轉換為指定類型 |
Int() int64 | 將值以 int 類型返回,所有有符號整型均可以此方式返回 |
Uint() uint64 | 將值以 uint 類型返回,所有無符號整型均可以此方式返回 |
Float() float64 | 將值以雙精度(float64)類型返回,所有浮點數(float32、float64)均可以此方式返回 |
Bool() bool | 將值以 bool 類型返回 |
Bytes() []bytes | 將值以字節數組 []bytes 類型返回 |
String() string | 將值以字符串類型返回 |
通過反射獲取變量的值:
func reflectValue() { var a int = 1024 // 獲取變量a的反射值對象 valueOfA := reflect.ValueOf(a) // 獲取interface{}類型的值, 通過類型斷言轉換 var getA int = valueOfA.Interface().(int) // 獲取64位的值, 強制類型轉換為int類型 var getA2 int = int(valueOfA.Int()) fmt.Println(getA, getA2) } // 1024 1024
結構體
反射值對象(reflect.Value)提供對結構體訪問的方法,通過這些方法可以完成對結構體任意值的訪問:
方 法 | 備 注 |
---|---|
Field(i int) Value | 根據索引,返回索引對應的結構體成員字段的反射值對象 |
NumField() int | 返回結構體成員字段數量 |
FieldByName(name string) Value | 根據給定字符串返回字符串對應的結構體字段,沒有找到時返回零值 |
FieldByIndex(index []int) Value | 多層成員訪問時,根據 []int 提供的每個結構體的字段索引,返回字段的值; 沒有找到時返回零值 |
FieldByNameFunc(match func(string) bool) Value | 根據匹配函數匹配需要的字段,沒有找到時返回零值 |
空與有效性判斷
反射值對象(reflect.Value)提供一系列方法進行零值和空判定:
方 法 | 說 明 |
---|---|
IsNil() bool | 是否為 nil,只對通道、函數、接口、map、指針或切片有效(否則會panic) |
IsValid() bool | 是否有效,當值本身非法時(不包含任何值,或值為 nil),返回 false |
修改值
通過反射修改變量值的前提條件之一:這個值必須可以被尋址,簡單地說就是這個變量必須能被修改。結構體成員中,如果字段沒有被導出,即便也可以被訪問,也不能通過反射修改。
方法名 | 備 注 |
---|---|
Elem() Value | 取值指向的元素值(類似于*操作);對指針或接口時發生panic |
Addr() Value | 對可尋址的值返回其地址(類似于&操作);當值不可尋址時發生panic |
CanAddr() bool | 表示值是否可尋址 |
CanSet() bool | 返回值能否被修改;要求值可尋址且是導出的字段 |
修改結構體字段的值(需要結構體地址,與導出字段):
func reflectModifyValue() { type Dog struct { LegCount int } // 獲取dog實例地址的反射值對象 valueOfDog := reflect.ValueOf(&Dog{}) // 取出dog實例地址的元素 valueOfDog = valueOfDog.Elem() vLegCount := valueOfDog.FieldByName("LegCount") vLegCount.SetInt(4) fmt.Println(vLegCount.Int()) }
函數調用
如果反射值對象(reflect.Value)為函數,可以通過Call()調用:參數使用反射值對象的切片[]reflect.Value構造后傳入,返回值通過[]reflect.Value返回。
func add(a, b int) int { return a + b } func reflectFunction() { // 將函數包裝為反射值對象 funcValue := reflect.ValueOf(add) // 構造函數參數, 傳入兩個整型值 paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)} // 反射調用函數 retList := funcValue.Call(paramList) // 獲取第一個返回值, 取整數值 fmt.Println(retList[0].Int()) }
反射三定律
官方提供了三條定律來說明反射:
- 反射可將interface類型變量轉換成反射對象;
- 反射可將反射對象還原成interface對象;
- 要修改反射對象,其值必須是可寫的(反射其指針類型);
var x float64 = 3.4 v := reflect.ValueOf(x) // v is reflext.Value var y float64 = v.Interface().(float64) fmt.Println("value:", y) // 3.4
值類型不能直接修改,可通過傳遞地址并通過Elem獲取后修改:
var x float64 = 3.4 v := reflect.ValueOf(&x) v.Elem().SetFloat(7.8) fmt.Println("x :", v.Elem().Interface()) // 7.8
interface
interface是Go實現抽象的一個非常強大的工具;當向接口賦值時,接口會存儲實體的類型信息;反射就是通過接口的類型信息實現的。
interface類型是一種特殊類型,代表方法集合;可存放任何實現了其方法的值(實際存放的是(value,type)
對)。reflect包中實現了反射的各種函數:
- 提取interface的value的方法
reflect.ValueOf()->reflect.Value
; - 提取interface的type的方法
reflect.TypeOf()->reflect.Type
;
空interface類型(interface{}
)的方法集為空,所以可認為任何類型都實現了該接口;因此其可存放任何值。
底層結構
interface底層結構分為iface和eface描述接口,其區別是eface為不包含任何方法的空接口。
iface
iface定義如下:
-
tab
指向一個itab
實體的指針:表示接口的類型(賦給此接口的實體類型); -
data
指向接口具體的值:一般而言是一個指向堆內存的指針。
type iface struct { tab *itab data unsafe.Pointer }
itab結構:
-
_type
字段描述了實體的類型:包括內存對齊方式,大小等; -
inter
字段則描述了接口的類型; -
fun
字段放置是實體類中和接口方法對應(實體中其他方法不在此處)的方法地址,以實現接口調用方法的動態分派;一般在每次給接口賦值發生轉換時會更新此表。
type itab struct { inter *interfacetype _type *_type link *itab hash uint32 bad bool inhash bool unused [2]byte fun [1]uintptr }
iface結構全貌圖:
eface
eface結構:只維護了一個 _type
字段,表示空接口所承載的具體的實體類型。
type eface struct { _type *_type data unsafe.Pointer }
原文鏈接:https://blog.csdn.net/alwaysrun/article/details/123024769
相關推薦
- 2022-05-06 python畫圖時給圖中的點加標簽和plt.text的使用_python
- 2022-11-04 C++多態特性之派生與虛函數與模板詳細介紹_C 語言
- 2022-10-09 玩轉Go命令行工具Cobra_Golang
- 2022-05-09 Docker?Overlay2磁盤空間占用過大清理的方法實現_docker
- 2022-06-15 C++詳細講解繼承與虛繼承實現_C 語言
- 2022-11-21 深入了解Golang官方container/heap用法_Golang
- 2023-02-06 python?wordcloud庫實例講解使用方法_python
- 2022-11-14 Python實現腳本轉換為命令行程序_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支