網(wǎng)站首頁 編程語言 正文
反射是語言里面是非常重要的一個特性,我們經(jīng)常會看見這個詞,但是對于反射沒有一個很好的理解,主要是因為對于反射的使用場景不太熟悉。
一、理解變量的內(nèi)在機制
1.類型信息,元信息,是預先定義好的,靜態(tài)的。
2.值信息,程序進行過程中,動態(tài)變化的。
二、反射和空接口
1.空接口相當于一個容器,能接受任何東西。
2.那怎么判斷空接口變量存儲的是什么類型呢?之前有使用過類型斷言,這只是一個比較基礎(chǔ)的方法
3.如果想獲取存儲變量的類型信息和值信息就要使用反射機制,所以反射是什么? 反射就是動態(tài)的獲取變量類型信息和值信息的機制。
三、怎么利用反射分析空接口里面的信息呢?
①首先利用的是GO語言里面的Reflect包
②利用包里的TypeOf方法可以獲取變量的類型信息
func reflect_typeof(a interface{}) { t := reflect.TypeOf(a) fmt.Printf("type of a is:%v\n", t) k := t.Kind() switch k { case reflect.Int64: fmt.Printf("a is int64\n") case reflect.String: fmt.Printf("a is string\n") } }
利用Kind() 可以獲取t的類型,如代碼所示,這里可以判斷a是Int64還是string, 像下面一樣使用:
func main() { var x int64 = 3 reflect_example(x) var y string = "hello" reflect_example(y) }
打印結(jié)果:
type of a is:int64
a is int64
type of a is:string
a is string
③利用包里的ValueOf方法可以獲取變量的值信息
func reflect_value(a interface{}) { v := reflect.ValueOf(a) k := v.Kind() switch k { case reflect.Int64: fmt.Printf("a is Int64, store value is:%d\n", v.Int()) case reflect.String: fmt.Printf("a is String, store value is:%s\n", v.String()) } }
利用ValueOf方法可以得到變量的值信息,ValueOf返回的是一個Value結(jié)構(gòu)體類型,有趣的是 可以使用 v.Type() 獲取該變量的類型,和上面reflect.TypeOf() 獲取的結(jié)果一樣。
此外,因為值信息是動態(tài)的,所以我們不僅僅可以獲取這個變量的類型,還能取得這個變量里面存儲的值,利用 v.Int() 、 v.String() 等等就能取得值。如上面的main,調(diào)用此函數(shù)返回的結(jié)果:
a is Int64, store value is:3
a is String, store value is:hello
這里存在一個問題,如果傳進去一個類型,使用了錯誤的解析,那么將會在運行的時候報錯, 例如將 一個string類型強行的v.Int()。
既然值類型是動態(tài)的,能取到保存的值,同樣可以設(shè)置值。在反射里面有很多set的方法,例如SetFloat、SetInt()、SetString()等可以幫助我們設(shè)置值。
下面的例子,我想把 x設(shè)置為 6.28,但是會報錯!
func main() { var x float64 = 3.14 v := reflect.ValueOf(x) v.SetFloat(6.28) fmt.Printf("After Set Value is %f", x) }
錯誤結(jié)果:
panic: reflect: reflect.Value.SetFloat using unaddressable value
......
結(jié)果上說明是不可設(shè)置的,為什么呢? 因為我們的x是一個值類型,而值類型的傳遞是拷貝了一個副本,當 v := reflect.ValueOf(x) 函數(shù)通過傳遞一個 x 拷貝創(chuàng)建了 v,那么 v 的改變并不能更改原始的 x。要想 v 的更改能作用到 x,那就必須傳遞 x 的地址 v = reflect.ValueOf(&x)。修改程序如下:
func main() { var x float64 = 3.14 v := reflect.ValueOf(&x) v.SetFloat(6.28) fmt.Printf("After Set Value is %f", x) }
結(jié)果:依然報錯!為什么傳了地址還報錯?因為&x是地址了,所以它的類型就變了,可以通過v.Type(),看下改變成了什么:
func main() { var x float64 = 3.14 v := reflect.ValueOf(&x) fmt.Printf("type of v is %v", v.Type()) //打印的結(jié)果是:type of v is *float64 }
由程序可以知道,這個返回的是一個指針類型的。所以上面SetFloat才會失敗,那怎么做?
我們正常的賦值,如果是地址的話,例如下面:一般我們都會對*y進行賦值, *的意思就是往這個地址里面賦值。
var y *float64 = new(float64) *y = 10.12 fmt.Printf("y = %v", *y)
同樣的,我們在反射里面也可以取地址,需要通過 Elem() 方法進行取地址。再次修改程序
func main() { var x float64 = 3.14 v := reflect.ValueOf(&x) fmt.Printf("type of v is %v\n", v.Type()) v.Elem().SetFloat(6.28) fmt.Printf("After set x is %v", x) }
結(jié)果為:
type of v is *float64
After set x is 6.28
四、利用反射獲取結(jié)構(gòu)體里面的方法和調(diào)用。
1.獲取結(jié)構(gòu)體的字段
我們可以通過上面的方法判斷一個變量是不是結(jié)構(gòu)體。
可以通過 NumField() 獲取所有結(jié)構(gòu)體字段的數(shù)目、進而遍歷,通過Field()方法獲取字段的信息。
type Student struct { Name string Sex int Age int Score float32 } func main() { //創(chuàng)建一個結(jié)構(gòu)體變量 var s Student = Student{ Name: "BigOrange", Sex: 1, Age: 10, Score: 80.1, } v := reflect.ValueOf(s) t := v.Type() kind := t.Kind() //分析s變量的類型,如果是結(jié)構(gòu)體類型,那么遍歷所有的字段 switch kind { case reflect.Int64: fmt.Printf("s is int64\n") case reflect.Float32: fmt.Printf("s is int64\n") case reflect.Struct: fmt.Printf("s is struct\n") fmt.Printf("field num of s is %d\n", v.NumField()) //NumFiled()獲取字段數(shù),v.Field(i)可以取得下標位置的字段信息,返回的是一個Value類型的值 for i := 0; i < v.NumField(); i++ { field := v.Field(i) //打印字段的名稱、類型以及值 fmt.Printf("name:%s type:%v value:%v\n", t.Field(i).Name, field.Type().Kind(), field.Interface()) } default: fmt.Printf("default\n") } }
執(zhí)行結(jié)果:
s is struct
field num of s is 4
name:Name type:string value:BigOrange
name:Sex type:int value:1
name:Age type:int value:10
name:Score type:float32 value:80.1
這里需要說明幾個問題:
①打印字段名稱的時候,使用的是t.Field(i).Name ,Name是靜態(tài)的,所以屬于類型的信息
②打印值的時候,這里將field.Interface()實際上相當于ValueOf的反操作(可以參考這篇文章https://www.jb51.net/article/255856.htm),所以才能把值打印出來
③此外如果Student中的Name字段變?yōu)閚ame(私有),那么則會報錯,不能反射出私有變量 錯誤信息 “panic: reflect.Value.Interface: cannot return value obtained from unexported field or method”
2.對結(jié)構(gòu)體內(nèi)的字段進行賦值操作
參考下面的代碼,對上面的Student進行賦值操作:
func main() { s := Student{ Name: "BigOrange", Sex: 1, Age: 10, Score: 80.1, } fmt.Printf("Name:%v, Sex:%v,Age:%v,Score:%v \n", s.Name, s.Sex, s.Age, s.Score) v := reflect.ValueOf(&s) //這里傳的是地址!??! v.Elem().Field(0).SetString("ChangeName") v.Elem().FieldByName("Score").SetFloat(99.9) fmt.Printf("Name:%v, Sex:%v,Age:%v,Score:%v \n", s.Name, s.Sex, s.Age, s.Score) }
結(jié)果:
Name:BigOrange, Sex:1,Age:10,Score:80.1
Name:ChangeName, Sex:1,Age:10,Score:99.9
3.獲取結(jié)構(gòu)體里面的方法
可以通過NumMethod()獲得接頭體里面的方法數(shù)量,然后遍歷通過Method()獲取方法的具體信息。如下代碼所示:
//新增-設(shè)置名稱方法 func (s *Student) SetName(name string) { fmt.Printf("有參數(shù)方法 通過反射進行調(diào)用:%v\n", s) s.Name = name } //新增-打印信息方法 func (s *Student) PrintStudent() { fmt.Printf("無參數(shù)方法 通過反射進行調(diào)用:%v\n", s) } func main() { s := Student{ Name: "BigOrange", Sex: 1, Age: 10, Score: 80.1, } v := reflect.ValueOf(&s) //取得Type信息 t := v.Type() fmt.Printf("struct student have %d methods\n", t.NumMethod()) for i := 0; i < t.NumMethod(); i++ { method := t.Method(i) fmt.Printf("struct %d method, name:%s type:%v\n", i, method.Name, method.Type) } }
輸出:
struct student have 2 methods
struct 0 method, name:PrintStudent type:func(*main.Student)
struct 1 method, name:SetName type:func(*main.Student, string)
從結(jié)果中看到我們可以獲取方法的名稱以及簽名信息,并且這個方法的輸出順序是按照字母排列的。
并且輸出結(jié)果可以看到一個有趣的現(xiàn)象:結(jié)構(gòu)體的方法其實也是通過函數(shù)實現(xiàn)的例如 func(s Student) SetName(name string) 這個方法,反射之后的結(jié)果就是 func(main.Student , string) 實際上把Student當參數(shù)了。
此外還可以通過反射來調(diào)用這些方法。想要通過反射調(diào)用結(jié)構(gòu)體里面的方法,首先要知道方法調(diào)用時一個動態(tài)的,所以要先通過ValueOf獲取值,然后通過獲取的值進行方法的調(diào)用 ,通過 value里面的Method方法 返回一個方法,然后通過Call方法調(diào)用,Call是參數(shù)是一個切片,也就是參數(shù)的列表。以下是Call方法的定義:可以看到參數(shù)是一個Value的數(shù)組:
如下代碼展示了如何調(diào)用有參數(shù)的方法和無參數(shù)的方法:
func main() { s := Student{ Name: "BigOrange", Sex: 1, Age: 10, Score: 80.1, } v := reflect.ValueOf(&s) //通過reflect.Value獲取對應(yīng)的方法并調(diào)用 m1 := v.MethodByName("PrintStudent") var args []reflect.Value m1.Call(args) m2 := v.MethodByName("SetName") var args2 []reflect.Value name := "stu01" nameVal := reflect.ValueOf(name) args2 = append(args2, nameVal) m2.Call(args2) m1.Call(args) }
執(zhí)行結(jié)果:
無參數(shù)方法 通過反射進行調(diào)用:&main.Student{Name:"BigOrange", Sex:1, Age:10, Score:80.1}
有參數(shù)方法 通過反射進行調(diào)用:&main.Student{Name:"BigOrange", Sex:1, Age:10, Score:80.1}
無參數(shù)方法 通過反射進行調(diào)用:&main.Student{Name:"stu01", Sex:1, Age:10, Score:80.1}
上面格式打?。?/p>
- %v 相應(yīng)值的默認格式。 Printf("%v", people) {zhangsan},
- %+v 打印結(jié)構(gòu)體時,會添加字段名 Printf("%+v", people) {Name:zhangsan}
- %#v 相應(yīng)值的Go語法表示 Printf("#v", people) main.Human{Name:"zhangsan"}
五、怎么獲取結(jié)構(gòu)體里tag的信息。
有時候我們在類型上面定義一些tag,例如使用json和數(shù)據(jù)庫的時候。Field()方法返回的StructField結(jié)構(gòu)體中保存著Tag信息,并且Tag信息可以通過一個Get(Key)的方法獲取出來,如下代碼所示:
type Student struct { Name string `json:"jsName" db:"dbName"` } func main() { s := Student{ Name: "BigOrange", } v := reflect.ValueOf(&s) t := v.Type() field0 := t.Elem().Field(0) fmt.Printf("tag json=%s\n", field0.Tag.Get("json")) fmt.Printf("tag db=%s\n", field0.Tag.Get("db")) }
結(jié)果:
tag json=jsName
tag db=dbName
六、應(yīng)用場景
1.序列化和反序列化,比如json, protobuf等各種數(shù)據(jù)協(xié)議
2.各種數(shù)據(jù)庫的ORM,比如gorm,sqlx等數(shù)據(jù)庫中間件
3.配置文件解析相關(guān)的庫,比如yaml、ini等
原文鏈接:https://www.cnblogs.com/dcz2015/p/11199060.html
相關(guān)推薦
- 2022-01-20 淺談關(guān)于 && , || , ? : , ?? , ?. 的運算方式以及用法
- 2022-10-18 Python中尋找數(shù)據(jù)異常值的3種方法_python
- 2023-12-07 com.mongodb.MongoSocketOpenException: Exception op
- 2022-04-17 python中random隨機函數(shù)詳解_python
- 2022-11-17 使用Python中Tkinter模塊的Treeview?組件顯示ini文件操作_python
- 2023-02-09 C++?類模板與成員函數(shù)模板示例解析_C 語言
- 2022-09-24 ASP.NET?MVC把表格導出到Excel_實用技巧
- 2022-05-09 關(guān)于Ajax的疑難雜癥詳解_AJAX相關(guān)
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支