title: 【转载】Go Language Basics: Reflection
date: 2021-08-09 16:40:33
comment: false
toc: true
category:
- Golang
tags: - Reprint
- Go
- Basics
- Reflection
This article is reprinted from: Go Language Basics: Reflection | Li Wenzhou's Blog
This article introduces the significance and basic usage of reflection in Go language.
The Intrinsic Mechanism of Variables#
In Go language, variables are divided into two parts:
- Type information: Predefined metadata.
- Value information: Dynamically changeable during program execution.
Introduction to Reflection#
Reflection refers to the ability to access and modify the program itself during runtime. When a program is compiled, variables are converted to memory addresses, and variable names are not written into the executable part by the compiler. During program execution, the program cannot obtain its own information.
Languages that support reflection can integrate reflection information of variables, such as field names, type information, struct information, etc., into the executable file during the compilation phase, and provide interfaces for the program to access reflection information, allowing the program to obtain reflection information about types at runtime and have the capability to modify them.
Go programs use the reflect package to access the reflection information of the program at runtime.
In the previous blog, we introduced the empty interface. The empty interface can store variables of any type, so how do we know what data this empty interface holds? Reflection is the dynamic acquisition of a variable's type information and value information at runtime.
The reflect Package#
In Go language's reflection mechanism, any interface value consists of a concrete type
and the value of that concrete type
(we introduced related concepts in the previous blog about interfaces). The relevant functions for reflection in Go language are provided by the built-in reflect package, and any interface value in reflection can be understood as consisting of reflect.Type
and reflect.Value
, and the reflect package provides the reflect.TypeOf
and reflect.ValueOf
functions to obtain the Value and Type of any object.
TypeOf#
In Go language, the reflect.TypeOf()
function can be used 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, types are further divided into two categories: Type
and Kind
. In Go language, we can use the type keyword to construct many custom types, while Kind
refers to the underlying type. However, in reflection, when it is necessary to distinguish between pointers, structs, and other major types, Kind
is used. For example, we define two pointer types and two struct types and use reflection to view their types and kinds.
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: "Little Prince",
age: 18,
}
var e = book{title: "Learning Go Language with the Little Prince"}
reflectType(d) // type:person kind:struct
reflectType(e) // type:book kind:struct
}
In Go language's reflection, types like arrays, slices, maps, and pointers return empty
for their .Name()
.
The Kind types defined in the reflect
package are as follows:
type Kind uint
const (
Invalid Kind = iota // Invalid type
Bool // Boolean type
Int // Signed integer type
Int8 // Signed 8-bit integer type
Int16 // Signed 16-bit integer type
Int32 // Signed 32-bit integer type
Int64 // Signed 64-bit integer type
Uint // Unsigned integer type
Uint8 // Unsigned 8-bit integer type
Uint16 // Unsigned 16-bit integer type
Uint32 // Unsigned 32-bit integer type
Uint64 // Unsigned 64-bit integer type
Uintptr // Pointer
Float32 // Single precision floating point
Float64 // Double precision floating point
Complex64 // 64-bit complex type
Complex128 // 128-bit complex type
Array // Array
Chan // Channel
Func // Function
Interface // Interface
Map // Map
Ptr // Pointer
Slice // Slice
String // String
Struct // Struct
UnsafePointer // Underlying pointer
)
ValueOf#
reflect.ValueOf()
returns a reflect.Value
type, which contains the value information of the original value. reflect.Value
can be converted back and forth with the original value.
The methods provided by the reflect.Value
type to obtain the original value are as follows:
Method | Description |
---|---|
Interface() interface {} | Returns the value as interface{} type, which can be converted to a specified type through type assertion |
Int() int64 | Returns the value as int type; all signed integers can be returned this way |
Uint() uint64 | Returns the value as uint type; all unsigned integers can be returned this way |
Float() float64 | Returns the value as double precision (float64) type; all floating-point numbers (float32, float64) can be returned this way |
Bool() bool | Returns the value as bool type |
Bytes() []bytes | Returns the value as byte array []bytes type |
String() string | Returns the value as string type |
Getting Values through Reflection#
func reflectValue(x interface{}) {
v := reflect.ValueOf(x)
k := v.Kind()
switch k {
case reflect.Int64:
// v.Int() gets the original integer value from reflection, then forcibly converts it to int64()
fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
case reflect.Float32:
// v.Float() gets the original floating-point value from reflection, then forcibly converts it to float32()
fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
case reflect.Float64:
// v.Float() gets the original floating-point value from reflection, then forcibly converts it to 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
// Convert the original int value to reflect.Value type
c := reflect.ValueOf(10)
fmt.Printf("type c :%T\n", c) // type c :reflect.Value
}
Setting Variable Values through Reflection#
To modify a variable's value through reflection in a function, it is important to note that the function parameter is a value copy, and the variable's address must be passed to modify its value. In reflection, the special Elem()
method is used to get 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) // Modifying a copy, the reflect package will raise panic
}
}
func reflectSetValue2(x interface{}) {
v := reflect.ValueOf(x)
// Use the Elem() method in reflection 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 channel, function, interface, map, pointer, or slice; otherwise, the IsNil function will cause panic.
isValid()#
func (v Value) IsValid() bool
IsValid()
returns whether v holds a value. If v is the zero value of Value, it will return false; at this time, methods other than IsValid, String, and Kind will cause panic.
Example#
IsNil()
is commonly used to determine whether a pointer is null; IsValid()
is commonly used to determine whether a return value is valid.
func main() {
// *int type null pointer
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 struct
b := struct{}{}
// Try to find the "abc" field from the struct
fmt.Println("Non-existent struct member:", reflect.ValueOf(b).FieldByName("abc").IsValid())
// Try to find the "abc" method from the struct
fmt.Println("Non-existent struct method:", reflect.ValueOf(b).MethodByName("abc").IsValid())
// map
c := map[string]int{}
// Try to find a non-existent key in the map
fmt.Println("Non-existent key in map:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("Nazha")).IsValid())
}
Struct Reflection#
Methods Related to Structs#
Any value obtained through reflect.TypeOf()
can get detailed information about struct members through the reflection value object (reflect.Type
) using the NumField()
and Field()
methods.
The methods related to obtaining struct member information in reflect.Type
are shown in the following table.
Method | Description |
---|---|
Field(i int) StructField | Returns the information of the struct field corresponding to the index. |
NumField() int | Returns the number of struct member fields. |
FieldByName(name string) (StructField, bool) | Returns the information of the struct field corresponding to the given string. |
FieldByIndex(index []int) StructField | When accessing multi-layer members, returns the field information based on the field index provided by []int. |
FieldByNameFunc(match func(string) bool) (StructField,bool) | Matches the required fields based on the provided matching function. |
NumMethod() int | Returns the number of methods in the method set of that type. |
Method(int) Method | Returns the i-th method in the method set of that type. |
MethodByName(string)(Method, bool) | Returns the method in the method set of that type based on the method name. |
StructField Type#
The StructField
type is used to describe the information of a field in a struct.
The definition of StructField
is as follows:
type StructField struct {
// Name is the name of the field. PkgPath is the package path of non-exported fields; for exported fields, this field is "".
// See http://golang.org/ref/spec#Uniqueness_of_identifiers
Name string
PkgPath string
Type Type // Field type
Tag StructTag // Field tag
Offset uintptr // Byte offset of the field in the struct
Index []int // Index slice used for Type.FieldByIndex
Anonymous bool // Whether it is an anonymous field
}
Struct Reflection Example#
When we obtain struct data through reflection, we can sequentially get its field information through indexing or obtain specified field information by field name.
type student struct {
Name string `json:"name"`
Score int `json:"score"`
}
func main() {
stu1 := student{
Name: "Little Prince",
Score: 90,
}
t := reflect.TypeOf(stu1)
fmt.Println(t.Name(), t.Kind()) // student struct
// Iterate through all field information of the struct using a 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"))
}
// Get specified struct field information by 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, we will write a function printMethod(s interface{})
to iterate and print the methods contained in s.
// Add two methods Study and Sleep to student (note the capitalized first letter)
func (s student) Study() string {
msg := "Study hard, make progress every day."
fmt.Println(msg)
return msg
}
func (s student) Sleep() string {
msg := "Sleep well, 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 parameters passed to the method through reflection 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. However, reflection should not be abused for the following three reasons.
- Code based on reflection is extremely fragile; type errors in reflection will only trigger panic at runtime, which may be a long time after the code is written.
- Code that heavily uses reflection is often difficult to understand.
- The performance of reflection is poor; code implemented based on reflection usually runs one to two orders of magnitude slower than normal code.
Exercises#
- Write code to implement an ini file parser program using reflection.