Home  >  Article  >  Backend Development  >  An article explaining reflection in Golang in detail

An article explaining reflection in Golang in detail

青灯夜游
青灯夜游forward
2022-12-14 20:26:346108browse

This article will mainly talk about reflection in Golang, hoping to gain a new understanding of you.

An article explaining reflection in Golang in detail

Although many people have been using the Go language for a certain period of time, and some have even used it for 1 or 2 years, they are still ambiguous about reflection in the Go language. , I am not very confident in my heart. [Related recommendations: Go video tutorial, Programming teaching]

What’s more, reflection is almost never used. Of course, there is nothing wrong with it. You can It is naturally the nicest to handle it in the simplest, most efficient, scalable, and good-performance way. There is no need to mechanically copy some advanced usage. After all, work is not our testing ground. You can go down and experiment by yourself, this Let’s take a closer look at how to play reflection

The article will talk about it from the following five aspects

  • What is reflection
  • Rules of reflection
  • Use cases and flexible application
  • Reflection principle
  • Summary

A simple look at what reflection is

In a simple way, Reflection is the ability to access and modify the program itself while the program is running, for example, when the program is running, you can modify the field names and field values ​​of the program, and you can also provide the program with interface access information, etc.

This is a mechanism provided in the Go language. We can You can see a lot about the use of reflect in the language public library

For example, the commonly used fmt package, Commonly used json Serialization and deserialization, naturally the gorm library we mentioned earlier also uses reflection

But why do we generally use What about reflection?

According to the ability of reflection, naturally because the interface we provide does not know what the incoming data type will be, the specific data type is only known when the program is running

But when we are coding, we also hope to verify what the type passed in when the program is running (such as serialization of json) and operate on this specific data. At this time, we need to use The ability of reflection

So wherever reflection is used, you can seeinterface{} Isn’t it surprising?

It is precisely because we are not sure what the incoming data type will be, so we designed it as interface{}. If you are not sure about the characteristics and usage of interface, you can check the historical articles. :

First pay attention to the rules of reflection

First pay attention to the three important laws of reflection. After knowing the rules,we follow the rules There will be no problems when playing, only when we don’t know the rules and always trigger the clauses, strange problems will occur

  • Reflection can be used to change the interface Type variables are converted into reflection type objects

  • Reflection can convert reflection type objects into interface type variables
  • The reflection type objects we want to modify at runtime, Then it is required that the value corresponding to this object must be writable

The above three rules are also relatively easy to understand. Do you still remember the pointers in the unsafe package we mentioned before?

We convert our commonly used data types into specified data types in packages (such as unsafe packages, or reflect packages), and then modify the data according to the rules in the packages

Quite equivalent So, by changing the vest, you can perform different operations

Pay attention to use cases and use them flexibly

Generally, we will learn the basic applications first, and then study its principles , study why it can be used in this way, and slowly you will understand more deeply

For Law 1, convert interface type variables into reflection type objects

Actual For the interface type variables mentioned here, we can pass in variables of any data type, such as int, float, string, map, slice, struct , etc.

Reflection type objects can be understood here as reflect.Type and reflect.Value objects in the reflect reflection package, which can be used through the TypeOf# provided in the reflect package. ## and ValueOf functions get

where

reflect.Type is actually an interface, which contains various interfaces that need to be implemented, and it provides information about Type-related information

You can see all the methods of

reflect.Type as shown below, where

  • green is all the data The types are all callable
  • Red is the function type data that can be called
  • Black is Map, array Array, channel Chan, pointer Ptr or slice Slice that can be called
  • The blue is the # that can be called by the structure
    ##Yellow
  • is the channel type call

reflect.Value

Actual The above is a struct. According to this struct, a set of methods are also associated, which stores the data type and specific data. You can see by looking at its data structure<pre class="brush:js;toolbar:false;">type Value struct { typ *rtype ptr unsafe.Pointer flag }</pre>See the unsafe.Pointer here Are you familiar? The bottom layer can naturally convert

unsafe.Pointer

to uintptr, then modify its data and then convert it back. If you are not familiar with Go pointers, you can check this Article:

    #Pointers in GO?
  • Write a simple demo to easily obtain the data type and value of the variable
func main() {   var demoStr string = "now reflect"
   fmt.Println("type:", reflect.TypeOf(demoStr))
   fmt.Println("value:", reflect.ValueOf(demoStr))
}

For the law Second, convert the reflection type object into an interface type variable

We can convert the

reflect.Value

type into our specific data type, because reflect. There are corresponding typ *rtype and ptr unsafe.Pointer in Value . For example, we can pass the interface of

reflect.Value

object () method to deal with

func main() {   var demoStr string = "now reflect"
   fmt.Println("type:", reflect.TypeOf(demoStr))
   fmt.Println("value:", reflect.ValueOf(demoStr))   var res string
   res = reflect.ValueOf(demoStr).Interface().(string)
   fmt.Println("res == ",res)
}

For Law 3, modify the reflection type object

First we read the book In the demo code, the variables passed in

TypeOf

and ValueOf are actually copies. If you want to modify the value in a reflection type object, you need to get the address of the specific variable. Then modify it, The premise is that this variable is writable You can understand it by giving an example

func main() {
   var demoStr string = "now reflect"
   v := reflect.ValueOf(demoStr)
   fmt.Println("is canset ", v.CanSet())
   //v.SetString("hello world")   // 会panic
   }

You can first Call the

reflect.Value

object's CanSet to check whether it is writable. If it is writable, we will write it again. If it is not writable, don't write it, otherwise it will panic

Then the address of the variable passed in can be modified? ?

#There is nothing wrong with the idea of ​​passing in the address, but there is something wrong with the way we set the value, so the above panic situation will also occur

Careful here It can be understood that the reflected object v is naturally unmodifiable. We should find the specific data pointer in

reflect.Value

, then it can be modified. You can use reflect.Value Elem Method

A slightly more complicated one

You may feel so after taking a fancy to the above case A simple case is ok as soon as it is demonstrated, but it crashes as soon as it is used at work. Naturally, it still has not been fully understood, indicating that it has not been digested well. Here is another example from work

A structure has a map in it , the key in the map is string, and the value is []string
The requirement is to access the #th slice of the map key corresponding to the hobby field in the structure for
    sport
  • ##1 elements and modify them to hellolworld
    type RDemo struct {
       Name  string
       Age   int
       Money float32
       Hobby map[string][]string
    }
    
    func main() {
       tmp := &RDemo{
          Name:  "xiaomiong",
          Age:   18,
          Money: 25.6,
          Hobby: map[string][]string{
             "sport": {"basketball", "football"},
             "food":  {"beef"},
          },
       }
    
       v := reflect.ValueOf(tmp).Elem()  // 拿到结构体对象
       h := v.FieldByName("Hobby")    // 拿到 Hobby 对象
       h1 := h.MapKeys()[0]    // 拿到 Hobby 的第 0 个key
       fmt.Println("key1 name == ",h1.Interface().(string))
    
       sli := h.MapIndex(h1)    // 拿到 Hobby 的第 0 个key对应的对象
       str := sli.Index(1)      // 拿到切片的第 1 个对象
       fmt.Println(str.CanSet())
    
       str.SetString("helloworld")
       fmt.Println("tmp == ",tmp)
    }

可以看到上述案例运行之后有时可以运行成功,有时会出现 panic 的情况,相信细心的 xdm 就可以看出来,是因为 map 中的 key 是 无序的导致的,此处也提醒一波,使用 map 的时候要注意这一点

看上述代码,是不是就能够明白咱们使用反射去找到对应的数据类型,然后按照数据类型进行处理数据的过程了呢

有需要的话,可以慢慢的去熟练反射包中涉及的函数,重点是要了解其三个规则,对象转换方式,访问方式,以及数据修改方式

反射原理

那么通过上述案例,可以知道关于反射中数据类型和数据指针对应的值是相当重要的,不同的数据类型能够用哪些函数这个需要注意,否则用错直接就会 panic

TypeOf

来看 TypeOf 的接口中涉及的数据结构

在 reflect 包中 rtype 是非常重要的,Go 中所有的类型都会包含这个结构,所以咱们反射可以应用起来,结构如下

// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
   size       uintptr
   ptrdata    uintptr
   hash       uint32 
   tflag      tflag
   align      uint8
   fieldAlign uint8
   kind       uint8
   equal     func(unsafe.Pointer, unsafe.Pointer) bool
   gcdata    *byte 
   str       nameOff
   ptrToThis typeOff
}

其中可以看到此处的 rtype 的结构保持和 runtime/type.go 一致 ,都是关于数据类型的表示,以及对应的指针,关于这一块的说明和演示可以查看文末的 interface{} 处的内容

ValueOf

ValueOf 的源码中,我们可以看到,重要的是 emptyInterface 结构

// emptyInterface is the header for an interface{} value.type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}复制代码

emptyInterface 结构中有 rtype 类型的指针, word 自然是对应的数据的地址了

reflect.Value 对象中的方法也是非常的多,用起来和上述说到的 reflect.Type 接口中的功能类似

关于源码中涉及到的方法,就不再过多的赘述了,更多的还是需要自己多多实践才能体会的更好

殊不知,此处的 reflect.Value 也是可以转换成 reflect.Type ,可以查看源码中 reflect\value.gofunc (v Value) Type() Type {

其中   reflect.Value  ,reflect.Type ,和任意数据类型 可以相互这样来转换

如下图:

总结

至此,关于反射就聊到这里,一些关于源码的细节并没有详细说,更多的站在一个使用者的角度去看反射需要注意的点

关于反射,大多的人是建议少用,因为是会影响到性能,不过如果不太关注这一点,那么用起来还是非常方便的

高级功能自然也是双刃剑,你用不好就会 panic,如果你期望去使用他,那么就去更多的深入了解和一步一步的吃透他吧

大道至简,反射三定律,活学活用

更多编程相关知识,请访问:编程视频!!

The above is the detailed content of An article explaining reflection in Golang in detail. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete