亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

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

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

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

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

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

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

通過 CanConvert 方法來判斷一個(gè)反射對象能否轉(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

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

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

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

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

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

在一個(gè)無效的 Value 上操作

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

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

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

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

對于這個(gè)問題,我們可以通過 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() 方法在傳遞的索引超出范圍的時(shí)候,直接 panic,而不會(huì)返回一個(gè) invalid 的 reflect.Value。

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

什么時(shí)候 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

在上面這個(gè)例子中,v 是有效的,它表示了一個(gè)指針,指針指向的對象為 nil。 但是 v.Elem()reflect.Indirect(v) 都是無效的,因?yàn)樗鼈儽硎镜氖侵羔樦赶虻膶ο?,而指針指向的對象?nil。 我們無法基于 nil 來做任何反射操作。

其他情況下 IsValid 返回 false

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

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

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

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

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

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

   // 傳遞值來創(chuàng)建的發(fā)射對象,
   // 不能修改其值,因?yàn)樗且粋€(gè)副本
   v := reflect.ValueOf(p)
   assert.False(t, v.CanSet())
   assert.False(t, v.Field(0).CanSet())

   // 下面這一行代碼會(huì) 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 報(bào)告 v 的值是否可以更改。只有可尋址(addressable)且不是通過使用未導(dǎo)出的結(jié)構(gòu)字段獲得的值才能更改。 如果 CanSet 返回 false,調(diào)用 Set 或任何類型特定的 setter(例如 SetBool、SetInt)將 panic。CanSet 的條件是可尋址。

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

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

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

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

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

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

   v := reflect.ValueOf(p)

   // 下面這一行會(huì)報(bào)錯(cuò):
   // panic: reflect: call of reflect.Value.Elem on struct Value
   // v.Elem()
   fmt.Println(v.Type())

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

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

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

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

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

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

但是這樣一來,有另外一個(gè)問題就是,如果我們的 reflect.Value 是從一個(gè) int 類型的值創(chuàng)建的, 那么我們調(diào)用 Field() 方法就會(huì)發(fā)生 panic,因?yàn)?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() {
      // 下面這一行會(huì) panic:
      // v1 沒有 Field 方法
      fmt.Println(v1.Field(0).String())
   })
}

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

總結(jié)

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

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

相關(guān)文章

  • golang解析域名的步驟全紀(jì)錄

    golang解析域名的步驟全紀(jì)錄

    這篇文章主要給大家介紹了利用golang如何解析域名的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-12-12
  • 深入理解go sync.Once的具體使用

    深入理解go sync.Once的具體使用

    在很多情況下,我們可能需要控制某一段代碼只執(zhí)行一次,go 為我們提供了?sync.Once?對象,它保證了某個(gè)動(dòng)作只被執(zhí)行一次,本文主要介紹了深入理解go sync.Once的具體使用,感興趣的可以了解一下
    2024-01-01
  • golang修改結(jié)構(gòu)體中的切片值方法

    golang修改結(jié)構(gòu)體中的切片值方法

    這篇文章主要介紹了golang修改結(jié)構(gòu)體中的切片值方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Go語言Swagger實(shí)現(xiàn)為項(xiàng)目生成 API 文檔

    Go語言Swagger實(shí)現(xiàn)為項(xiàng)目生成 API 文檔

    Swagger 是一個(gè)基于 OpenAPI 規(guī)范設(shè)計(jì)的工具,用于為 RESTful API 生成交互式文檔,下面小編就來介紹一下如何在 Go 項(xiàng)目中集成 Swagger,特別是結(jié)合 Gin 框架生成 API 文檔
    2025-03-03
  • Linux環(huán)境下編譯并運(yùn)行g(shù)o項(xiàng)目的全過程

    Linux環(huán)境下編譯并運(yùn)行g(shù)o項(xiàng)目的全過程

    Go語言是Google的開源編程語言,廣泛應(yīng)用于云計(jì)算、分布式系統(tǒng)開發(fā)等領(lǐng)域,在Linux上也有大量的應(yīng)用場景,這篇文章主要給大家介紹了關(guān)于Linux環(huán)境下編譯并運(yùn)行g(shù)o項(xiàng)目的相關(guān)資料,需要的朋友可以參考下
    2023-11-11
  • 深入淺出Go:掌握基礎(chǔ)知識(shí)的關(guān)鍵要點(diǎn)

    深入淺出Go:掌握基礎(chǔ)知識(shí)的關(guān)鍵要點(diǎn)

    Go是一種開源的編程語言,由Google開發(fā),它具有簡潔、高效、并發(fā)性強(qiáng)的特點(diǎn),適用于構(gòu)建可靠的、高性能的軟件系統(tǒng),本文將介紹Go的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2023-10-10
  • 淺談Golang內(nèi)存逃逸

    淺談Golang內(nèi)存逃逸

    本文主要介紹了Golang內(nèi)存逃逸,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • golang 之import和package的使用

    golang 之import和package的使用

    這篇文章主要介紹了golang 之import和package的使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2019-02-02
  • Go語言中sync包使用方法教程

    Go語言中sync包使用方法教程

    在Go語言的并發(fā)編程實(shí)踐中,性能優(yōu)化總是繞不開的話題,下面這篇文章主要介紹了Go語言中sync包使用方法的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-03-03
  • logrus hook輸出日志到本地磁盤的操作

    logrus hook輸出日志到本地磁盤的操作

    這篇文章主要介紹了logrus hook輸出日志到本地磁盤的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11

最新評(píng)論