Reflection of 09Go language foundation
Internal mechanism of variables
Variables in Go language are divided into two parts:
- Type information: predefined meta information.
- Value information: it can change dynamically during program operation.
Reflection introduction
Reflection refers to the ability to access and modify the program itself during the running period of the program. When the program is compiled, the variable is converted to the memory address, and the variable name will not be written to the executable part by the compiler. While running the program, the program cannot get its own information.
Languages that support reflection can integrate the reflection information of variables, such as field name, type information and structure information, into the executable file during program compilation, and provide the program with an interface to access the reflection information, so that the reflection information of types can be obtained during program run-time and can be modified.
The Go program uses the reflect package to access the reflection information of the program at run time.
An empty interface can store variables of any type. How do we know what data this empty interface holds? Reflection is to dynamically obtain the type information and value information of a variable at runtime.
reflect package
In the reflection mechanism of Go language, any interface Value is composed of a specific Type and a specific Type Value (we introduced related concepts in the previous interface blog). In the Go language, the functions related to reflection are provided by the built-in reflect package. Any interface Value can be understood as composed of reflect.Type and reflect.Value in reflection, and the reflect package provides two functions: reflect.TypeOf and reflect.ValueOf to obtain the Value and Type of any object.
TypeOf
In Go language, use the reflect.TypeOf() function to obtain the type object (reflect.Type) of any value, and the program can access the type information of any value through the type object.
package main import ( "fmt" "reflect" ) func reflectType(x interface{}) { v := reflect.TypeOf(x) fmt.Printf("type:%v\n", v) } func main() { var a float32 = 3.14 reflectType(a) // type:float32 var b int64 = 100 reflectType(b) // type:int64 }
type name and type kind
In reflection, there are also two types of types: type and Kind. In Go language, we can use the type keyword to construct many user-defined types, and Kind refers to the underlying type. However, in reflection, when it is necessary to distinguish between large types such as pointers and structures, Kind will be used. For example, we define two pointer types and two struct types, and view their types and types through reflection.
package main import ( "fmt" "reflect" ) type myInt int64 func reflectType(x interface{}) { t := reflect.TypeOf(x) fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind()) } func main() { var a *float32 // Pointer var b myInt // Custom type var c rune // Type alias reflectType(a) // type: kind:ptr reflectType(b) // type:myInt kind:int64 reflectType(c) // type:int32 kind:int32 type person struct { name string age int } type book struct{ title string } var d = person{ name: "Shahe little prince", age: 18, } var e = book{title: "<Learn from the little prince Go Language"} reflectType(d) // type:person kind:struct reflectType(e) // type:book kind:struct }
In the reflection of Go language, for variables of types such as array, slice, Map and pointer, their. Name() returns null.
The Kind types defined in the reflect package are as follows:
type Kind uint const ( Invalid Kind = iota // Illegal type Bool // Boolean Int // signed int Int8 // Signed 8-bit integer Int16 // Signed 16 bit integer Int32 // Signed 32-bit integer Int64 // Signed 64 bit integer Uint // unsigned int Uint8 // Unsigned 8-bit integer Uint16 // Unsigned 16 bit integer Uint32 // Unsigned 32-bit integer Uint64 // Unsigned 64 bit integer Uintptr // Pointer Float32 // Single-precision floating-point Float64 // Double precision floating point number Complex64 // 64 bit complex type Complex128 // 128 bit complex type Array // array Chan // passageway Func // function Interface // Interface Map // mapping Ptr // Pointer Slice // section String // character string Struct // structural morphology UnsafePointer // Bottom pointer )
ValueOf
reflect.ValueOf() returns the type reflect. Value, which contains the value information of the original value. The reflect.Value and the original value can be converted to each other.
The method to obtain the original value provided by the reflect.Value type is as follows:
method | explain |
---|---|
Interface() interface {} | Returns the value as interface {} type, which can be converted to the specified type through type assertion |
Int() int64 | Returns a value as an int, which can be returned by all signed integers |
Uint() uint64 | Returns the value as uint, which is how all unsigned integers can be returned |
Float() float64 | Returns the value as a double (float64) type. All floating-point numbers (float32, float64) can be returned in this way |
Bool() bool | Returns the value as bool |
Bytes() []bytes | Returns the value as a byte array [] bytes |
String() string | Returns the value as a string |
Get value by reflection
func reflectValue(x interface{}) { v := reflect.ValueOf(x) k := v.Kind() switch k { case reflect.Int64: // v.Int() gets the original value of the integer from the reflection, and then casts the type through int64() fmt.Printf("type is int64, value is %d\n", int64(v.Int())) case reflect.Float32: // v.Float() gets the original value of the floating-point type from the reflection, and then casts the type through float32() fmt.Printf("type is float32, value is %f\n", float32(v.Float())) case reflect.Float64: // v.Float() gets the original value of the floating-point type from the reflection, and then casts the type through float64() fmt.Printf("type is float64, value is %f\n", float64(v.Float())) } } func main() { var a float32 = 3.14 var b int64 = 100 reflectValue(a) // type is float32, value is 3.140000 reflectValue(b) // type is int64, value is 100 // Converts the original value of type int to type reflect.Value c := reflect.ValueOf(10) fmt.Printf("type c :%T\n", c) // type c :reflect.Value }
Set the value of the variable by reflection
If you want to modify the value of a variable through reflection in a function, you need to note that the function parameters pass a copy of the value. You must pass the variable address to modify the variable value. In reflection, a proprietary Elem() method is used to obtain the value corresponding to the pointer.
package main import ( "fmt" "reflect" ) func reflectSetValue1(x interface{}) { v := reflect.ValueOf(x) if v.Kind() == reflect.Int64 { v.SetInt(200) //If the copy is modified, the reflect package will cause panic } } func reflectSetValue2(x interface{}) { v := reflect.ValueOf(x) // In reflection, use the Elem() method to get the value corresponding to the pointer if v.Elem().Kind() == reflect.Int64 { v.Elem().SetInt(200) } } func main() { var a int64 = 100 // reflectSetValue1(a) //panic: reflect: reflect.Value.SetInt using unaddressable value reflectSetValue2(&a) fmt.Println(a) }
isNil() and isValid()
isNil()
func (v Value) IsNil() bool
IsNil() reports whether the value held by v is nil. The classification of the value held by v must be one of channels, functions, interfaces, mappings, pointers and slices; Otherwise, the IsNil function will cause panic.
isValid()
func (v Value) IsValid() bool
IsValid() returns whether v holds a Value. If v is Value, zero Value will return false. At this time, v methods other than IsValid, String and Kind will cause panic.
for instance
IsNil() is often used to determine whether the pointer is null; IsValid() is often used to determine whether the return value is valid.
func main() { // *Null pointer of type int var a *int fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil()) // nil value fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid()) // Instantiate an anonymous structure b := struct{}{} // Try to find the "abc" field from the structure fmt.Println("Nonexistent structure member:", reflect.ValueOf(b).FieldByName("abc").IsValid()) // Try to find the "abc" method from the structure fmt.Println("Nonexistent structure method:", reflect.ValueOf(b).MethodByName("abc").IsValid()) // map c := map[string]int{} // Try to find a key that does not exist in the map fmt.Println("map Key not present in:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("Naza")).IsValid()) }
Structural reflection
Methods related to structures
After any value obtains the reflection object information through reflect.TypeOf(), if its type is a structure, you can obtain the detailed information of the structure member through the NumField() and Field() methods of the reflection value object (reflect.Type).
The methods related to obtaining structure members in reflect.Type are shown in the following table.
method | explain |
---|---|
Field(i int) StructField | According to the index, return the information of the structure field corresponding to the index. |
NumField() int | Returns the number of structure member fields. |
FieldByName(name string) (StructField, bool) | Returns the information of the structure field corresponding to the given string. |
FieldByIndex(index []int) StructField | When accessing multi-layer members, the field information is returned according to the field index of each structure provided by [] int. |
FieldByNameFunc(match func(string) bool) (StructField,bool) | Match the required fields according to the passed in matching function. |
NumMethod() int | Returns the number of methods in the method set of this type |
Method(int) Method | Returns the ith method in the type method set |
MethodByName(string)(Method, bool) | Returns the method in the method set of this type according to the method name |
StructField type
The StructField type is used to describe the information of a field in the structure.
StructField is defined as follows:
type StructField struct { // Name is the name of the field. PkgPath is the package path of non export field, which is' 'for export field. // See http://golang.org/ref/spec#Uniqueness_of_identifiers Name string PkgPath string Type Type // Type of field Tag StructTag // Label of the field Offset uintptr // The byte offset of the field in the structure Index []int // Index slice for Type.FieldByIndex Anonymous bool // Anonymous field }
Structure reflection example
When we use reflection to get a structure data, we can get its field information in turn through the index, or we can get the specified field information through the field name.
type student struct { Name string `json:"name"` Score int `json:"score"` } func main() { stu1 := student{ Name: "princeling", Score: 90, } t := reflect.TypeOf(stu1) fmt.Println(t.Name(), t.Kind()) // student struct // Traverse all field information of the structure through the for loop for i := 0; i < t.NumField(); i++ { field := t.Field(i) fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json")) } // Obtain the field information of the specified structure through the field name if scoreField, ok := t.FieldByName("Score"); ok { fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json")) } }
Next, write a function printMethod(s interface {}) to traverse the methods contained in printing s.
// Add two methods to student, Study and sleep (note the initial capitalization) func (s student) Study() string { msg := "study hard and make progress every day." fmt.Println(msg) return msg } func (s student) Sleep() string { msg := "Sleep well and grow up quickly." fmt.Println(msg) return msg } func printMethod(x interface{}) { t := reflect.TypeOf(x) v := reflect.ValueOf(x) fmt.Println(t.NumMethod()) for i := 0; i < v.NumMethod(); i++ { methodType := v.Method(i).Type() fmt.Printf("method name:%s\n", t.Method(i).Name) fmt.Printf("method:%s\n", methodType) // The parameter passed through a reflection call method must be of type [] reflect.Value var args = []reflect.Value{} v.Method(i).Call(args) } }
Reflection is a double-edged sword
Reflection is a powerful and expressive tool that allows us to write more flexible code. But reflection should not be abused for three reasons.
- Reflection based code is extremely fragile. Type errors in reflection will cause panic when it is actually running, which is likely to be a long time after the code is written.
- Code that uses reflection heavily is often difficult to understand.
- The performance of reflection is low. The code based on reflection usually runs one or two orders of magnitude slower than normal code.