Golang reflection - Part 1

1. Definition of reflection

It's a great source of conflict ~ (quoted from the official blog)

Reflection refers to dynamically accessing and modifying the structure and members of objects of any Type at runtime. The reflection package is provided in go language to provide the function of reflection. Each variable has two properties: Type and Value

Reflection can be self describing and self controlling
For example, python reflection: execute functions according to strings and import packages according to strings

Go is a static language. Reflection is a mechanism provided by go. You can do the following things when you don't know the type at compile time

  • Update variable
  • View values at runtime
  • Call method
  • Operate on their layout

Two classic scenes using reflection

  • The function you wrote doesn't know what the type passed to you is. It may not be agreed, or there may be many types passed in

  • You want to decide which function to call (call the method according to the string) and execute the function dynamically through the user's input

2. Basic data type of reflection

3,Type

reflect.Type is an interface type used to obtain information related to variable types. You can obtain the type information of a variable through the reflect.TypeOf function

Source code go/src/reflect/type.go

type Type interface {
	Align() int
	FieldAlign() int
	Method(int) Method  // The i th method
	MethodByName(string) (Method, bool)  // Get method by name
	NumMethod() int  // Number of methods
	Name() string  // Get structure name
	PkgPath() string  // Package path
	Size() uintptr  // Size of memory occupied
	String() string  // Get string representation
	Kind() Kind // data type
	Implements(u Type) bool  // Determine whether an interface is implemented
	AssignableTo(u Type) bool  // Can it be assigned to another type
	ConvertibleTo(u Type) bool  // Can I convert to another type
	Comparable() bool
	Bits() int
	ChanDir() ChanDir
	IsVariadic() bool
	Elem() Type  // Resolve pointer (change pointer type to normal type)
	Field(i int) StructField  // Member i
	FieldByIndex(index []int) StructField  // Get nested members according to index path
	FieldByName(name string) (StructField, bool)  // Get members by name
	FieldByNameFunc(match func(string) bool) (StructField, bool)
	In(i int) Type
	Key() Type
	Len() int  // Length of container
	NumField() int
	NumIn() int  // Number of output parameters
	NumOut() int  // Returns the number of parameters
	Out(i int) Type

	common() *rtype
	uncommon() *uncommonType
}

4,Value

reflect.Value is a structure type, which is used to obtain the information of variable value. The value information of modifying the original data type (a variable) can be obtained through the reflect.ValueOf function

Source code go/src/reflect/value.go

type Value struct {
   // Data type represented
	typ *rtype
	// Pointer to raw data
	ptr unsafe.Pointer
}

5. Three laws of reflection

The interface type has a value and type pair, and the reflection is to check the value and type pair of the interface
Specifically, Go provides one set of methods to extract the value of the interface and another set of methods to extract the type of the interface

  • reflect.Type provides a set of interface processing interface types, that is, value, type in type
  • reflect.Value provides a set of interface processing interface values, i.e. value, value in type

5.1 first law of reflection

The first law of reflection: reflection can convert interface type variables into reflective objects

How to get the value and type of a variable through reflection

package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    t := reflect.TypeOf(x)  //t is reflext.Type
    fmt.Println("type:", t)
    fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
    v := reflect.ValueOf(x) //v is reflext.Value
    fmt.Println("value:", v)
}

Program output

type: float64
kind is float64: true
value: 3.4

Reflection is for interface type variables, where the parameters accepted by TypeOf() and ValueOf() are of interface {} type, that is, the x value is transferred into interface

5.2 second law of reflection

The second law of reflection: reflection can restore a reflected object to an interface object

The reason why it is called 'reflection', reflection objects and interface objects can be transformed into each other

package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x) //v is reflext.Value
    var y float64 = v.Interface().(float64)
    fmt.Println("value:", y)
}

Object x is converted to reflection object v, and V is converted to interface object through Interface(). The interface object obtains the value of float64 type through. (float64) type assertion

5.3 the third law of reflection

The third law of reflection: the reflection object can be modified, and the value value must be settable

Reflection allows you to convert an interface type variable to a reflection object, which you can use to set the value it holds

package main
import (
    "reflect"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x)
    v.SetFloat(7.1) // Error: will panic.
}

panic appears by setting a new value for the reflection object v

panic: reflect: reflect.Value.SetFloat using unaddressable value

The reason for the error is that v is not modifiable.

Whether the reflection object can be modified depends on its stored value. Recall whether the function passes a value or an address when passing parameters, it is not difficult to understand why the above example failed.

In the above example, the value of X passed into the reflect.ValueOf() function is actually the value of X, not x itself. That is, modifying the value of v cannot affect x, that is, it is an invalid modification, so golang will report an error

When you think of this, you can understand that if you use the address of x when building v, you can modify it, but at this time, v represents the pointer address. What we want to set is the content pointed to by the pointer, that is, * v is what we want to modify. Modify the value of x by v?

reflect.Value provides the Elem() method to get the value pointed to by the pointer

package main
import (
"reflect"
    "fmt"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(&x)
    v.Elem().SetFloat(7.1)
    fmt.Println("x :", v.Elem().Interface())
}

Output is:

x : 7.1

6. Common API s for reflection

6.1 get type

typeUser := reflect.TypeOf(&User{}) //Get Type through TypeOf()
fmt.Println(typeUser)                      //*User
fmt.Println(typeUser.Elem())               //User
fmt.Println(typeUser.Name())               //Empty string
fmt.Println(typeUser.Elem().Name())        //User, class name without package name
fmt.Println(typeUser.Kind())               //ptr
fmt.Println(typeUser.Elem().Kind())        //struct
fmt.Println(typeUser.Kind() == reflect.Ptr)
fmt.Println(typeUser.Elem().Kind() == reflect.Struct)

6.2 obtaining Field information

typeUser := reflect.TypeOf(User{}) //struct Type is required, but pointer Type is not allowed
fieldNum := typeUser.NumField()           //Number of member variables
for i := 0; i < fieldNum; i++ {
	field := typeUser.Field(i)
	fmt.Printf("%d %s offset %d anonymous %t type %s exported %t json tag %s\n", i,
		field.Name,            //Variable name
		field.Offset,          //The string type occupies 16 bytes relative to the memory offset of the first address of the structure
		field.Anonymous,       //Is it an anonymous member
		field.Type,            //Data type, reflect.Type
		field.IsExported(),    //Whether it is visible outside the package (i.e. whether it starts with a capital letter)
		field.Tag.Get("json")) //Get the tag defined in ` ` after the member variable
//Field can be obtained through FieldByName
if nameField, ok := typeUser.FieldByName("Name"); ok {
	fmt.Printf("Name is exported %t\n", nameField.IsExported())
}
//Field can also be obtained according to FieldByIndex
thirdField := typeUser.FieldByIndex([]int{2}) //The parameter is a slice because struct s are nested
fmt.Printf("third field name %s\n", thirdField.Name)
}

6.3 obtaining method information

typeUser := reflect.TypeOf(common.User{})
methodNum := typeUser.NumMethod() //Number of member methods. Methods where the receiver is a pointer [not included]
for i := 0; i < methodNum; i++ {
	method := typeUser.Method(i)
	fmt.Println(method.Type)  // The complete signature of the function will be output, and the input parameter will also take the structure itself as the input parameter, so the number of parameters is one more
	fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())
}
fmt.Println()
if method, ok := typeUser.MethodByName("Examine2"); ok {  // Get by method name
	fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())
}
typeUser2 := reflect.TypeOf(&common.User{})
methodNum = typeUser2.NumMethod() //Number of member methods. The method whose receiver is a pointer or value is included, that is, the method pointer implemented by the value is also implemented (otherwise, it does not hold)
for i := 0; i < methodNum; i++ {
	method := typeUser2.Method(i)
	fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())
}

6.4 obtaining function information

typeFunc := reflect.TypeOf(Add) //Get function type
fmt.Printf("is function type %t\n", typeFunc.Kind() == reflect.Func)
argInNum := typeFunc.NumIn()   //Number of input parameters
argOutNum := typeFunc.NumOut() //Number of output parameters
for i := 0; i < argInNum; i++ {
	argTyp := typeFunc.In(i)
	fmt.Printf("The first%d Type of input parameter%s\n", i, argTyp)
}
for i := 0; i < argOutNum; i++ {
	argTyp := typeFunc.Out(i)
	fmt.Printf("The first%d Types of output parameters%s\n", i, argTyp)
}

6.5 assignment and conversion relationship

  • Type1. Assignableto (type2) / / can the type represented by type1 be assigned to the type represented by type2
  • Type1. Convertibleto (type2)) / / can the type represented by type1 be converted to the type represented by type2
  • java reflection can obtain inheritance relationship, but go language does not support inheritance, so it must be of the same type to AssignableTo and ConvertibleTo

Example

u := reflect.TypeOf(User{})
t := reflect.TypeOf(Student{}) //User is nested inside Student
u2 := reflect.TypeOf(User{})

//false false
fmt.Println(t.AssignableTo(u))  //Can the type represented by t be assigned to the type represented by u
fmt.Println(t.ConvertibleTo(u)) //Can the type represented by t be converted to the type represented by u

//false false
fmt.Println(u.AssignableTo(t))
fmt.Println(u.ConvertibleTo(t))

//true true
fmt.Println(u.AssignableTo(u2))
fmt.Println(u.ConvertibleTo(u2))

6.6 whether the interface is implemented

//Get the interface type through reflect. Typeof ((* < interface >) (NIL)). Elem(). Because people is an interface and cannot create an instance, nil is forced to be of type * common.People
typeOfPeople := reflect.TypeOf((*common.People)(nil)).Elem()  // nil can be understood as an instance of the People pointer
fmt.Printf("typeOfPeople kind is interface %t\n", typeOfPeople.Kind() == reflect.Interface)
t1 := reflect.TypeOf(common.User{})
t2 := reflect.TypeOf(&common.User{})
//If the value type of User implements the interface, the pointer type also implements the interface; But not the other way around (try changing the receiver of Think to * User)
fmt.Printf("t1 implements People interface %t\n", t1.Implements(typeOfPeople))  // false
fmt.Printf("t2 implements People interface %t\n", t2.Implements(typeOfPeople))  // true

6.7 value and other types of interchanges

//Original type to Value
iValue := reflect.ValueOf(1)
sValue := reflect.ValueOf("hello")
userPtrValue := reflect.ValueOf(&common.User{
	Id:     7,
	Name:   "Jackson",
	Weight: 65,
	Height: 1.68,
})
fmt.Println(iValue)       //1
fmt.Println(sValue)       //hello
fmt.Println(userPtrValue) //&{7 Jackson 65 1.68}
//Value to Type
iType := iValue.Type()
sType := sValue.Type()
userType := userPtrValue.Type()
//Calling Kind() on Type and corresponding Value results in the same
fmt.Println(iType.Kind() == reflect.Int, iValue.Kind() == reflect.Int, iType.Kind() == iValue.Kind())                   //true true
fmt.Println(sType.Kind() == reflect.String, sValue.Kind() == reflect.String, sType.Kind() == sValue.Kind())             //true true
fmt.Println(userType.Kind() == reflect.Ptr, userPtrValue.Kind() == reflect.Ptr, userType.Kind() == userPtrValue.Kind()) //true true true

//Pointer Value and non pointer Value are converted to each other
userValue := userPtrValue.Elem()                    //Elem() pointer Value to non pointer Value
fmt.Println(userValue.Kind(), userPtrValue.Kind())  //struct ptr
userPtrValue3 := userValue.Addr()                   //Addr() non pointer Value to pointer Value
fmt.Println(userValue.Kind(), userPtrValue3.Kind()) //struct ptr

//Convert to original type
//Convert the Value to interface {} through the Interface() function, and then convert it from interface {} to the original data type
//Or directly call Int(), String(), etc. on Value in one step
fmt.Printf("origin value iValue is %d %d\n", iValue.Interface().(int), iValue.Int())
fmt.Printf("origin value sValue is %s %s\n", sValue.Interface().(string), sValue.String())
user := userValue.Interface().(common.User)
fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user.Id, user.Name, user.Weight, user.Height)
user2 := userPtrValue.Interface().(*common.User)
fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user2.Id, user2.Name, user2.Weight, user2.Height)

6.8 three cases of value judging null value

The pre declarations of pointer, channel, func, interface, map and slice are nil

var i interface{} //The interface does not point to a specific value
v := reflect.ValueOf(i)
fmt.Printf("v Holding value %t, type of v is Invalid %t\n", v.IsValid(), v.Kind() == reflect.Invalid)  // false

var user *common.User = nil
v = reflect.ValueOf(user) //Value points to a nil
if v.IsValid() {
	fmt.Printf("v The value held is nil %t\n", v.IsNil()) //Ensure IsValid() before calling IsNil(), otherwise panic / / true will occur
}

var u common.User //Just declare that the values inside are 0 values
v = reflect.ValueOf(u)
if v.IsValid() {
	fmt.Printf("v The value held is the 0 value of the corresponding type %t\n", v.IsZero()) //Ensure IsValid() before calling IsZero(), otherwise panic / / true will occur
}

reference resources: https://go.dev/blog/laws-of-reflection

See you ~

Pay attention to the official account plus more dry cargo.

Posted by sssphp on Thu, 11 Nov 2021 19:19:08 -0800