The concept of reflection in programming languages
In the field of computer science, reflection refers to a class of applications that are self-described and self-controlled. That is to say, such applications use a certain mechanism to realize self-representation and inspection of their own behavior, and can adjust or modify the state and related semantics of the behavior described by the application based on the status and results of their own behavior.
The reflection model for each language is different, and some languages do not support reflection at all. The Golang language implements reflection. The reflection mechanism is to call the object's methods and properties dynamically at runtime. The official reflect package is reflection-related, and it can be used as long as this package is included.
If you add one more sentence, Golang's gRPC is also achieved through reflection.
interface and reflection
Before talking about reflection, let’s take a look at some of Golang’s principles on type design
- Variables include (type, value)
- Understand this and you will know why nil != nil
type includes static type and concrete type. Simply put, static type is the type you see when encoding (such as int, string), concrete type is the type seen by the runtime system.
Whether a type assertion can succeed depends on the variable's concrete type, not the static type. Therefore, a reader variable can also be asserted by the type as a writer if its concrete type also implements the write method.
The reflection next is based on the type. The type of the variable specified by Golang is static (that is, the variables such as int and string, and its type is static type). It is determined when creating the variable. Reflection is mainly related to Golang's interface type (its type is concrete type). Only the interface type has reflection.
In Golang's implementation, each interface variable has a corresponding pair, and the value and type of the actual variable are recorded in the pair:
(value, type)
value is the actual variable value, type is the type of the actual variable. A variable of type interface{} contains 2 pointers, one pointer points to the value type [corresponding to concrete type], and the other pointer points to the actual value [corresponding to value].
For example, create a variable of type * and assign it to an interface variable r:
tty, err := ("/dev/tty", os.O_RDWR, 0) var r r = tty
The following information will be recorded in the pair of interface variable r: (tty, *). This pair is unchanged during the continuous assignment of interface variables. Assign interface variable r to another interface variable w:
var w w = r.()
The pair of interface variable w is the same as the pair of r, both: (tty, *), and even if w is an empty interface type, the pair remains unchanged.
The existence of interface and its pair is the prerequisite for realizing reflection in Golang. Once you understand the pair, it is easier to understand reflection. Reflection is a mechanism used to detect pairs stored inside interface variables (value value; type concrete type) .
Golang's reflection reflect
Basic functions of reflect TypeOf and ValueOf
Since reflection is a mechanism used to detect pairs stored inside interface variables (value; type concrete type) So what kind of way is there in Golang's reflect reflection package that allows us to directly obtain information inside the variable? It provides two types (or two methods) that allow us to easily access the content of interface variables, namely () and (), and see the official explanation
// ValueOf returns a new Value initialized to the concrete value // stored in the interface i. ValueOf(nil) returns the zero func ValueOf(i interface{}) Value {...} Translate:ValueOfUsed to obtain the value of the data in the input parameter interface,If the interface is empty, return0 // 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 {...} Translate:TypeOfUsed to dynamically obtain the type of value in the input parameter interface,If the interface is empty, returnnil
() is to get the type in the pair, and () get the value in the pair. The example is as follows:
package main import ( "fmt" "reflect" ) func main() { var num float64 = 1.2345 ("type: ", (num)) ("value: ", (num)) } Running results: type: float64 value: 1.2345
illustrate
: It directly gives us the type we want, such as float64, int, various points, struct and other real types
: The specific value we want is given directly, such as the specific value of 1.2345, or the value of a structure struct like &{1 "" 25}
That is to say, reflection can convert "interface type variable" to "reflection type object", and reflection type refers to these two types.
Get information about the interface from
After executing (interface), a variable of type "" is obtained. The real content of the interface variable can be obtained through its own Interface() method, and then it can be converted through type judgment to convert it to the original real type. However, we may have known the original type, or we may have unknown the original type. Therefore, the following are two situations to explain.
Known the original type [Captive conversion]
The method of converting a type to its corresponding type after a known type is as follows: Directly through the Interface method and then cast, as follows:
realValue := ().(Known types)
Examples are as follows:
package main import ( "fmt" "reflect" ) func main() { var num float64 = 1.2345 pointer := (&num) value := (num) // It can be understood as "cast conversion", but when it is necessary to pay attention, if the conversion type does not fully match, then panic directly // Golang has very strict requirements on types, and the types must be fully compliant. //The following two are: *float64 and the other is float64. If you are confused, it will panic convertPointer := ().(*float64) convertValue := ().(float64) (convertPointer) (convertValue) } Running results: 0xc42000e238 1.2345
illustrate
- When converting, if the converted type does not fully meet, then panic will be directly, and the type requirements are very strict!
- When converting, distinguish between pointers or fingers
- In other words, reflection can convert "reflective type object" to "interface type variable" again
Unknown original type [traversal detection of its filed]
In many cases, we may not know the specific type, so what should we do at this time? We need to traverse and detect its Filed to know, the example is as follows:
package main import ( "fmt" "reflect" ) type User struct { Id int Name string Age int } func (u User) ReflectCallFunc() { (" ReflectCallFunc") } func main() { user := User{1, "", 25} DoFiledAndMethod(user) } // Get any parameters through the interface and then reveal them one by onefunc DoFiledAndMethod(input interface{}) { getType := (input) ("get Type is :", ()) getValue := (input) ("get all Fields is:", getValue) // Get method field // 1. First get the interface, and then traverse through NumField // 2. Get the Field through the Field // 3. Finally, get the corresponding value through Field's Interface() for i := 0; i < (); i++ { field := (i) value := (i).Interface() ("%s: %v = %v\n", , , value) } // Get method // 1. First get the interface, and then traverse through .NumMethod for i := 0; i < (); i++ { m := (i) ("%s: %v\n", , ) } } Running results: get Type is : User get all Fields is: {1 25} Id: int = 1 Name: string = Age: int = 25 ReflectCallFunc: func()
illustrate
Through the running result, we can know that the specific variables and their types of the interface of unknown type are:
- First get the interface, and then traverse through NumField
- Then obtain the Field through the Field
- Finally, the corresponding value is obtained through Field's Interface()
Through the running result, we can know that the steps to obtain the method (function) of an unknown type of interface are:
- First get the interface, and then traverse through NumMethod
- Then use the Method to obtain the corresponding real method (function)
- Finally, learn the specific method name of the result by taking its Name and Type
- In other words, reflection can convert "reflective type object" to "interface type variable" again
- Nestings of struct or struct are the same way to judge and handle
By setting the value of the actual variable
It is obtained through (X). Only when X is a pointer can you modify the value of the actual variable X, that is, to modify the object of reflection type, you must ensure that its value is "addressable".
Examples are as follows:
package main import ( "fmt" "reflect" ) func main() { var num float64 = 1.2345 ("old value of pointer:", num) // By obtaining num, note that the parameter must be a pointer to modify its value pointer := (&num) newValue := () ("type of pointer:", ()) ("settability of pointer:", ()) // Reassign value (77) ("new value of pointer:", num) //////////////////// // What if the parameter is not a pointer? pointer = (num) //newValue = () // If it is not a pointer, panic directly here, "panic: reflect: call of on float64 Value"} Running results: old value of pointer: 1.2345 type of pointer: float64 settability of pointer: true new value of pointer: 77
illustrate
- The parameter that needs to be passed is the pointer * float64, and then the value pointed to can be obtained through ().Be sure to be a pointer。
- If the passed parameter is not a pointer, but a variable,
- Get the object corresponding to the original value through Elem and then directly panic
- Check whether it is possible to set return false by the CanSet method
- () indicates whether its value can be reset. If the output is true, it can be modified. Otherwise, it cannot be modified. After the modification is completed, it will be printed and found that it has really been modified.
- () means to obtain the reflected object corresponding to the original value. Only the original object can be modified. The current reflective object cannot be modified.
- That is to say, if you want to modify the reflection type object, its value must be "addressable" [the corresponding pointer is passed in, and the reflection object corresponding to the original value must be obtained through the Elem method]
- Nestings of struct or struct are the same way to judge and handle
By calling the method
This is a high-level usage. We only talked about the usage of several reflections of types and variables, including how to get their value, their type, and if you reset the new value. However, in engineering applications, another commonly used and advanced usage is to call the method [function] through reflect. For example, when we want to do framework engineering, we need to extend the method at will, or the user can customize the method. So what means do we use to extend it so that users can customize it? The key point is that the user's customization method is unknown, so we can solve it through reflect.
Examples are as follows:
package main import ( "fmt" "reflect" ) type User struct { Id int Name string Age int } func (u User) ReflectCallFuncHasArgs(name string, age int) { ("ReflectCallFuncHasArgs name: ", name, ", age:", age, "and origal :", ) } func (u User) ReflectCallFuncNoArgs() { ("ReflectCallFuncNoArgs") } // How to make method calls through reflection?// It could have been called directly, but if you want to pass reflection, you must first register the method, that is, MethodByName, and then mobilize it through reflection func main() { user := User{1, "", 25} // 1. To call the corresponding method through reflection, you must first obtain it through (interface) and obtain the "reflective type object" before you can do the next step of processing. getValue := (user) // Make sure to specify the parameter as the correct method name // 2. First look at the calling method with parameters methodValue := ("ReflectCallFuncHasArgs") args := []{("wudebao"), (30)} (args) // Make sure to specify the parameter as the correct method name // 3. Look at the calling method without parameters methodValue = ("ReflectCallFuncNoArgs") args = make([], 0) (args) } Running results: ReflectCallFuncHasArgs name: wudebao , age: 30 and origal : ReflectCallFuncNoArgs
illustrate
To call the corresponding method through reflection, you must first obtain it through (interface) and obtain the "reflective type object" before you can do the next step of processing
This .MethodByName needs to specify the exact and true method name. If the error is wrong, it will be panic directly. MethodByName returns the name of the method corresponding to the value of a function.
[], this is the parameter of the method that needs to be called in the end. It can be no or one or more, and it depends on the actual parameters.
The Call method, this method will eventually call the real method, and the parameters must be consistent. If 'Kind is not a method, then it will be panic directly.
It could have been called directly, but if you want to pass reflection, you must first register the method, that is, MethodByName, and then call it through reflection
Golang's reflection reflect performance
Golang's reflection is very slow, which is related to its API design. In Java, we usually use reflection in this way.
Field field = ("hello"); (obj1); (obj2);
The type of reflection object obtained is . It is reusable. As long as you pass in a different obj, you can get the corresponding field on this obj.
But Golang's reflection is not designed like this:
type_ := (obj) field, _ := type_.FieldByName("hello")
The field object taken here is of type , but it has no way to get the value on the corresponding object. If you want to get a value, you have to use another set of reflections to the object instead of type
type_ := (obj) fieldValue := type_.FieldByName("hello")
The fieldValue type taken here is , it is a specific value, not a reusable reflection object. Each reflection requires malloc structure, and also involves GC.
summary
There are two main reasons for Golang reflect slow
It involves memory allocation and subsequent GC;
There are a lot of enumerations in the reflect implementation, that is, for loops, such as types.
Summarize
The above details the various functions and usages of Golang's reflection reflect, all with corresponding examples. I believe that it can be practiced in engineering applications. To summarize it, it is:
-
Reflection can greatly improve program flexibility, giving interface{} greater room for play
- Reflection must be combined with interface to play
- If the type of a variable is concrete type (that is, interface variable) there is a reflection.
-
Reflection can convert "interface type variable" to "reflection type object"
- Reflection uses TypeOf and ValueOf functions to get target object information from the interface
-
Reflection can convert "reflection type object" to "interface type variable
- ().(known type)
- The traversed Field gets its Field
-
Reflection can modify the reflection type object, but its value must be "addressable"
- If you want to use reflection to modify the object state, the premise is settable, that is, pointer-interface
Methods can be called "dynamic" through reflection
Because Golang itself does not support templates, in the past scenarios where templates were needed, reflection was often needed to implement them.
Reference link
The Go Blog: Actually, it’s enough to read the official instructions!
Official reflect-Kind
The Three Laws of Reflection in Go Language
Go basic learning five interface interface and reflection reflection
Improve the reflection performance of golang
This is the end of this article about deeply understanding Golang's reflection reflect example. For more related Golang reflection reflex content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!