首頁 後端開發 Golang golang反射有啥用?

golang反射有啥用?

Apr 17, 2020 pm 02:30 PM
golang 反射

golang反射有啥用?下面本篇文章來跟大家介紹一下golang反射(reflection)。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。

golang反射有啥用?

golang(go)是一種過程程式語言,可用來快速機器碼編譯。它是一種靜態型別的編譯語言。它提供了並發機制,可以輕鬆開發多核心和聯網的機器級程式。它是快速,動態類型和解釋語言;它提供對介面和類型嵌入的支援。

基本上了解

在Go語言中,大多數時候值/型別/函數非常直接,要的話,定義一個。你想要個Struct

type Foo struct {
    A int 
    B string
}
登入後複製

你想要一個值,你定義出來

var x Foo
登入後複製

你想要一個函數,你定義出來

func DoSomething(f Foo) {
  fmt.Println(f.A, f.B)
}
登入後複製

但是有些時候,你需要搞一些運行時才能確定的東西,例如你要從檔案或網路中取得一些字典資料。又或者你要搞一些不同類型的資料。在這種情況下,reflection(反射)就有用啦。 reflection能夠讓你擁有以下能力:

  • 在運行時檢查type

  • 在運行時檢查/修改/建立值/函數/結構

總的來說,go的reflection# 圍繞者三個概念Types, Kinds, Values。所有關於反射的操作都在reflect包裡面

反射的Power

##Type的Power

首先,我們來看看如何透過反射來取得值得類型。

varType := reflect.TypeOf(var)
登入後複製

從反射介面可以看到有一大堆得函數等著我們去用。可以從註釋裡面看到。反射包預設我們知道我們要乾啥子,例如varType.Elem()就會panic。因為Elem()只有Array, Chan, Map, Ptr, or Slice.這些類型才有這個方法。具體可以查看測試程式碼。透過執行以下程式碼可檢視所有reflect函數的範例

package main
import (
    "fmt"
    "reflect"
)
type FooIF interface {
    DoSomething()
    DoSomethingWithArg(a string)
    DoSomethingWithUnCertenArg(a ... string)
}
type Foo struct {
    A int
    B string
    C struct {
        C1 int
    }
}
func (f *Foo) DoSomething() {
    fmt.Println(f.A, f.B)
}
func (f *Foo) DoSomethingWithArg(a string) {
    fmt.Println(f.A, f.B, a)
}
func (f *Foo) DoSomethingWithUnCertenArg(a ... string) {
    fmt.Println(f.A, f.B, a[0])
}
func (f *Foo) returnOneResult() int {
    return 2
}
func main() {
    var simpleObj Foo
    var pointer2obj = &simpleObj
    var simpleIntArray = [3]int{1, 2, 3}
    var simpleMap = map[string]string{
        "a": "b",
    }
    var simpleChan = make(chan int, 1)
    var x uint64
    var y uint32
    varType := reflect.TypeOf(simpleObj)
    varPointerType := reflect.TypeOf(pointer2obj)
    // 对齐之后要多少容量
    fmt.Println("Align: ", varType.Align())
    // 作为结构体的`field`要对其之后要多少容量
    fmt.Println("FieldAlign: ", varType.FieldAlign())
    // 叫啥
    fmt.Println("Name: ", varType.Name())
    // 绝对引入路径
    fmt.Println("PkgPath: ", varType.PkgPath())
    // 实际上用了多少内存
    fmt.Println("Size: ", varType.Size())
    // 到底啥类型的
    fmt.Println("Kind: ", varType.Kind())
    // 有多少函数
    fmt.Println("NumMethod: ", varPointerType.NumMethod())
    // 通过名字获取一个函数
    m, success := varPointerType.MethodByName("DoSomethingWithArg")
    if success {
        m.Func.Call([]reflect.Value{
            reflect.ValueOf(pointer2obj),
            reflect.ValueOf("sad"),
        })
    }
    // 通过索引获取函数
    m = varPointerType.Method(1)
    m.Func.Call([]reflect.Value{
        reflect.ValueOf(pointer2obj),
        reflect.ValueOf("sad2"),
    })
    // 是否实现了某个接口
    fmt.Println("Implements:", varPointerType.Implements(reflect.TypeOf((*FooIF)(nil)).Elem()))
    //  看看指针多少bit
    fmt.Println("Bits: ", reflect.TypeOf(x).Bits())
    // 查看array, chan, map, ptr, slice的元素类型
    fmt.Println("Elem: ", reflect.TypeOf(simpleIntArray).Elem().Kind())
    // 查看Array长度
    fmt.Println("Len: ", reflect.TypeOf(simpleIntArray).Len())
    // 查看结构体field
    fmt.Println("Field", varType.Field(1))
    // 查看结构体field
    fmt.Println("FieldByIndex", varType.FieldByIndex([]int{2, 0}))
    // 查看结构提field
    fi, success2 := varType.FieldByName("A")
    if success2 {
        fmt.Println("FieldByName", fi)
    }
    // 查看结构体field
    fi, success2 = varType.FieldByNameFunc(func(fieldName string) bool {
        return fieldName == "A"
    })
    if success2 {
        fmt.Println("FieldByName", fi)
    }
    //  查看结构体数量
    fmt.Println("NumField", varType.NumField())
    // 查看map的key类型
    fmt.Println("Key: ", reflect.TypeOf(simpleMap).Key().Name())
    // 查看函数有多少个参数
    fmt.Println("NumIn: ", reflect.TypeOf(pointer2obj.DoSomethingWithUnCertenArg).NumIn())
    // 查看函数参数的类型
    fmt.Println("In: ", reflect.TypeOf(pointer2obj.DoSomethingWithUnCertenArg).In(0))
    // 查看最后一个参数,是否解构了
    fmt.Println("IsVariadic: ", reflect.TypeOf(pointer2obj.DoSomethingWithUnCertenArg).IsVariadic())
    // 查看函数有多少输出
    fmt.Println("NumOut: ", reflect.TypeOf(pointer2obj.DoSomethingWithUnCertenArg).NumOut())
    // 查看函数输出的类型
    fmt.Println("Out: ", reflect.TypeOf(pointer2obj.returnOneResult).Out(0))
    // 查看通道的方向, 3双向。
    fmt.Println("ChanDir: ", int(reflect.TypeOf(simpleChan).ChanDir()))
    // 查看该类型是否可以比较。不能比较的slice, map, func
    fmt.Println("Comparable: ", varPointerType.Comparable())
    // 查看类型是否可以转化成另外一种类型
    fmt.Println("ConvertibleTo: ", varPointerType.ConvertibleTo(reflect.TypeOf("a")))
    // 该类型的值是否可以另外一个类型
    fmt.Println("AssignableTo: ", reflect.TypeOf(x).AssignableTo(reflect.TypeOf(y)))
}
登入後複製

Value的Power

除了檢查變數的類型,你可以透過reflection來讀取/寫入/新建一個值。不過首先先取得反射值類型

refVal := reflect.ValueOf(var)
登入後複製

如果你想要修改變數的值。你需要取得反射指向該變數的指針,具體原因後面解釋

refPtrVal := reflect.ValueOf(&var)
登入後複製

當然你有了reflect.Value,透過Type()方法可以很容易的取得reflect.Type。如果要改變該變數的值用

refPtrVal.Elem().Set(newRefValue)
登入後複製

當然Set方法的參數必須也得是reflect.Value

如果你想建立一個新的值,用以下下程式碼

newPtrVal := reflect.New(varType)
登入後複製

然後在用Elem().Set()來進行值的初始化。當然還有不同的value有一大堆的不同的方法。這裡就不寫了。我們將重點放在下面這段官方範例

package main
import (
    "fmt"
    "reflect"
)
func main() {
    // swap is the implementation passed to MakeFunc.
    // It must work in terms of reflect.Values so that it is possible
    // to write code without knowing beforehand what the types
    // will be.
    swap := func(in []reflect.Value) []reflect.Value {
        return []reflect.Value{in[1], in[0]}
    }
    // makeSwap expects fptr to be a pointer to a nil function.
    // It sets that pointer to a new function created with MakeFunc.
    // When the function is invoked, reflect turns the arguments
    // into Values, calls swap, and then turns swap's result slice
    // into the values returned by the new function.
    makeSwap := func(fptr interface{}) {
        // fptr is a pointer to a function.
        // Obtain the function value itself (likely nil) as a reflect.Value
        // so that we can query its type and then set the value.
        fn := reflect.ValueOf(fptr).Elem()
        // Make a function of the right type.
        v := reflect.MakeFunc(fn.Type(), swap)
        // Assign it to the value fn represents.
        fn.Set(v)
    }
    // Make and call a swap function for ints.
    var intSwap func(int, int) (int, int)
    makeSwap(&intSwap)
    fmt.Println(intSwap(0, 1))
    // Make and call a swap function for float64s.
    var floatSwap func(float64, float64) (float64, float64)
    makeSwap(&floatSwap)
    fmt.Println(floatSwap(2.72, 3.14))
}
登入後複製

原理

承認type與interface

##go是一個靜態型別語言,每一個變數都有static type,例如int,float,何謂static type,我的理解是一定長度的二進位塊與解釋。例如同樣的二進位區塊00000001 在bool型別中意思是true。而在int型別解釋是1. 我們看看以下這個最簡單的例子

type MyInt int
var i int
var j MyInt
登入後複製
i,j在記憶體中都是用int這一個底層型別來表示,但是在實際編碼過程中,在編譯的時候他們並非一個型,你不能直接將i的值賦給j。是不是有點奇怪,你執行的時候編譯器會告訴你,你不能把MyInt類型的值賦給int型別的值。這個type不是class也不是python的type.

interface作為一種特殊的type, 表示方法的集合。一個interface的值可以存任何確定的值只要這個值實作了interface的方法。 interface{}某些時候和Java的Object好想,實際上interface是有兩部分內容組成的,實際的值和值的具體類型。這也可以解釋為什麼下面這段程式碼和其他語言都不一樣。具體關於interface的原理可以參考go data structures: interfaces。

package main
import (
    "fmt"
)
type A interface {
    x(param int)
}
type B interface {
    y(param int)
}
type AB struct {
}
func (ab *AB) x(param int) {
    fmt.Printf("%p", ab)
    fmt.Println(param)
}
func (ab *AB) y(param int) {
    fmt.Printf("%p", ab)
    fmt.Println(param)
}
func printX(a A){
    fmt.Printf("%p", a)
    a.x(2)
}
func printY(b B){
    fmt.Printf("%p", b)
    b.y(3)
}
func main() {
    var ab = new(AB)
    printX(ab)
    printY(ab)
    var aInfImpl A
    var bInfImpl B
    aInfImpl = new(AB)
    //bInfImpl = aInfImpl  会报错
    bInfImpl = aInfImpl.(B)
    bInfImpl.y(2)
}
登入後複製

golang反射三定理

#把一個interface值,分割出反射物件

反射僅用於檢查介面值的(Value, Type)。如上一章提到的兩個方法ValueOf和TypeOf。透過ValueOf我閘可以輕易的拿到Type###
package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    fmt.Println("type:", reflect.TypeOf(x))
}
登入後複製
###這段程式碼輸出###
type: float64
登入後複製
###那麼問題就來了,介面在哪裡?只是申明了一個float64的變數。哪裡來的interface。有的,答案藏在 TypeOf參數裡面###
func TypeOf(i interface{}) Type
登入後複製
###當我們呼叫reflect.TypeOf(x), x先被存在一個空的interface裡面。然後在被當作參數傳到函式執行棧內。 ** reflect.TypeOf解開這個interface的pair然後恢復出類型資訊**#########把反射物件組合成一個介面值######

就像镜面反射一样,go的反射是可逆的。给我一个reflect.Value。我们能够恢复出一个interface的值。事实上,以下函数干的事情就是将Value和Type组狠起来塞到 interface里面去。所以我们可以

y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)
登入後複製

接下来就是见证奇迹的时刻。fmt.Println和fmt.Printf的参数都是interface{}。我们真正都不需要将上面例子的y转化成明确的float64。我就可以去打印他比如

fmt.Println(v.Interface())
登入後複製

甚至我们的interface藏着的那个type是float64。我们可以直接用占位符来打印

fmt.Println("Value is %7.le\n", v.Interface())
登入後複製

再重复一边,没有必要将v.Interface()的类型强转到float64;这个空的interface{}包含了concrete type。函数调用会恢复出来

要改变一个反射对象,其值必须是可设置的

第三条比较让你比较困惑。不过如果我们理解了第一条,那么这条其实非常好理解。先看一下下面这个例子

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
登入後複製

如果执行这段代码,你会发现出现panic以下信息

panic: reflect.Value.SetFloat using unaddressable value
登入後複製

可设置性是一个好东西,但不是所有reflect.Value都有他...可以通过CanSet 函数来获取是否可设置

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())
登入後複製

那么到底为什么要有一个可设置性呢?可寻址才可设置,我们在用reflect.ValueOf时候,实际上是函数传值。获取x的反射对象,实际上是另外一个float64的内存的反射对象。这个时候我们再去设置该反射对象的值,没有意义。这段内存并不是你申明的那个x。

推荐学习:Golang教程

以上是golang反射有啥用?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何使用 Golang 安全地讀取和寫入檔案? 如何使用 Golang 安全地讀取和寫入檔案? Jun 06, 2024 pm 05:14 PM

在Go中安全地讀取和寫入檔案至關重要。指南包括:檢查檔案權限使用defer關閉檔案驗證檔案路徑使用上下文逾時遵循這些準則可確保資料的安全性和應用程式的健全性。

如何為 Golang 資料庫連線配置連線池? 如何為 Golang 資料庫連線配置連線池? Jun 06, 2024 am 11:21 AM

如何為Go資料庫連線配置連線池?使用database/sql包中的DB類型建立資料庫連線;設定MaxOpenConns以控制最大並發連線數;設定MaxIdleConns以設定最大空閒連線數;設定ConnMaxLifetime以控制連線的最大生命週期。

如何在 Golang 中將 JSON 資料保存到資料庫中? 如何在 Golang 中將 JSON 資料保存到資料庫中? Jun 06, 2024 am 11:24 AM

可以透過使用gjson函式庫或json.Unmarshal函數將JSON資料儲存到MySQL資料庫中。 gjson函式庫提供了方便的方法來解析JSON字段,而json.Unmarshal函數需要一個目標類型指標來解組JSON資料。這兩種方法都需要準備SQL語句和執行插入操作來將資料持久化到資料庫中。

Golang框架與Go框架:內部架構與外部特性對比 Golang框架與Go框架:內部架構與外部特性對比 Jun 06, 2024 pm 12:37 PM

GoLang框架與Go框架的差異體現在內部架構與外部特性。 GoLang框架基於Go標準函式庫,擴充其功能,而Go框架由獨立函式庫組成,以實現特定目的。 GoLang框架更靈活,Go框架更容易上手。 GoLang框架在效能上稍有優勢,Go框架的可擴充性更高。案例:gin-gonic(Go框架)用於建立RESTAPI,而Echo(GoLang框架)用於建立Web應用程式。

從前端轉型後端開發,學習Java還是Golang更有前景? 從前端轉型後端開發,學習Java還是Golang更有前景? Apr 02, 2025 am 09:12 AM

後端學習路徑:從前端轉型到後端的探索之旅作為一名從前端開發轉型的後端初學者,你已經有了nodejs的基礎,...

如何找出 Golang 正規表示式符合的第一個子字串? 如何找出 Golang 正規表示式符合的第一個子字串? Jun 06, 2024 am 10:51 AM

FindStringSubmatch函數可找出正規表示式匹配的第一個子字串:此函數傳回包含匹配子字串的切片,第一個元素為整個匹配字串,後續元素為各個子字串。程式碼範例:regexp.FindStringSubmatch(text,pattern)傳回符合子字串的切片。實戰案例:可用於匹配電子郵件地址中的域名,例如:email:="user@example.com",pattern:=@([^\s]+)$獲取域名match[1]。

golang框架開發實戰教學:常見疑問解答 golang框架開發實戰教學:常見疑問解答 Jun 06, 2024 am 11:02 AM

Go框架開發常見問題:框架選擇:取決於應用需求和開發者偏好,如Gin(API)、Echo(可擴展)、Beego(ORM)、Iris(效能)。安裝和使用:使用gomod指令安裝,導入框架並使用。資料庫互動:使用ORM庫,如gorm,建立資料庫連線和操作。身份驗證和授權:使用會話管理和身份驗證中間件,如gin-contrib/sessions。實戰案例:使用Gin框架建立一個簡單的部落格API,提供POST、GET等功能。

Go語言中哪些庫是由大公司開發或知名的開源項目提供的? Go語言中哪些庫是由大公司開發或知名的開源項目提供的? Apr 02, 2025 pm 04:12 PM

Go語言中哪些庫是大公司開發或知名開源項目?在使用Go語言進行編程時,開發者常常會遇到一些常見的需求,�...

See all articles