網站首頁 編程語言 正文
介紹
reflect包實現運行時反射,允許一個程序操作任何類型的對象。典型的使用是:取靜態類型interface{}
的值,通過調用TypeOf
獲取它的動態類型信息,調用ValueOf
會返回一個表示運行時數據的一個值。本文通過記錄對reflect包的簡單使用,來對反射有一定的了解。本文使用的Go版本:
$ go version go version go1.18 darwin/amd64
在了解反射之前,先了解以下幾個概念:
- 靜態類型:每個變量都有一個靜態類型,這個類型是在編譯時(compile time)就已知且固定的。
-
動態類型:接口類型的變量還有一個動態類型,是在運行時(run time)分配給變量的值的一個非接口類型。(除非分配給變量的值是
nil
,因為nil
沒有類型)。 -
空接口類型
interface{}
(別名any
)表示空的方法集,它可以是任何值的類型,因為任何值都滿足有0或多個方法(有0個方法一定是任何值的子集)。 - 一個接口類型的變量存儲一對內容:分配給變量的具體的值,以及該值的類型描述符??梢允疽庑缘乇硎緸?code>(value, type)對,這里的
type
是具體的類型,而不是接口類型。
反射的規律
1. 從接口值到反射對象的反射
在基本層面,反射只是檢測存儲在接口變量中的(value, type)
對的一種機制。
可以使用reflect包的?reflect.ValueOf
和reflect.TypeOf
方法,獲取接口變量值中的(value, type)
對,類型分別為reflect.Value
和reflect.Type
。
(1)TypeOf方法:
func TypeOf(i any) Type
TypeOf
返回表示i
的動態類型的反射Type
。如果i
是nil
,那么返回nil
。
(2)ValueOf方法:
func ValueOf(i any) Value
ValueOf
返回一個新的Value
,初始化為存儲在接口i
中的具體值。ValueOf(nil)
會返回零Value
。這個零Value
是反射對象中表示沒有值的Value
。
var a interface{} = 1 var b interface{} = 1.11 var c string = "aaa" // 將接口類型的變量運行時存儲的具體的值和類型顯示地獲取到 fmt.Println("type:", reflect.TypeOf(a)) // type: int fmt.Println("value:", reflect.ValueOf(a)) // value: 1 fmt.Println("type:", reflect.TypeOf(nil)) // type: <nil> fmt.Println("value:", reflect.ValueOf(nil)) // value: <invalid reflect.Value> fmt.Println("type:", reflect.TypeOf(b)) // type: float64 fmt.Println("value:", reflect.ValueOf(b)) // value: 1.11 fmt.Println("type:", reflect.TypeOf(c)) // type: string fmt.Println("value:", reflect.ValueOf(c)) // value: aaa
reflect.Value
的?Type
方法,返回一個?reflect.Value
的類型。
reflect.Value
的String
方法,將reflect.Value
的底層值作為字符串返回。
fmt.Printf
使用了反射:
-
%T
使用reflect.TypeOf
,拿到變量的動態類型。 -
%v
深入到?reflect.Value
內部拿到變量具體的值。
var a interface{} = 1 fmt.Println("type:", reflect.ValueOf(a).Type()) // type: int fmt.Println("string:", reflect.ValueOf(a).String()) // string: <int Value> fmt.Printf("type: %T \n", a) // type: int fmt.Printf("string: %v \n", a) // string: 1
Type
?和?Value
都有一個?Kind
方法,返回一個表示存儲的項的類型的常量。?比如Uint
,?Float64
,?Slice
等。
Value
的類似Int
和Float
這種名稱的方法能夠獲取存儲在內部的值。
Value
的“getter”(取值) 和 “setter”(設置值)會對能保存該值的最大類型進行操作。比如對于所有有符號整數,都是int64。
var a interface{} = 1 var b interface{} = 1.11 reflectA := reflect.ValueOf(a) fmt.Println("kind: ", reflectA.Kind()) // kind: int reflectIntA := reflectA.Int() // 返回的是 能存儲有符號整數的最大類型 的值 reflectFloatB := reflect.ValueOf(b).Float() // 返回的是 能存儲浮點數的最大類型 的值 var a1 int64 = reflectIntA var b1 float64 = reflectFloatB // var a2 int32 = reflectIntA // 會報錯:cannot use reflectIntA (variable of type int64) as int32 value in variable // var b2 float32 = reflectFloatB // 會報錯:cannot use reflectFloatB (variable of type float64) as float32 value in variable fmt.Println("a1: ", a1, "b1: ", b1) // a1: 1 b1: 1.11
2. 從反射對象到接口值的反射
像物理反射一樣,Go 中的反射產生了它自己的逆。可以使用reflect.Value
的Interface
方法還原一個接口值。Interface()
將類型和值信息打包回一個接口表示。
Interface
方法:
func (v Value) Interface() (i any)
Interface
將v
的當前值作為一個interface{}
返回。它等同于:
var i interface{} = (v 的底層值)
練習代碼:
var a interface{} = 1 reflectA := reflect.ValueOf(a) var a3 interface{} = reflectA.Interface() var a4 int = reflectA.Interface().(int) // 使用接口值的類型斷言 fmt.Println(a3, a4) // 1 1 ,因為fmt.Println接收接口類型interface{}的參數,使用 reflect.Value 拿到具體的值,所以打印出運行時的具體結果
3. 要修改反射對象,該值一定是可設置的
可設置性是reflect.Value
的一個屬性,表示一個反射對象可以修改用于創建該反射對象的實際存儲??稍O置性可以通過CanSet
方法獲得。如果對不可設置的reflect.Value
調用Set
方法,就會報錯。
使用reflect.Value
類型的Elem
方法能通過指針間接尋址得到一個可設置性為真的reflect.Value
。
var d float64 = 2.222 fmt.Println(reflect.ValueOf(d).CanSet()) // false reflectD := reflect.ValueOf(&d).Elem() fmt.Println(reflectD.CanSet()) // true reflectD.SetFloat(3.33) fmt.Println(d, reflectD) // 3.33 3.33
reflect.ValueOf(d)
是通過復制d
中的內容得到的reflect.Value
類型的值,它復制的內容存放的內存地址 和d
的值存放的內存地址是不同的。所以不能通過它來修改d
中原本存儲的內容。
上面的代碼中可以看到,調用SetFloat(3.33)
之后,反射對象reflectD
和創建該反射對象的d
都發生了改變。
使用反射修改結構體的字段:
type T struct { A int B string } t := T{111, "xxx"} s := reflect.ValueOf(&t).Elem() typeOfT := s.Type() for i := 0; i < s.NumField(); i++ { // NumField 返回結構體中的字段數量 f := s.Field(i) fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, // 獲取第i個字段的名稱 f.Type(), // 獲取第i個字段的類型 f.Interface(), // 將第i個字段轉換回接口類型的值 ) // 0: A int = 111 // 1: B string = xxx } s.Field(0).SetInt(222) // 設置結構體的第一個字段的值 s.Field(1).SetString("yyy") // 設置結構體的第二個字段的值 fmt.Println(t) // {222 yyy}
上述練習代碼都在一個reflect.go文件中,練習時在終端執行go run reflect.go
運行該文件。
原文鏈接:https://juejin.cn/post/7097534989029343246
相關推薦
- 2022-05-13 (Qt)使用QCommandLineParser進行程序的命令行解析
- 2023-01-26 Redis中的慢日志_Redis
- 2023-04-02 go?MethodByName()不能獲取私有方法的解決_Golang
- 2023-03-22 nginx搭建高可用集群的實現方法_nginx
- 2022-08-26 Python?Decorator裝飾器的創建方法及常用場景分析_python
- 2022-05-23 vmware增加新硬盤無需重啟即可生效的命令腳本_VMware
- 2023-03-02 Python實現設置顯示屏分辨率_python
- 2022-05-18 C語言?動態內存開辟常見問題解決與分析流程_C 語言
- 最近更新
-
- 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同步修改后的遠程分支