Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to analyze the reflection reflect of Golang

2025-01-15 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/01 Report--

This article shows you how to analyze the reflective reflect of Golang. The content is concise and easy to understand. It will definitely brighten your eyes. I hope you can get something through the detailed introduction of this article.

The concept of reflection in programming languages

In the field of computer science, reflection refers to a class of applications that can be self-described and controlled. In other words, this kind of application uses a certain mechanism to self-representation and examination its own behavior, and can adjust or modify the state and related semantics of the described behavior according to the state and result of its own behavior.

The reflection model for each language is different, and some languages do not support reflection at all. The Golang language implements reflection, and the reflection mechanism is to dynamically call the methods and properties of the object at run time. The official reflect package is reflection-related and can be used as long as it contains this package.

By the way, Golang's gRPC is also implemented through reflection.

Interface and reflection

Before we talk about reflection, let's take a look at some of Golang's principles about type design.

The variable includes two parts (type, value)

If you understand this, you will know why nil! = nil

Type includes static type and concrete type. To put it simply, static type is the type you see when coding (such as int, string), and concrete type is the type seen by the runtime system

The success of type assertions depends on the concrete type of the variable, not static type. Therefore, a reader variable can also be asserted as writer by type if its concrete type also implements the write method.

The reflection that we are going to talk about next is based on the type. The type of variables of the specified type of Golang is static (that is, variables such as int and string are specified, and its type is static type). It has been determined when the variable is created that reflection is mainly related to the interface type of Golang (its type is concrete type), and only the interface type has reflection.

In the implementation of Golang, each interface variable has a corresponding pair,pair that records the value and type of the actual variable:

(value, type)

Value is the actual variable value, and type is the type of the actual variable. A variable of type interface {} contains two pointers, one to the type of the value [corresponding to concrete type] and the other to the actual value [corresponding to value].

For example, create a variable of type * os.File and assign it to an interface variable r:

Tty, err: = os.OpenFile ("/ dev/tty", os.O_RDWR, 0) var r io.Readerr = tty

The following information is recorded in the pair of the interface variable r: (tty, * os.File), this pair is constant during the continuous assignment of the interface variable, and assign the interface variable r to another interface variable w:

Var w io.Writerw = r. (io.Writer)

The pair of the interface variable w is the same as the pair of r, both are: (tty, * os.File), and pair is unchanged even if w is an empty interface type.

The existence of interface and its pair is the premise of realizing reflection in Golang. If you understand pair, it is easier to understand reflection. Reflection is a mechanism used to detect pair pairs stored inside interface variables (value value; type concrete type).

Basic functions of Golang reflection reflectreflect TypeOf and ValueOf

Since reflection is a mechanism for detecting pair pairs stored inside interface variables (value value; type concrete type). So what is the way for us to get the information inside the variable directly in Golang's reflect reflection package? It provides two types (or two methods) that allow us to easily access the contents of interface variables, reflect.ValueOf () and reflect.TypeOf (), 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: ValueOf is used to get the value of the data in the input parameter interface. TypeOf returns nil.func TypeOf (I interface {}) Type {...}: the type used by TypeOf to dynamically obtain the value in the input parameter interface, and return nil if the interface is empty.

Reflect.TypeOf () is used to get type,reflect.ValueOf in pair and value in pair. The example is as follows:

Package mainimport ("fmt"reflect") func main () {var num float64 = 1.2345 fmt.Println ("type:", reflect.TypeOf (num)) fmt.Println ("value:", reflect.ValueOf (num))} run result: type: float64value: 1.2345 description

Reflect.TypeOf: directly give us the type types we want, such as float64, int, various pointer, struct, and so on.

Reflect.ValueOf: directly give us the specific value we want, such as the specific value of 1.2345, or the value of a structure like & {1 "Allen.Wu" 25} struct.

This means that reflection can convert "interface type variables" into "reflection type objects", and reflection types refer to reflect.Type and reflect.Value.

Get the information of interface interface from relfect.Value

When reflect.ValueOf (interface) is executed, you get a variable of type "relfect.Value", the real content of the interface variable can be obtained through its own Interface () method, and then it can be converted to the original real type by type judgment. However, we may know the original type, or it may be unknown, so there are two cases below.

Known original type [cast]

The practice of converting a known type to its corresponding type is as follows, directly through the Interface method and then casting, as follows:

RealValue: = value.Interface (). (known types)

Examples are as follows:

Package mainimport ("fmt"reflect") func main () {var num float64 = 1.2345 pointer: = reflect.ValueOf (& num) value: = reflect.ValueOf (num) / / can be understood as "cast", but it should be noted that when converting, if the converted type does not fully match, then the direct panic / / Golang is very strict with the type. The type must match the following two completely, one is * float64, the other is float64. If confused, it will panic convertPointer: = pointer.Interface (). (* float64) convertValue: = value.Interface (). (float64) fmt.Println (convertPointer) fmt.Println (convertValue)} run result: 0xc42000e2381.2345 description

When converting, if the type of conversion does not exactly match, then directly panic, the type requirements are very strict!

When converting, it is necessary to distinguish between a pointer and a pointer.

That is to say, reflection can reconvert "reflection type object" to "interface type variable".

Unknown original type [traversal probing its Filed]

In many cases, we may not know its specific type, so what should we do at this time? We need to traverse and probe its Filed to know that the example is as follows:

Package mainimport ("fmt"reflect") type User struct {Id int Name string Age int} func (u User) ReflectCallFunc () {fmt.Println ("Allen.Wu ReflectCallFunc")} func main () {user: = User {1, "Allen.Wu", 25} DoFiledAndMethod (user)} / / obtain arbitrary parameters through the interface Then reveal func DoFiledAndMethod (input interface {}) {getType: = reflect.TypeOf (input) fmt.Println ("getType is:", getType.Name ()) getValue: = reflect.ValueOf (input) fmt.Println ("get all Fields is:", getValue) / / get the method field / / 1. First get the reflect.Type of interface, and then traverse / / 2 through NumField. Then get its Field / / 3 through reflect.Type 's Field. Finally, through Interface () of Field, we get the corresponding value for i: = 0; I < getType.NumField (); iTunes + {field: = getType.Field (I) value: = getValue.Field (I). Interface () fmt.Printf ("% s:% v =% v\ n", field.Name, field.Type, value)} / / get method / / 1. First get the reflect.Type of interface, and then traverse for I: = 0; I < getType.NumMethod () through .NumMethod Fmt.Printf + {m: = getType.Method (I) fmt.Printf ("% s:% v\ n", m.Name, m.Type)}} run result: getType is: Userget all Fields is: {1 Allen.Wu 25} Id: int = 1Name: string = Allen.WuAge: int = 25ReflectCallFunc: func (main.User) description

From the running results, we can see that the steps to obtain the specific variables and types of unknown types of interface are as follows:

First get the reflect.Type of interface, and then traverse through NumField

Then get its Field through reflect.Type 's Field

Finally, the corresponding value is obtained through the Interface () of Field.

From the running results, we can know that the steps to get the ownership method (function) of an unknown type of interface are:

First get the reflect.Type of interface, and then traverse through NumMethod

Then obtain the corresponding real method (function) through the Method of reflect.Type respectively

Finally, the specific method name is obtained by taking the Name and Type of the result.

That is to say, reflection can reconvert "reflection type object" to "interface type variable".

The nesting of struct or struct is handled in the same way.

Set the value of the actual variable through reflect.Value

Reflect.Value is obtained through reflect.ValueOf (X), and only when X is a pointer can the value of the actual variable X be modified through reflec.Value, that is, if you want to modify an object of reflection type, you must ensure that its value is "addressable".

Examples are as follows:

Package mainimport ("fmt"reflect") func main () {var num float64 = 1.2345 fmt.Println ("old value of pointer:", num) / / get the reflect.Value in num through reflect.ValueOf, note Parameter must be a pointer to modify its value pointer: = reflect.ValueOf (& num) newValue: = pointer.Elem () fmt.Println ("type of pointer:", newValue.Type ()) fmt.Println ("settability of pointer:", newValue.CanSet ()) / / reassign newValue.SetFloat (77) fmt.Println ("new value of pointer:" Num) / if the parameter of reflect.ValueOf is not a pointer What's gonna happen? Pointer = reflect.ValueOf (num) / / newValue = pointer.Elem () / / if it is not a pointer, directly panic, "panic: reflect: call of reflect.Value.Elem on float64 Value"} run the result: old value of pointer: 1.2345type of pointer: float64settability of pointer: truenew value of pointer: 77 description

The parameter you need to pass in is the pointer * float64, and then you can get the Value you point to through pointer.Elem (). Be sure to use the pointer.

If the parameter passed in is not a pointer but a variable, then

If you get the object corresponding to the original value through Elem, you will directly panic.

Use CanSet method to query whether the return false can be set.

NewValue.CantSet () indicates whether the value can be reset, and if the output is true, it can be modified, otherwise it cannot be modified, and then print to find that it has really been modified.

Reflect.Value.Elem () indicates the reflection object corresponding to the original value. Only the original object can be modified. The current reflection object cannot be modified.

That is, if you want to modify a reflection type object, its value must be "addressable" [the corresponding pointer is passed in, and the reflection object corresponding to the original value is obtained through the Elem method]

The nesting of struct or struct is handled in the same way.

Call the method through reflect.ValueOf

This is an advanced usage. We only talked about the use of several reflections on types and variables, including how to get its value, its type, and if you reset the new value. But in engineering applications, another common and advanced usage is to call methods [functions] through reflect. For example, when we want to do a framework project, we need to expand the method at will, or the user can customize the method, so what means do we use to expand so that users can customize it? The key point is that the user's custom method is unknown, so we can do it through reflect

Examples are as follows:

Package mainimport ("fmt"reflect") type User struct {Id int Name string Age int} func (u User) ReflectCallFuncHasArgs (name string, age int) {fmt.Println ("ReflectCallFuncHasArgs name:", name, ", age:", age, "and origal User.Name:" U.Name)} func (u User) ReflectCallFuncNoArgs () {fmt.Println ("ReflectCallFuncNoArgs")} / / how do I call a method through reflection? / / could have been called directly with u.ReflectCallFuncXXX, but if you want to pass reflection, first register the method, that is, MethodByName, and then transfer mv.Callfunc main () {user: = User {1, "Allen.Wu", 25} / / 1 through reflection. To call the corresponding method through reflection, you must first get the reflect.Value through reflect.ValueOf (interface), and get the "reflection type object" before you can do the next step: getValue: = reflect.ValueOf (user) / / be sure to specify the correct method name / / 2. First look at the calling method methodValue: = getValue.MethodByName ("ReflectCallFuncHasArgs") args: = [] reflect.Value {reflect.ValueOf ("wudebao"), reflect.ValueOf (30)} methodValue.Call (args) / / be sure to specify the parameter to the correct method name / / 3. Take a look at the no-parameter calling method methodValue = getValue.MethodByName ("ReflectCallFuncNoArgs") args = make ([] reflect.Value, 0) methodValue.Call (args)} running result: ReflectCallFuncHasArgs name: wudebao, age: 30 and origal User.Name: Allen.WuReflectCallFuncNoArgs description

To call the corresponding method through reflection, you must first get the reflect.Value through reflect.ValueOf (interface), and get the "reflection type object" before you can do the next step.

Reflect.Value.MethodByName the .MethodByName, you need to specify an accurate and real method name. If an error occurs, panic,MethodByName will directly return the name of the reflect.Value method corresponding to a function value.

[] reflect.Value, which is the parameter of the final method that needs to be called, can be none or one or more, depending on the actual parameters.

Reflect.Value 's Call method, which will eventually call the real method, the parameters must be consistent, if reflect.Value'Kind is not a method, then it will be directly panic.

It could have been called directly with u.ReflectCallFuncXXX, but if you want to go through reflection, first register the method, that is, MethodByName, and then call methodValue.Call through reflection

Reflective reflect performance of Golang

The reflection of Golang is slow, which is related to its API design. In java, we usually use reflection to do this.

Field field = clazz.getField ("hello"); field.get (obj1); field.get (obj2)

The reflection object type obtained by this is java.lang.reflect.Field. It can be reused. As long as you pass in a different obj, you can get the corresponding field on this obj.

But the reflection of Golang is not designed like this:

Type_: = reflect.TypeOf (obj) field, _: = type_.FieldByName ("hello")

The field object taken here is of type reflect.StructField, but it has no way to get the value on the corresponding object. If you want to take a value, you have to use a different set of reflections on object rather than type.

Type_: = reflect.ValueOf (obj) fieldValue: = type_.FieldByName ("hello")

The fieldValue type taken here is reflect.Value, which is a specific value, not a reusable reflection object. Each reflection requires the reflect.Value structure malloc, and also involves GC.

Summary

There are two main reasons why Golang reflect is slow.

Involves memory allocation and subsequent GC

There are a lot of enumerations in the reflect implementation, that is, for loops, such as types.

Summary

The above details the various features and uses of Golang's reflective reflect, all of which are accompanied by corresponding examples. It is believed that they can be put into practice in engineering applications. To sum up, they are:

Reflection can greatly improve the flexibility of the program, giving interface {} more room to play.

Reflex must be combined with interface to play well.

The type of a variable is reflected only if it is a concrete type (that is, an interface variable).

Reflection can convert an interface type variable to a reflection type object

Reflection uses TypeOf and ValueOf functions to obtain target object information from the interface

Reflection can convert a reflection type object to an interface type variable

Reflect.value.Interface (). (known types)

Traversing the Field of reflect.Type to get its Field

Reflection can modify a reflection type object, but its value must be "addressable"

You want to use reflection to modify the state of an object, provided that interface.data is settable, that is, pointer-interface

Methods can be called "dynamically" through reflection

Because Golang itself does not support templates, it is often necessary to use reflect in scenarios where templates are needed in the past

The above is how to analyze the reflective reflect of Golang. Have you learned any knowledge or skills? If you want to learn more skills or enrich your knowledge reserve, you are welcome to follow the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report