Go語(yǔ)言反射使用及優(yōu)缺點(diǎn)總結(jié)
一:什么是反射
反射可以認(rèn)為是程序在運(yùn)行時(shí)的一種能力,反射可以在程序運(yùn)行時(shí)訪問(wèn)、檢測(cè)和修改它本身狀態(tài),比如在程序運(yùn)行時(shí)可以檢查變量的類型和值,調(diào)用它們的方法,甚至修改它們的值。使用反射可以增加程序的靈活性,簡(jiǎn)單來(lái)說(shuō),反射就是程序在運(yùn)行時(shí)能夠檢測(cè)自身和修改自身的一種能力。
二:Go 語(yǔ)言反射
對(duì)于很多的高級(jí)語(yǔ)言都實(shí)現(xiàn)了反射,像 Java,Python。在 Go 語(yǔ)言中,反射在 Go 語(yǔ)言內(nèi)置的 reflect 包下實(shí)現(xiàn)。Go 語(yǔ)言中的反射建立在 Go 的類型系統(tǒng)之上,并且與接口密切相關(guān)。通過(guò)前面的學(xué)習(xí)我們知道 Go 語(yǔ)言的空接口包含類型 (Type) 和值 (Value) 兩個(gè)部分,在反射里,也要用到類型 (Type) 和值 (Value)。
reflect 包中定義了 reflect.Type 和 reflect.Value,正好對(duì)應(yīng)我們前面所說(shuō)的Type 和 Value。要注意的是 reflect.Type 是一個(gè)接口而 reflect.Value 是一個(gè)具體的結(jié)構(gòu)體。在 reflect.Type 接口中定義了很多跟類型相關(guān)的方法,而reflect.Value 則是綁定了很多跟值相關(guān)的方法。
reflect.TypeOf()
由于 reflect.Type 是一個(gè)接口,所以只有當(dāng)某個(gè)類型實(shí)現(xiàn)了這個(gè)接口,我們才能獲取到它的類型,同時(shí),在 reflect 包內(nèi),類型描述符是未導(dǎo)出類型,所以我們只能通過(guò) reflect.TypeOf() 方法獲取 reflect.Type 類型的值。
我們首先看一個(gè)例子,看下 reflect.TypeOf() 的常用用法:
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
}
func main() {
var num int64 = 100
t1 := reflect.TypeOf(num)
fmt.Println(t1.String())
st := Student{
Name: "zhangsan",
Age: 18,
}
t2 := reflect.TypeOf(st)
fmt.Println(t2.String())
}
運(yùn)行結(jié)果:
int64
main.Student
可以看到對(duì)于基礎(chǔ)類型和 struct 類型通過(guò)調(diào)用 reflect.TypeOf() 都打印出了對(duì)應(yīng)的類型信息。注意 reflect.TypeOf 返回的是一個(gè) reflect.Type 接口類型,我們通過(guò)調(diào)用這個(gè)接口的 String() 方法,得到最終的字符串信息。
在前面學(xué)習(xí) interface 的章節(jié)中,我們知道一個(gè)具體的數(shù)據(jù)類型是可以賦值給一個(gè) interface 類型的,反過(guò)來(lái)則不行,要用到 interface 的斷言。在一個(gè)interface 賦值之后,其實(shí)是對(duì)應(yīng)了兩個(gè)類型,一個(gè)是靜態(tài)類型,就是在程序編譯期就確定的類型,interface 的靜態(tài)類型就是接口 interface,同時(shí)當(dāng) interface 賦值之后,他還有一個(gè)動(dòng)態(tài)類型,就是被賦值的那個(gè)數(shù)據(jù)的具體類型,假設(shè)在上例中,我們將 st 賦值給一個(gè)空 interface,那么這個(gè) interface 的動(dòng)態(tài)類型就是 Student。
對(duì)一個(gè)數(shù)據(jù)對(duì)象進(jìn)行反射操作,其實(shí)是首先將具體對(duì)象類型轉(zhuǎn)化為一個(gè) interface 類型,然后再將 interface 類型轉(zhuǎn)化為 reflect 包下的反射類型,反射類型里的類型信息和值信息其實(shí)就是對(duì)應(yīng)著這個(gè)中間類型 interface 的類型和值。

reflect.TypeOf() 方法獲取的就是這個(gè) interface{} 中的類型部分。
reflect.ValueOf()
同理,reflect.ValueOf() 方法自然就是獲取接口中的值部分,reflect.ValueOf() 的返回值其實(shí)就是一個(gè) reflect.Value 結(jié)構(gòu)體。
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
}
func main() {
var num int64 = 100
v1 := reflect.ValueOf(num)
fmt.Println(v1)
fmt.Println(v1.String())
st := Student{
Name: "zhangsan",
Age: 18,
}
v2 := reflect.ValueOf(st)
fmt.Println(v2)
fmt.Println(v2.String())
}
運(yùn)行結(jié)果:
100
<int64 Value>
{zhangsan 18}
<main.Student Value>
注意到這里 fmt.Println(v1) 和 fmt.Println(v1.String()) 打印的不一樣,上面說(shuō)了 reflect.ValueOf() 的返回值就是一個(gè) reflect.Value 結(jié)構(gòu),但是 fmt.Println(v1) 卻打印出了具體的值,這是因?yàn)?fmt.Println 的參數(shù)是一個(gè)接口類型,在執(zhí)行過(guò)程中有一些類型轉(zhuǎn)換,對(duì) reflect.Value 結(jié)構(gòu)做了特殊處理。
三:反射使用
值對(duì)象
reflect 包下跟值對(duì)象相關(guān)的常用函數(shù)或方法:
| 函數(shù)/方法 | 說(shuō)明 |
|---|---|
| reflect.TypeOf() | 獲取某個(gè)對(duì)象的反射類型實(shí)現(xiàn) (reflect.Type) |
| reflect.ValueOf() | 獲取某個(gè)對(duì)象的反射值對(duì)象(reflect.Value) |
| reflect.Value.NumField() | 獲取結(jié)構(gòu)體的反射值對(duì)象中的字段個(gè)數(shù),只對(duì)結(jié)構(gòu)體類型有效 |
| reflect.Value.Field(i) | 獲取結(jié)構(gòu)體的反射值對(duì)象中的第i個(gè)字段,只對(duì)結(jié)構(gòu)體類型有效 |
| reflect.Kind() | 從反射值對(duì)象中獲取該值的種類 |
| reflect.Value.MapKeys() | 對(duì) map 的每個(gè)鍵的 reflect.Value 對(duì)象組成的一個(gè)切片 |
| reflect.Value.MapIndex(i) | 根據(jù)map的某個(gè)鍵的reflect.Value對(duì)象,返回值的reflect.Value對(duì)象 |
| reflect.Value.Len() | 對(duì)切片或數(shù)組的反射對(duì)象求切片或數(shù)組的長(zhǎng)度 |
| reflect.Value.Index(i) | 返回切片或數(shù)組第i個(gè)元素的reflect.Value值 |
| reflect.Int()/reflect.Uint()/reflect.String()/reflect.Bool() | 從反射的值對(duì)象中取出對(duì)應(yīng)值,注意 reflect.Int()/reflect.Uint()方法對(duì)種類做了合并處理,它們只返回相應(yīng)的最大范圍的類型,Int()返回Int64類型,Uint()返回Uint64類型 |
獲取 struct 反射值
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
Score float64
}
func main() {
st := Student{
Name: "zhangsan",
Age: 18,
Score: 95.5,
}
v := reflect.ValueOf(st)
fmt.Printf("the field num of Student is %d\n", v.NumField())
fmt.Printf("field1 type is %v, value is %s\n", v.Field(0).Type().Name(), v.Field(0).String())
fmt.Printf("field2 type is %v, value is %d\n", v.Field(1).Type().Name(), v.Field(1).Int())
fmt.Printf("field2 type is %v, value is %f\n", v.Field(2).Type().Name(), v.Field(2).Float())
}
運(yùn)行結(jié)果:
the field num of Student is 3
field1 type is string, value is zhangsan
field2 type is int, value is 18
field2 type is float64, value is 95.500000
v := reflect.ValueOf(st),v是一個(gè) Student 類型的反射值對(duì)象,通過(guò) v.NumField() 可以得出 Student 類型的字段個(gè)數(shù),然后 v.Field(i).Type().Name() 打印出各個(gè)字段值的類型,v.Field(i) 打印出各個(gè)字段值
注意:NumField() 和 Field() 方法只有原對(duì)象是結(jié)構(gòu)體時(shí)才能調(diào)用,否則會(huì) panic
類型對(duì)象
| 函數(shù)/方法 | 說(shuō)明 |
|---|---|
| reflect.Type.NumField() | 獲取結(jié)構(gòu)體的反射類型對(duì)象中的字段個(gè)數(shù),只對(duì)結(jié)構(gòu)體類型有效 |
| reflect.Type.Field(i) | 獲取結(jié)構(gòu)體的反射類型對(duì)象中的第i個(gè)字段,只對(duì)結(jié)構(gòu)體類型有效 |
| reflect.Type.Elem() | 根據(jù)指針獲取對(duì)應(yīng)的具體類型 |
| reflect.Type.NumIn() | 獲取函數(shù)反射類型的參數(shù)個(gè)數(shù) |
| reflect.Type.In(i) | 獲取函數(shù)反射類型的第i個(gè)參數(shù) |
| reflect.Type.NumOut() | 獲取函數(shù)反射類型的返回值個(gè)數(shù) |
| reflect.Type.Out(i) | 獲取函數(shù)反射類型的第i個(gè)返回值 |
| reflect.Type.NumMethod() | 獲取struct上綁定的方法個(gè)數(shù) |
| reflect.Type.Method(i) | 獲取struct上綁定的第i個(gè)方法 |
struct 反射類型
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
Score float64
}
func main() {
st := Student{
Name: "zhangsan",
Age: 18,
Score: 90.5,
}
t := reflect.TypeOf(st)
fmt.Println(t.Name())
fmt.Println(t.Kind())
fmt.Println(t.NumField())
for i := 0; i < t.NumField(); i++ {
fmt.Printf("field1 name is %s, field1 type is %s\n", t.Field(i).Name, t.Field(i).Type.String())
}
}
運(yùn)行結(jié)果:
Student
struct
3
field1 name is Name, field1 type is string
通過(guò) reflect.Type 的 Name() 方法可以獲取對(duì)應(yīng)的 Type 類型,Kind() 方法獲取底層的數(shù)據(jù)種類,即 kind,跟 reflect.Value 一樣,reflect.Type 也提供了 NumField() 方法用于獲取結(jié)構(gòu)體對(duì)象中的字段個(gè)數(shù),通過(guò) t.Field(i).Name 可以獲取對(duì)應(yīng)字段的名字。同樣,F(xiàn)ield(i) 和 NumField() 也只能對(duì)結(jié)構(gòu)體反射使用
指針?lè)瓷漕愋?/h4>
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
Score float64
}
func main() {
st := &Student{
Name: "zhangsan",
Age: 18,
Score: 90.5,
}
t := reflect.TypeOf(st)
fmt.Println(t.Kind())
fmt.Println(t.Elem().Name()) // 這里一定要加Elem(),根據(jù)指針獲取到具體類型后,才能或者具體的type名
fmt.Println(t.Elem().NumField()) // 這里一定要加Elem(),根據(jù)指針獲取到具體類型后,才能字段個(gè)數(shù)
for i := 0; i < t.Elem().NumField(); i++ {
fmt.Printf("field1 name is %s, field1 type is %s\n", t.Elem().Field(i).Name, t.Elem().Field(i).Type.String())
}
}
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
Score float64
}
func main() {
st := &Student{
Name: "zhangsan",
Age: 18,
Score: 90.5,
}
t := reflect.TypeOf(st)
fmt.Println(t.Kind())
fmt.Println(t.Elem().Name()) // 這里一定要加Elem(),根據(jù)指針獲取到具體類型后,才能或者具體的type名
fmt.Println(t.Elem().NumField()) // 這里一定要加Elem(),根據(jù)指針獲取到具體類型后,才能字段個(gè)數(shù)
for i := 0; i < t.Elem().NumField(); i++ {
fmt.Printf("field1 name is %s, field1 type is %s\n", t.Elem().Field(i).Name, t.Elem().Field(i).Type.String())
}
}
運(yùn)行結(jié)果:
ptr
Student
3
field1 name is Name, field1 type is string
field2 name is Age, field2 type is int
field3 name is Score, field3 type is float64
可以看到,跟上面直接獲取struct有一點(diǎn)點(diǎn)小小的區(qū)別,那就是fmt.Println(t.Kind())打印出的是一個(gè)ptr指針類型,而不再是struct類型,正是因?yàn)檫@里是一個(gè)ptr,所以我們不能直接在這個(gè)ptr上調(diào)用.Name()以及其他的.NumField()之類的方法,要根據(jù)ptr的.Elem()獲取到具體類型之后,才能用這些方法,否則程序就回報(bào)panic,這點(diǎn)一定要注意
反射獲取 struct 方法
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
Score float64
}
func (s *Student) GetName() string {
return s.Name
}
func (s *Student) SetName(name string) {
s.Name = name
}
func (s *Student) GetAge() int {
return s.Age
}
func (s *Student) SetAge(age int) {
s.Age = age
}
func (s *Student) GetScore() float64 {
return s.Score
}
func (s *Student) SetScore(score float64) {
s.Score = score
}
func main() {
st := &Student{
Name: "zhangsan",
Age: 18,
Score: 90.5,
}
t := reflect.TypeOf(st)
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("%+v\n", m)
}
}
運(yùn)行結(jié)果:
{GetName func(*main.Student) string}
{SetName func(*main.Student, string)}
{GetAge func(*main.Student) int}
{SetAge func(*main.Student, int)}
{GetScore func(*main.Student) float64}
{SetScore func(*main.Student, float64)}
- reflect.Type.NumMethod():返回struct所綁定的的方法個(gè)數(shù)
- reflect.Type.Method(i):返回第i個(gè)方法的 reflect.Method 對(duì)象
reflect.Method 定義在 src/reflect/type.go 文件:
type Method struct {
Name string // 方法名
PkgPath string
Type Type // 方法類型(
Func Value // 方法值(方法的接收器作為第一個(gè)參數(shù))
Index int // 是結(jié)構(gòu)體中的第幾個(gè)方法
}
所以,通過(guò) reflect.Method 對(duì)象,我們可以獲取到 struct 所綁定的對(duì)應(yīng)方法的方法名,方法類型等信息
通過(guò)反射調(diào)用方法
在上一小節(jié)我們知道了 reflect.Type.Method(i) 可以獲取到 struct 所綁定的具體的方法對(duì)象 reflect.Method,通過(guò)這個(gè)對(duì)象,我們不僅可以獲取方法的詳細(xì)信息,還可以動(dòng)態(tài)的調(diào)用方法。
其實(shí)在 reflect.Value 里我們也可以使用 NumMethod()/Method(i) 方法獲取到對(duì)應(yīng)的方法信息,不同的是 reflect.Value.Method(i) 返回的使一個(gè) reflect.Value 對(duì)象,但是同樣可以根據(jù)這個(gè)對(duì)象來(lái)動(dòng)態(tài)調(diào)用方法,只是兩者調(diào)用方法的方式有所區(qū)別
請(qǐng)看具體例子:
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
Score float64
}
func (s *Student) GetName() string {
return s.Name
}
func (s *Student) SetName(name string) {
s.Name = name
}
func (s *Student) GetAge() int {
return s.Age
}
func (s *Student) SetAge(age int) {
s.Age = age
}
func (s *Student) GetScore() float64 {
return s.Score
}
func (s *Student) SetScore(score float64) {
s.Score = score
}
func main() {
st := &Student{
Name: "zhangsan",
Age: 18,
Score: 90.5,
}
fmt.Printf("st === %+v\n", st)
t := reflect.TypeOf(st)
v := reflect.ValueOf(st)
m1, ok := t.MethodByName("SetName") // 獲取SetName方法
fmt.Printf("t get func by name:%t\n", ok)
argsV1 := make([]reflect.Value, 0)
argsV1 = append(argsV1, v)
argsV1 = append(argsV1, reflect.ValueOf("lisi"))
m1.Func.Call(argsV1) //
fmt.Printf("st === %+v\n", st)
m2 := v.MethodByName("SetName") // 獲取SetName方法
argsV2 := make([]reflect.Value, 0)
argsV2 = append(argsV2, reflect.ValueOf("wangwu"))
m2.Call(argsV2)
fmt.Printf("st === %+v\n", st)
}
運(yùn)行結(jié)果:
st === &{Name:zhangsan Age:18 Score:90.5}
t get func by name:true
st === &{Name:lisi Age:18 Score:90.5}
st === &{Name:wangwu Age:18 Score:90.5}
可以看到通過(guò) reflect.Type.MethodByName() 方法獲取到的reflect.Method對(duì)象和reflect.Value.MethodByName()方法獲取到的reflect.Method獲取到的reflect.Value對(duì)象都可以在程序運(yùn)行時(shí)動(dòng)態(tài)的調(diào)用方法修改結(jié)構(gòu)本身,student的name由zhangsan------>lisi------>wangwu。
但是二者的調(diào)用存在一個(gè)區(qū)別:通過(guò)reflect.Method調(diào)用方法,必須使用Func字段,而且要傳入接收器的reflect.Value作為第一個(gè)參數(shù)
m1.Func.Call(argsV1)
reflect.Value.MethodByName()返回一個(gè)reflect.Value對(duì)象,它不需要接收器的reflect.Value作為第一個(gè)參數(shù),而且直接使用Call()發(fā)起方法調(diào)用:
m2.Call(argsV2)
通過(guò)反射設(shè)置值
package main
import (
"fmt"
"reflect"
"strings"
)
type User struct {
Name1 string `big:"-"`
Name2 string
}
func SetStruct(obj any) {
v := reflect.ValueOf(obj).Elem()
t := reflect.TypeOf(obj).Elem()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
bigField := field.Tag.Get("big")
// 判斷類型是不是字符串
if field.Type.Kind() != reflect.String {
continue
}
if bigField == "" {
continue
}
// 修改值
valueFiled := v.Field(i)
valueFiled.SetString(strings.ToTitle(valueFiled.String()))
}
}
func main() {
s := User{Name1: "name1", Name2: "name2"}
SetStruct(&s)
fmt.Println(s)
}
四:反射的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 可以提升程序代碼的靈活性,根據(jù)條件在程序運(yùn)行時(shí)靈活的調(diào)用函數(shù),并且修改源代碼結(jié)構(gòu)
缺點(diǎn):
- 主要是性能影響,反射過(guò)程中會(huì)有大量的內(nèi)存開(kāi)辟和 gc 過(guò)程,導(dǎo)致程序的性能降低
五:共勉
到此這篇關(guān)于Go語(yǔ)言反射使用及優(yōu)缺點(diǎn)總結(jié)的文章就介紹到這了,更多相關(guān)Go語(yǔ)言反射內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解如何使用go-acme/lego實(shí)現(xiàn)自動(dòng)簽發(fā)證書(shū)
這篇文章主要為大家詳細(xì)介紹了如何使用?go-acme/lego?的客戶端或庫(kù)完成證書(shū)的自動(dòng)簽發(fā),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03
一文帶你使用Golang實(shí)現(xiàn)SSH客戶端
SSH?全稱為?Secure?Shell,是一種用于安全地遠(yuǎn)程登錄到網(wǎng)絡(luò)上的其他計(jì)算機(jī)的網(wǎng)絡(luò)協(xié)議,本文主要為大家詳細(xì)介紹了如何使用?Golang?實(shí)現(xiàn)?SSH?客戶端,需要的可以參考下2023-11-11
Golang模擬令牌桶進(jìn)行對(duì)訪問(wèn)的限流方式
這篇文章主要介紹了Golang模擬令牌桶進(jìn)行對(duì)訪問(wèn)的限流方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
詳解在Go語(yǔ)言中如何實(shí)現(xiàn)枚舉類型
枚舉類型是一種常用的數(shù)據(jù)類型,用于表示一組有限的、預(yù)定義的、具名的常量值。而在?Go?語(yǔ)言里是沒(méi)有內(nèi)置枚舉類型的,所以本文將介紹如何實(shí)現(xiàn)?“枚舉類型”,需要的可以參考一下2023-04-04
GO語(yǔ)言make和new關(guān)鍵字的區(qū)別
本篇文章來(lái)介紹一道非常常見(jiàn)的面試題,到底有多常見(jiàn)呢?可能很多面試的開(kāi)場(chǎng)白就是由此開(kāi)始的。那就是?new?和?make?這兩個(gè)內(nèi)置函數(shù)的區(qū)別,希望對(duì)大家有所幫助2023-04-04
golang時(shí)間字符串和時(shí)間戳轉(zhuǎn)換的案例
這篇文章主要介紹了golang時(shí)間字符串和時(shí)間戳轉(zhuǎn)換的案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
go語(yǔ)言實(shí)戰(zhàn)之實(shí)現(xiàn)比特幣地址校驗(yàn)步驟
這篇文章主要介紹了go語(yǔ)言實(shí)戰(zhàn)之實(shí)現(xiàn)比特幣地址校驗(yàn)步驟,利用生產(chǎn)的隨機(jī)數(shù)采用橢圓加密算法生成公鑰,具體步驟實(shí)例代碼請(qǐng)參考下本文2021-05-05

