Go core development study notes (Eve) - reflection

Local use of reflection

  1. When the serialization and deserialization, if desired sequences structure field name is converted to uppercase to lowercase, json:"xxx"here it uses reflection.
  2. Two anonymous function variables, a defined adapter function as unitary Interface:
    the adapter function: the function name is assumed anonymous <anonymous function name>, anonymous function parameters a, b ... of the adapter function FUNC (<anonymous function name> , a, b ...)
    that is to create a template, and anonymous anonymous function function name in the function parameters are passed as a parameter adapter function.
  3. Reflecting the value that they can go to develop the framework.

Reflection principle

  1. Reflecting acquired dynamically at runtime various variable information, such as the type of the variable type, category kind.
    If the category type and the data type are common, no difference;
    if it is a structure variable types and categories are different;

  2. By reflection, can modify the value of the variable, associated methods can be called.

  3. Use reflective required import "reflect"

Application of reflection

  1. Commonly used two functions: reflect.TypeOf (<variable>); reflect.ValueOf (<variable>)

  2. Use case illustrates, later cases have explanatory notes.

    Demo 1: the basic data types ≒ interface {} ≒ reflect.Value

    package main
    import (
    	"fmt"
    	"reflect"
    )
    func main() {
    	var num int = 100
    	/*
    	问题一:
    	通过反射拿到num的rtype和rvalue
    	注意区分反射拿到的值前面加个r做区分,reflect.rtype和type不是同一个类型
    	这些*reflect.rtype 或 reflect.Value 是有方法的,和普通数据类型是不一样的。
    	*/
    	rty := reflect.TypeOf(num)           // int 注意这个int不是普通数据类型int,是有很多方法的
    	rval := reflect.ValueOf(num)         // 100 注意这个100不是普通值为100,是有很多方法的
    	fmt.Printf("%T %v\n",rty,rty)   //*reflect.rtype
    	fmt.Printf("%T %v\n",rval,rval)  // reflect.Value
    
    	/*问题二:
    	举例说明,rval如果是int类型,那么可以加减乘除,下面验证
    	 */
    	n1 := 10
    	n2 := 2 + n1
    	fmt.Println(n2)
    	//fmt.Println(n2 + rval)  //invalid operation: n2 + rval (mismatched types int and reflect.Value)
    	/*
    	如何解决上述问题
    	func (v Value) IsNil() bool
    	func (v Value) Kind() Kind
    	func (v Value) Type() Type
    	func (v Value) Convert(t Type) Value
    	func (v Value) Elem() Value
    	func (v Value) Bool() bool
    	func (v Value) Int() int64
    	 */
    	fmt.Println(int64(n2) + rval.Int())  //提供将reflect.Value转化为int64的方法,这样强转一下就好了
    
    	/*
    	问题三:
    	如何将reflect.Value转换为最初的int:只需要将reflect.Value转为空接口,再把空接口断言成需要转换的数据类型即可
    	 */
    	num_ori := rval.Interface().(int)
    	fmt.Printf("%T %v\n",num_ori,num_ori)  //int 100
    }
    

    Demo 2: Structure type ≒ interface {} ≒ reflect.Value

    package main
    import (
    	"fmt"
    	"reflect"
    )
    type Kunkun struct {
    	Name string
    	Age int
    	Skill string
    }
    func main() {
    	/*
    	定义一个结构体实例caixukun,通过反射拿到type和value
    	 */
    	var caixukun = Kunkun{"蔡徐坤",20,"Ctrl"}
    	cxkrty := reflect.TypeOf(caixukun)
    	cxkrval := reflect.ValueOf(caixukun)
    	fmt.Printf("%T %v\n",cxkrty,cxkrty)   // *reflect.rtype  main.Kunkun
    	fmt.Printf("%T %v\n",cxkrval,cxkrval)  // reflect.Value {蔡徐坤 20 Ctrl} 仅在运行时知道类型,编译阶段过不去
    
    	//将拿到的值转换成空接口,如果这是想取cxkival中的字段值是无法取出来的
    	cxkival := cxkrval.Interface()
    	fmt.Printf("%T %v\n",cxkival,cxkival) //main.Kunkun {蔡徐坤 20 Ctrl}
    	fmt.Println(caixukun.Name)
    	//fmt.Println(cxkival.Name)  //cxkival.Name undefined (type interface {} is interface with no methods)
    
    	//将空接口类型断言成结构体类型就可以使用字段了
    	cxk_ori, ok := cxkival.(Kunkun)  //已经将空接口类型数据断言成Kunkun结构体
    	if ok {
    		fmt.Println("成功断言取出字段名称: ",cxk_ori.Name)    //蔡徐坤
    	}
    }
    

Reflection notes and details

  1. reflect.Value.Kind (), reflect.rType.Kind () Gets a class variable return is a constant, different in terms of type, category is a collection of types.

    var num int = 10    // type: int , Kind: int  这是普通数据类型
    var cxk Kunkun      // type: Kunkun , Kind: Struct 结构体就有宏观和微观概念了,例如Kunkun是自定义type,是结构体的一类,Struct包含Kunkun这种结构体。
    
  2. A reflective manner to obtain the value of the variable, the data type matches the requirements, otherwise they will panic .

  3. Value of a variable modified by reflection, see below demonstrates two:

    Demo 1: changing the value of the variable by ordinary reflection

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    func main() {
    	/*
    	1. 通过反射修改普通变量的值
    	2. 通过反射修改结构体实例的值
    	 */
    	//var cxk = Kunkun{"蔡徐坤",20,"Ctrl"}
    	var num int = 100
    	//使用反射修改普通变量的值,如果要改变变量值,必须传递地址,通过改变内存地址中的值完成变量改变
    	rNum := reflect.ValueOf(num)
    	fmt.Printf("%T %v\n",rNum,rNum)
    	//rNum.SetInt(20)            	   //reflect.Value.SetInt using unaddressable value,因为不是地址格式的数据所以不行
    	//rNum.Elem().SetInt(20)          //把num变成20
    	fmt.Printf("%T %v\n",num,num)   //这样改变肯定无效,因为不是指针格式
    
    	//下面为正确做法
    	rNum1 := reflect.ValueOf(&num)   //必须是地址,不然没有意义,无法改变num的值
    	fmt.Printf("%T %v\n",rNum1,rNum1)
    	rNum1.Elem().SetInt(20)          //把num变成20,rNum1为地址,rNum1.Elem()为一个value,Elem()用的最多,一定要注意
    	fmt.Printf("%T %v\n",num,num)    //如果没有Elem(),unaddressable value panic
    }
    

    Demo 2: a value obtained by changing the structure of the field reflector

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type kunkun struct {
    	Name string
    	Age int
    	Skill string
    }
    
    func main() {
    	/*
    	上面理论讲的很清楚了,下方与上方修改普通变量几乎没有区别,唯一在于传入的地址变成了字段 &(cxk.Skill)
    	 */
    	var cxk = kunkun{"蔡徐坤",20,"Ctrl"}
    	rCxk := reflect.ValueOf(&(cxk.Skill))
    	rCxk.Elem().SetString("唱跳rap篮球")      //相当于改变了原来结构体实例中的技能变为 "唱跳rap篮球"
    	fmt.Println(cxk.Skill)
    }
    

★★★ reflection of best practices

Case: using reflection to traverse the field structure, the structure of the method call, and obtaining a value tag structure.

package main

import (
	"fmt"
	"reflect"
)

//kunkun结构体
type caixukun struct {
	Name string   `json:"name"`
	Age int	      `json:"age"`
	Skill string
}
func (c caixukun) Intro() {      // reflect.Value.Method()中 i=0
	fmt.Println("大家好,我是练习时长两年半的练习生cxk!")
}
func (c caixukun) Dance() {      // reflect.Value.Method()中 i=1
	fmt.Println("鸡你太美~~丁丁丁丁丁丁丁~鸡你太美~~~丁丁丁丁丁丁丁丁~~")
}
func (c caixukun) Set(n1 int,n2 int) int {      // // reflect.Value.Method()中 i=2
	return n1 + n2
}

func Ref(cxk interface{}) {
	rty := reflect.TypeOf(cxk)    //获取结构体变量的反射类型
	rval := reflect.ValueOf(cxk)  //获取结构体变量的反射值
	rkd := rval.Kind()            //获取结构体变量的类型常量
	//fmt.Printf("%T %v\n",rty,rty)   //*reflect.rtype main.caixukun
	//fmt.Printf("%T %v\n",rval,rval) //reflect.Value {蔡徐坤 20 Ctrl}
	//fmt.Printf("%T %v\n",rkd,rkd)   //reflect.Kind struct
	if rkd != reflect.Struct {          //Struct为常量比较
		fmt.Println("unexpect struct, re-input the correct struct, exit Program~")
		return
	}

	//获取结构体实例所有字段和值
	Fnum := rval.NumField()    //常用的一个方法,获得结构体方法数量,类似len()和 cap() 3个字段
	//接下来演示如何拿到结构体实例的字段和值
	for i := 0 ; i < Fnum ; i++ {
		/*
		Name string   `json:"name"`   //有tag
		Age int	      `json:"age"`    //有tag
		Skill string                  //无tag
		*/
		fmt.Printf("字段索引值为%d,字段值为%v\n",i,rval.Field(i)) //reflect.Value.Field(x)获得字段值
		//检查结构体实例字段的tag标签
		tagRval := rty.Field(i).Tag.Get("json")
		fmt.Printf("第%d个字段为%v,对应的tag为%v\n",i,rval.Field(i),tagRval)
	}

	//获取结构体实例有多少个方法以及方法调用
	methods := rval.NumMethod()
	fmt.Printf("结构体实例存在%d个方法\n",methods)
	
	//reflect.Value.Method(x int)方法中,方法索引排序并不是按照结构体代码由上到下,而是按照方法首字母ASCII代码码值进行
	//所以方法0为:Dance(),方法1为:Intro(),方法2为:Set(xxx),如果方法中本身不传参,Call中必须填入(nil)
	rval.Method(1).Call(nil)   //调用Intro方法: 大家好,我是练习时长两年半的练习生cxk!
	rval.Method(0).Call(nil)   //调用Dance方法: 鸡你太美~~...
	
	//对于要传递参数的方法,传进call()中的是一个[]reflect.Value切片,所以需要先定义切片
	var slice []reflect.Value
	slice = append(slice,reflect.ValueOf(30))   //传第一个参数 进切片
	slice = append(slice,reflect.ValueOf(20))   //传第二个参数 进切片
	fmt.Println(rval.Method(2).Call(slice)[0])  //返回值只有一个值,也是一个切片,所以只需要接收切片第一个值[0]

}

func main() {
	/*
	使用反射来遍历结构体字段,调用结构体的方法,并获得结构体的标签值
	两个重要方法
	1. reflect.Value.Method() :
	func (v Value) Method(i int) Value
	默认按照传入结构体实例的方法名排序对应的i值,初始值i=0
	见上方结构体方法注释,三个方法,分别为方法0,1,2,返回值是一个reflect.Value
	2. reflect.Value.Call() :
	func (v Value) Call(in []Value) []Value
	通过Method获取的几个Value,作为参数形式传入Call()中,调用几种方法,注意[]value为reflect.Value的切片
	 */
	var cxk = caixukun{"蔡徐坤",20,"Ctrl"}
	Ref(cxk)
}
Published 49 original articles · won praise 18 · views 3996

Guess you like

Origin blog.csdn.net/weixin_41047549/article/details/90579722