Location>code7788 >text

An in-depth look at Golang Reflection: Functions and Principles and Applications

Popularity:821 ℃/2024-07-21 19:37:01

Hi dear friends, I'm K. Today we're going to talk about Golang Reflection. Today, we're going to talk about Golang reflection.

Go provides reflection for generality reasons. With the help of reflection, we can implement more generic functions, pass in arbitrary parameters, and within the function dynamically call the methods of the parameter object and access its properties through reflection. For example, the following bridge interface supports the flexibility of calling arbitrary functions by dynamically calling the function pointed to by funcPtr via reflection at runtime based on the argument funcPtr passed in.

func bridge(funcPtr interface{}, args ...interface{})

Again, ORM framework functions, in order to support the handling of arbitrary parameter objects, dynamically assign values to parameter objects at runtime based on incoming parameters through reflection.

type User struct {
        Name string
        Age  int32
}
user := User{}
(&user)

In this article, we will delve into the features and principles of the Golang reflection package reflect. At the same time, we learn something for practical application on one hand and for utilitarian interview purposes on the other. So, this article will also introduce you to typical applications of reflection as well as high-frequency interview questions.

1 Key functions

The reflect package provides more features, but the core function is to convert the interface variable into a reflection type object and, through the reflection type object provides functionality, access to the real object's methods and properties. This article only introduces three core functions, other methods can look at the official documents.

1. Object type conversion. Through the TypeOf and ValueOf methods, you can convert interface variables to reflect the type of object Type and Value. through the Interface method, you can convert Value back to interface variables.

type any = interface{}

// Getting Reflection Objects
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i any) Type

// Getting Reflection Objects
// ValueOf returns a new Value initialized to the concrete value stored in the interface i.
// ValueOf(nil) returns the zero Value.
func ValueOf(i any) Value

// Reflected objects are converted back tointerface
func (v Value) Interface() (i any)

Examples are shown below:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    age := 18
    ("type: ", (age)) // exportstype: int
    value := (age)
    ("value: ", value) // exportsvalue: 18

    (().(int)) // exports18
}

2. Variable value setting. The value of the real variable can be set through the SetXX related method. It is obtained through (x), and the value of the real variable x can be changed by modifying it only if x is a pointer.

// Set assigns x to the value v. It panics if  returns false. 
// As in Go, x's value must be assignable to v's type and must not be derived from an unexported field.
func (v Value) Set(x Value)
func (v Value) SetInt(x int64)
...

// Elem returns the value that the interface v contains or that the pointer v points to. It panics if v's Kind is not Interface or Pointer. It returns the zero Value if v is nil.
func (v Value) Elem() Value

Examples are shown below:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    age := 18
    // The value of age can be modified by getting the
    // Parameters must be pointers in order to modify their values.
    pointerValue := (&age)
    // The combination of the Elem and Set methods is equivalent to assigning a *p= value to the variable pointed to by the pointer.
    newValue := ()
    (28)
    (age) // The value is changed, outputting 28

    // The argument is not a pointer
    pointerValue = (age)
    newValue = () // if not pointer, direct panic: reflect: call of on int Value
}

3. method call. Method and MethodByName can get the specific method , Call can realize the method call .

// Method returns a function value corresponding to v's i'th method. 
// The arguments to a Call on the returned function should not include a receiver; 
// the returned function will always use v as the receiver. Method panics if i is out of range or if v is a nil interface value.
func (v Value) Method(i int) Value

// MethodByName returns a function value corresponding to the method of v with the given name.
func (v Value) MethodByName(name string) Value

// Call calls the function v with the input arguments in. For example, if len(in) == 3, (in) represents the Go call v(in[0], in[1], in[2]). 
// Call panics if v's Kind is not Func. It returns the output results as Values
func (v Value) Call(in []Value) []Value

Examples are shown below:

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Age int
}

func (u User) ReflectCallFunc(name string) {
    ("age %d ,name %+v\n", , name)
}

func main() {
    user := User{18}

    // 1. pass (a bill or inspection etc)(interface)to get the
    getValue := (user)

    methodValue := ("ReflectCallFunc")
    args := []{("kelder brother")}
    // 2. pass (a bill or inspection etc)Callinvoke a method
    (args) // exportsage 18 ,name kelder brother
}

2 Principles

The Go language reflection is built on top of the Go type system and interface design, so before chatting about the principles of the REFLECT package, it is necessary to mention Go's type and interface underlying design.

2.1 Static and dynamic types

In Go, every variable determines a static type at compile time. A static type is the type of the variable at the time it is declared. For example, the following variable, i, has the static type interface

var i interface{}

The so-called concrete type refers to the real type of a variable that is only visible to the system when the program is running. For example, the following variable i, static type is interface, but the real type is int

var i interface{}   

i = 18 

2.2 Interface Underlying Design

For any variable whose static type is interface, Go runtime stores the value and dynamic type of the variable. For example, the following variable, age, stores the value and dynamic type (18, int)

var age interface{}
age = 18

2.3 Principle of reflect

reflect is based on the interface implementation. Reflect objects are constructed through the dynamic types and data of the interface's underlying data structures.

Get the dynamic type of the underlying interface to construct the object. With Type, you can get information about the methods, fields, etc. that the variable contains.

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)((&i)) // efacebecause ofinterfacesubstructure
    return toType() // just likeinterfaceUnderlying dynamic types
}

func toType(t *rtype) Type {
    if t == nil {
        return nil
    }
    return t
}

Get the underlying Type and data of the interface, encapsulated into an object.

type Value struct {
    // typ holds the type of the value represented by a Value.
    typ *rtype // dynamic type

    // Pointer-valued data or, if flagIndir is set, pointer to data.
    // Valid when either flagIndir is set or () is true.
    ptr // data pointer

    // flag holds metadata about the value.
    // The lowest bits are flag bits:
    // - flagStickyRO: obtained via unexported not embedded field, so read-only
    // - flagEmbedRO: obtained via unexported embedded field, so read-only
    // - flagIndir: val holds a pointer to the data
    // - flagAddr: is true (implies flagIndir)
    // - flagMethod: v is a method value.
    // The next five bits give the Kind of the value.
    // This repeats () except for method values.
    // The remaining 23+ bits give a method number for method values.
    // If () != Func, code can assume that flagMethod is unset.
    // If ifaceIndir(typ), code can assume that flagIndir is set.
    flag // marker position,Used to mark thisvalueIs the method、Whether it's a pointer, etc.

}

type flag uintptr

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }
    return unpackEface(i)
}

// unpackEface converts the empty interface i to a Value.
func unpackEface(i interface{}) Value {
    // interfacesubstructure
    e := (*emptyInterface)((&i))
    // NOTE: don't read until we know whether it is really a pointer or not.
    // dynamic type
    t :=
    if t == nil {
        return Value{}
    }
    // marker position,Used to mark thisvalueIs the method、Whether it's a pointer, etc.
    f := flag(())
    if ifaceIndir(t) {
        f |= flagIndir
    }
    return Value{t, , f} // ttypology,data-driven,
}

3 Applications

There are two common application scenarios for reflection at work:

1. Do not know which function is called by the interface, according to the incoming parameters at runtime through reflection calls. For example, the following bridge pattern:

package main

import (
    "fmt"
    "reflect"
)

// Called via reflection within a functionfuncPtr
func bridge(funcPtr interface{}, args ...interface{}) {
    n := len(args)
    inValue := make([], n)
    for i := 0; i < n; i++ {
        inValue[i] = (args[i])
    }
    function := (funcPtr)
    (inValue)
}

func call1(v1 int, v2 int) {
    (v1, v2)
}
func call2(v1 int, v2 int, s string) {
    (v1, v2, s)
}
func main() {
    bridge(call1, 1, 2) // exports1 2
    bridge(call2, 1, 2, "test") // exports1 2 test
}

2. Do not know the type of parameters passed into the function, the function needs to run-time processing of any parameter object, this need to reflect the structure of the object. Typical application scenarios are ORM, orm example is as follows:

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name string
    Age int32
int32 }

func FindOne(x interface{}) {
    sv := (x)
    sv = ()
    // For orm, change it to look it up from the db and set it in via reflection
    ("Name").SetString("Brother k")
    ("Age").SetInt(18)
}

func main() {
    user := &User{}
    FindOne(user)
    (*user) // output {k brother 18}
}

4 High-frequency interview questions

(Reflection package) How to get the field tag?

Get tag via reflection package. steps are as follows.

  1. By generating a reflection object

  2. By getting the Field

  3. Accessing tags via Field

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name string `json:"name" otherTag:"name"`
    age string `json:"age"`
}

func main() {
    user := User{}
    userType := (user)
    field := (0)
    (("json"), ("otherTag")) // exportsname name

    field = (1)
    (("json")) // exportsage
}


2. Why can't the json package export tags for private variables?

As you can see from the example in 1, reflection has access to the tag of the private variable age. the reason the json package can't export private variables is because of the implementation of the json package, which skips the tag of the private variables.

func typeFields(t ) structFields {
    // Scan for fields to include.
    for i := 0; i < (); i++ {
        sf := (i)
        // Non-exporting members(private variable),neglecttag
        if !() {
            // Ignore unexported non-embedded fields.
            continue
        }
        tag := ("json")
        if tag == "-" {
            continue
        }          
    }
}

When used in a package, can a variable in a structure be converted to a field in json without a tag?

  1. If it is a private member, it can't be converted because the json package will ignore the tag information of the private member. For example, in the demo below, both a and b in the User structure cannot be json serialized.

  2. If it is a public member.

  • Without tag, it can be converted to field in json normally, and the key of json is consistent with the field name in the structure. For example, in the following demo, after the serialization of C in User, the key and the structure field name are consistent with C.
  • With tag added, when converting from struct to json, the key of json is the same as the value of tag. For example, in the following demo, the D in User is d after serialization.
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    a string // lowercase Nonetag
    b string `json:"b"` //lowercase (letters)+tag
    C string //capital NONEtag
    D string `json:"d"` //banker's anti-fraud numerals+tag
}

func main() {
    u := User{
        a: "1",
        b: "2",
        C: "3",
        D: "4",
    }
    ("%+v\n", u) // exports{a:1 b:2 C:3 D:4}
    jsonInfo, _ := (u)
    ("%+v\n", string(jsonInfo)) // exports{"C":"3","d":"4"}
}