欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文帶你了解Golang中reflect反射的常見錯誤

 更新時間:2023年01月05日 08:20:07   作者:eleven26  
go?反射的錯誤大多數(shù)都來自于調(diào)用了一個不適合當(dāng)前類型的方法,?而且,這些錯誤通常是在運行時才會暴露出來,而不是在編譯時,如果我們傳遞的類型在反射代碼中沒有被覆蓋到那么很容易就會?panic。本文就介紹一下使用?go?反射時很大概率會出現(xiàn)的錯誤,需要的可以參考一下

go 的反射是很脆弱的,保證反射代碼正確運行的前提是,在調(diào)用反射對象的方法之前, 先問一下自己正在調(diào)用的方法是不是適合于所有用于創(chuàng)建反射對象的原始類型。 go 反射的錯誤大多數(shù)都來自于調(diào)用了一個不適合當(dāng)前類型的方法(比如在一個整型反射對象上調(diào)用 Field() 方法)。 而且,這些錯誤通常是在運行時才會暴露出來,而不是在編譯時,如果我們傳遞的類型在反射代碼中沒有被覆蓋到那么很容易就會 panic。

本文就介紹一下使用 go 反射時很大概率會出現(xiàn)的錯誤。

獲取 Value 的值之前沒有判斷類型

對于 reflect.Value,我們有很多方法可以獲取它的值,比如 Int()、String() 等等。 但是,這些方法都有一個前提,就是反射對象底層必須是我們調(diào)用的那個方法對應(yīng)的類型,否則會 panic,比如下面這個例子:

var f float32 = 1.0
v := reflect.ValueOf(f)
// 報錯:panic: reflect: call of reflect.Value.Int on float32 Value
fmt.Println(v.Int())

上面這個例子中,f 是一個 float32 類型的浮點數(shù),然后我們嘗試通過 Int() 方法來獲取一個整數(shù),但是這個方法只能用于 int 類型的反射對象,所以會報錯。

  • 涉及的方法:Addr, Bool, Bytes, Complex, Int, Uint, Float, Interface;調(diào)用這些方法的時候,如果類型不對則會 panic。
  • 判斷反射對象能否轉(zhuǎn)換為某一類型的方法:CanAddr, CanInterface, CanComplex, CanFloat, CanInt, CanUint。
  • 其他類型是否能轉(zhuǎn)換判斷方法:CanConvert,可以判斷一個反射對象能否轉(zhuǎn)換為某一類型。

通過 CanConvert 方法來判斷一個反射對象能否轉(zhuǎn)換為某一類型:

// true
fmt.Println(v.CanConvert(reflect.TypeOf(1.0)))

如果我們想將反射對象轉(zhuǎn)換為我們的自定義類型,就可以通過 CanConvert 來判斷是否能轉(zhuǎn)換,然后再調(diào)用 Convert 方法來轉(zhuǎn)換:

type Person struct {
   Name string
}

func TestReflect(t *testing.T) {
   p := Person{Name: "foo"}
   v := reflect.ValueOf(p)

   // v 可以轉(zhuǎn)換為 Person 類型
   assert.True(t, v.CanConvert(reflect.TypeOf(Person{})))

   // v 可以轉(zhuǎn)換為 Person 類型
   p1 := v.Convert(reflect.TypeOf(Person{}))
   assert.Equal(t, "foo", p1.Interface().(Person).Name)
}

說明:

  • reflect.TypeOf(Person{}) 可以取得 Person 類型的信息
  • v.Convert 可以將 v 轉(zhuǎn)換為 reflect.TypeOf(Person{}) 指定的類型

沒有傳遞指針給 reflect.ValueOf

如果我們想通過反射對象來修改原變量,就必須傳遞一個指針,否則會報錯(暫不考慮 slice, map, 結(jié)構(gòu)體字段包含指針字段的特殊情況):

func TestReflect(t *testing.T) {
   p := Person{Name: "foo"}
   v := reflect.ValueOf(p)

   // 報錯:panic: reflect: reflect.Value.SetString using unaddressable value
   v.FieldByName("Name").SetString("bar")
}

這個錯誤的原因是,v 是一個 Person 類型的值,而不是指針,所以我們不能通過 v.FieldByName("Name") 來修改它的字段。

對于反射對象來說,只拿到了 p 的拷貝,而不是 p 本身,所以我們不能通過反射對象來修改 p。

在一個無效的 Value 上操作

我們有很多方法可以創(chuàng)建 reflect.Value,而且這類方法沒有 error 返回值,這就意味著,就算我們創(chuàng)建 reflect.Value 的時候傳遞了一個無效的值,也不會報錯,而是會返回一個無效的 reflect.Value

func TestReflect(t *testing.T) {
   var p = Person{}
   v := reflect.ValueOf(p)

   // Person 不存在 foo 方法
   // FieldByName 返回一個表示 Field 的反射對象 reflect.Value
   v1 := v.FieldByName("foo")
   assert.False(t, v1.IsValid())

   // v1 是無效的,只有 String 方法可以調(diào)用
   // 其他方法調(diào)用都會 panic
   assert.Panics(t, func() {
      // panic: reflect: call of reflect.Value.NumMethod on zero Value
      fmt.Println(v1.NumMethod())
   })
}

對于這個問題,我們可以通過 IsValid 方法來判斷 reflect.Value 是否有效:

func TestReflect(t *testing.T) {
   var p = Person{}
   v := reflect.ValueOf(p)

   v1 := v.FieldByName("foo")
   // 通過 IsValid 判斷 reflect.Value 是否有效
   if v1.IsValid() {
      fmt.Println("p has foo field")
   } else {
      fmt.Println("p has no foo field")
   }
}

Field() 方法在傳遞的索引超出范圍的時候,直接 panic,而不會返回一個 invalid 的 reflect.Value。

IsValid 報告反射對象 v 是否代表一個值。 如果 v 是零值,則返回 false。 如果 IsValid 返回 false,則除 String 之外的所有其他方法都將發(fā)生 panic。 大多數(shù)函數(shù)和方法從不返回?zé)o效值。

什么時候 IsValid 返回 false

reflect.ValueIsValid 的返回值表示 reflect.Value 是否有效,而不是它代表的值是否有效。比如:

var b *int = nil
v := reflect.ValueOf(b)
fmt.Println(v.IsValid())                   // true
fmt.Println(v.Elem().IsValid())            // false
fmt.Println(reflect.Indirect(v).IsValid()) // false

在上面這個例子中,v 是有效的,它表示了一個指針,指針指向的對象為 nil。 但是 v.Elem()reflect.Indirect(v) 都是無效的,因為它們表示的是指針指向的對象,而指針指向的對象為 nil。 我們無法基于 nil 來做任何反射操作。

其他情況下 IsValid 返回 false

除了上面的情況,IsValid 還有其他情況下會返回 false

  • 空的反射值對象,獲取通過 nil 創(chuàng)建的反射對象,其 IsValid 會返回 false
  • 結(jié)構(gòu)體反射對象通過 FieldByName 獲取了一個不存在的字段,其 IsValid 會返回 false。
  • 結(jié)構(gòu)體反射對象通過 MethodByName 獲取了一個不存在的方法,其 IsValid 會返回 false。
  • map 反射對象通過 MapIndex 獲取了一個不存在的 key,其 IsValid 會返回 false。

示例:

func TestReflect(t *testing.T) {
   // 空的反射對象
   fmt.Println(reflect.Value{}.IsValid())      // false
   // 基于 nil 創(chuàng)建的反射對象
   fmt.Println(reflect.ValueOf(nil).IsValid()) // false

   s := struct{}{}
   // 獲取不存在的字段
   fmt.Println(reflect.ValueOf(s).FieldByName("").IsValid())  // false
   // 獲取不存在的方法
   fmt.Println(reflect.ValueOf(s).MethodByName("").IsValid()) // false

   m := map[int]int{}
   // 獲取 map 的不存在的 key
   fmt.Println(reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}

注意:還有其他一些情況也會使 IsValid 返回 false,這里只是列出了部分情況。 我們在使用的時候需要注意我們正在使用的反射對象會不會是無效的。

通過反射修改不可修改的值

對于 reflect.Value 對象,我們可以通過 CanSet 方法來判斷它是否可以被設(shè)置:

func TestReflect(t *testing.T) {
   p := Person{Name: "foo"}

   // 傳遞值來創(chuàng)建的發(fā)射對象,
   // 不能修改其值,因為它是一個副本
   v := reflect.ValueOf(p)
   assert.False(t, v.CanSet())
   assert.False(t, v.Field(0).CanSet())

   // 下面這一行代碼會 panic:
   // panic: reflect: reflect.Value.SetString using unaddressable value
   // v.Field(0).SetString("bar")

   // 指針反射對象本身不能修改,
   // 其指向的對象(也就是 v1.Elem())可以修改
   v1 := reflect.ValueOf(&p)
   assert.False(t, v1.CanSet())
   assert.True(t, v1.Elem().CanSet())
}

CanSet 報告 v 的值是否可以更改。只有可尋址(addressable)且不是通過使用未導(dǎo)出的結(jié)構(gòu)字段獲得的值才能更改。 如果 CanSet 返回 false,調(diào)用 Set 或任何類型特定的 setter(例如 SetBoolSetInt)將 panic。CanSet 的條件是可尋址。

對于傳值創(chuàng)建的反射對象,我們無法通過反射對象來修改原變量,CanSet 方法返回 false。 例外的情況是,如果這個值中包含了指針,我們依然可以通過那個指針來修改其指向的對象。

只有通過 Elem 方法的返回值才能設(shè)置指針指向的對象。

在錯誤的 Value 上調(diào)用 Elem 方法

reflect.ValueElem() 返回 interface 的反射對象包含的值或指針反射對象指向的值。如果反射對象的 Kind 不是 reflect.Interfacereflect.Pointer,它會發(fā)生 panic。 如果反射對象為 nil,則返回零值。

我們知道,interface 類型實際上包含了類型和數(shù)據(jù)。而我們傳遞給 reflect.ValueOf 的參數(shù)就是 interface,所以在反射對象中也提供了方法來獲取 interface 類型的類型和數(shù)據(jù):

func TestReflect(t *testing.T) {
   p := Person{Name: "foo"}

   v := reflect.ValueOf(p)

   // 下面這一行會報錯:
   // panic: reflect: call of reflect.Value.Elem on struct Value
   // v.Elem()
   fmt.Println(v.Type())

   // v1 是 *Person 類型的反射對象,是一個指針
   v1 := reflect.ValueOf(&p)
   fmt.Println(v1.Elem(), v1.Type())
}

在上面的例子中,v 是一個 Person 類型的反射對象,它不是一個指針,所以我們不能通過 v.Elem() 來獲取它指向的對象。 而 v1 是一個指針,所以我們可以通過 v1.Elem() 來獲取它指向的對象。

調(diào)用了一個其類型不能調(diào)用的方法

這可能是最常見的一類錯誤了,因為在 go 的反射系統(tǒng)中,我們調(diào)用的一些方法又會返回一個相同類型的反射對象,但是這個新的反射對象可能是一個不同的類型了。同時返回的這個反射對象是否有效也是未知的。

在 go 中,反射有兩大對象 reflect.Typereflect.Value,它們都存在一些方法只適用于某些特定的類型,也就是說, 在 go 的反射設(shè)計中,只分為了類型兩大類。但是實際的 go 中的類型就有很多種,比如 int、string、struct、interfaceslice、map、chan、func 等等。

我們先不說 reflect.Type,我們從 reflect.Value 的角度看看,將這么多類型的值都抽象為 reflect.Value 之后, 我們?nèi)绾潍@取某些類型值特定的信息呢?比如獲取結(jié)構(gòu)體的某一個字段的值,或者調(diào)用某一個方法。 這個問題很好解決,需要獲取結(jié)構(gòu)體字段是吧,那給你提供一個 Field() 方法,需要調(diào)用方法吧,那給你提供一個 Call() 方法。

但是這樣一來,有另外一個問題就是,如果我們的 reflect.Value 是從一個 int 類型的值創(chuàng)建的, 那么我們調(diào)用 Field() 方法就會發(fā)生 panic,因為 int 類型的值是沒有 Field() 方法的:

func TestReflect(t *testing.T) {
   p := Person{Name: "foo"}
   v := reflect.ValueOf(p)

   // 獲取反射對象的 Name 字段
   assert.Equal(t, "foo", v.Field(0).String())

   var i = 1
   v1 := reflect.ValueOf(i)
   assert.Panics(t, func() {
      // 下面這一行會 panic:
      // v1 沒有 Field 方法
      fmt.Println(v1.Field(0).String())
   })
}

至于有哪些方法是某些類型特定的,可以參考一下下面兩個文檔:

總結(jié)

  • 在調(diào)用 Int()、Float() 等方法時,需要確保反射對象的類型是正確的類型,否則會 panic,比如在一個 flaot 類型的反射對象上調(diào)用 Int() 方法就會 panic。
  • 如果想修改原始的變量,創(chuàng)建 reflect.Value 時需要傳入原始變量的指針。
  • 如果 reflect.ValueIsValid() 方法返回 false,那么它就是一個無效的反射對象,調(diào)用它的任何方法都會 panic,除了 String 方法。
  • 對于基于值創(chuàng)建的 reflect.Value,如果想要修改它的值,我們無法調(diào)用這個反射對象的 Set* 方法,因為修改一個變量的拷貝沒有任何意義。
  • 同時,我們也無法通過 reflect.Value 去修改結(jié)構(gòu)體中未導(dǎo)出的字段,即使我們創(chuàng)建 reflect.Value 時傳入的是結(jié)構(gòu)體的指針。
  • Elem() 只可以在指針或者 interface 類型的反射對象上調(diào)用,否則會 panic,它的作用是獲取指針指向的對象的反射對象,又或者獲取接口 data 的反射對象。
  • reflect.Valuereflect.Type 都有很多類型特定的方法,比如 Field()、Call() 等,這些方法只能在某些類型的反射對象上調(diào)用,否則會 panic

到此這篇關(guān)于一文帶你了解Golang中reflect反射的常見錯誤的文章就介紹到這了,更多相關(guān)Golang reflect反射內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • MacOS中 VSCode 安裝 GO 插件失敗問題的快速解決方法

    MacOS中 VSCode 安裝 GO 插件失敗問題的快速解決方法

    這篇文章主要介紹了MacOS中 VSCode 安裝 GO 插件失敗問題的快速解決方法,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-05-05
  • 解決goland 導(dǎo)入項目后import里的包報紅問題

    解決goland 導(dǎo)入項目后import里的包報紅問題

    這篇文章主要介紹了解決goland 導(dǎo)入項目后import里的包報紅問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • go HTTP2 的頭部壓縮算法hpack實現(xiàn)詳解

    go HTTP2 的頭部壓縮算法hpack實現(xiàn)詳解

    這篇文章主要為大家介紹了go HTTP2 的頭部壓縮算法hpack實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • Golang實現(xiàn)快速求冪的方法詳解

    Golang實現(xiàn)快速求冪的方法詳解

    這篇文章主要為大家詳細介紹了如何利用Golang實現(xiàn)快速求冪,文中的示例代碼講解詳細,對我們學(xué)習(xí)或工作有一定參考價值,需要的可以參考一下
    2022-06-06
  • Go單體服務(wù)開發(fā)最佳實踐總結(jié)

    Go單體服務(wù)開發(fā)最佳實踐總結(jié)

    這篇文章主要介紹了Go單體服務(wù)開發(fā)最佳實踐,通過本文詳細跟大家分享一下如何使用?go-zero?快速開發(fā)一個有多個模塊的單體服務(wù),需要的朋友可以參考下
    2022-04-04
  • Go語言高效I/O并發(fā)處理雙緩沖和Exchanger模式實例探索

    Go語言高效I/O并發(fā)處理雙緩沖和Exchanger模式實例探索

    這篇文章主要介紹了Go語言高效I/O并發(fā)處理雙緩沖和Exchanger模式實例探索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • go Antlr重構(gòu)腳本解釋器實現(xiàn)示例

    go Antlr重構(gòu)腳本解釋器實現(xiàn)示例

    這篇文章主要為大家介紹了go Antlr重構(gòu)腳本解釋器實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • 詳解Go 語言中的比較操作符

    詳解Go 語言中的比較操作符

    這篇文章專注于 6 個操作符,==,!=,<,<=,> 和 >=。我們將深入探討它們的語法和用法的細微差別,感興趣的朋友跟隨腳本之家小編一起看看吧
    2018-08-08
  • Go源碼分析之預(yù)分配slice內(nèi)存

    Go源碼分析之預(yù)分配slice內(nèi)存

    這篇文章主要從Go語言源碼帶大家分析一下預(yù)分配slice內(nèi)存的相關(guān)知識,文中的示例代碼簡潔易懂,對我們深入了解go有一定的幫助,需要的可以學(xué)習(xí)一下
    2023-08-08
  • Golang 實現(xiàn) Redis系列(六)如何實現(xiàn) pipeline 模式的 redis 客戶端

    Golang 實現(xiàn) Redis系列(六)如何實現(xiàn) pipeline 模式的 redis 客戶端

    pipeline 模式的 redis 客戶端需要有兩個后臺協(xié)程負責(zé) tcp 通信,調(diào)用方通過 channel 向后臺協(xié)程發(fā)送指令,并阻塞等待直到收到響應(yīng),本文是使用 golang 實現(xiàn) redis 系列的第六篇, 將介紹如何實現(xiàn)一個 Pipeline 模式的 Redis 客戶端。
    2021-07-07

最新評論