go学习-反射

Posted 懒佯佯大哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go学习-反射相关的知识,希望对你有一定的参考价值。

介绍

  • 反射是一种机制,是指可以动态的获取对象信息(名称、属性、类型等)、操作对象(创建对象、修改属性值、调用方法等)

  • 意义:

    • 因为有的时候,并不知道对象具体是什么类型,有哪些属性和方法
    • 便于编写通用的框架,实现松耦合、高复用。比如,ORM库操作、json序列化等
  • golang的反射

    • golang提供了reflect包实现反射处理,golang的反射是对接口变量的动态类型(type)和动态值(value)相关的操作。
  • reflect包的核心如下:

    • reflect.TypeOf(i interface) Type:获取一个“空接口”的动态类型,封装成reflect.Type返回(内部为reflect.rType实例)(备注:入参为副本拷贝)
    • reflect.ValueOf(i interface) Value:获取一个“空接口”的所有信息(备注:入参为副本拷贝)
    • Value:是一个结构体,拥有“动态对象”的所有信息
    • Type:这是一个接口
    • 此外,还有2个包,可以了解一下:
      * Method:定义了方法的属性名而已
      * Kind:定义了元数据
  • 图示:


示例介绍

入门示例
  • 通过反射操作一个int:
func main() 
	// 基本数据类型的反射操作
	var i int = 10
	fmt.Println("获取i的Type:", reflect.TypeOf(i))		// 获取反射类型
	fmt.Println("获取i的Value:", reflect.ValueOf(i))	// 获取反射值
	
	// 开始修改i的值,需要用指针,因为ValueOf的入参为值拷贝
	iOf := reflect.ValueOf(&i)
	iOf.Elem().SetInt(20)
	fmt.Println("修改后i的值为:", i)

	// 将Value再转回指针:
	i2 := iOf.Interface().(*int)
	fmt.Println("修改后i2的值为:", *i2)

  • 输出结果:
获取i的Type: int
获取i的Value: 10
修改后i的值为: 20
修改后i2的值为: 20
这是一个简单的示例,可以看到,通过反射,正常的修改了i的值。
并且,反射后的Value,也可以正常的转换回int指针
Value操作
  • 上面的简单的反射操作,核心在于Value类的操作,而Value包含了很多方法(http://docscn.studygolang.com/pkg/reflect/#Copy)
// 设置Value值:
//func (v Value) Set(x Value)				// 设置一个Value值,比如int类型的:i.Set(reflect.ValueOf(44))
//func (v Value) SetBool(x bool)			// 设置bool值,如下都是相同的调用方式:i.Elem().SetBool(false)
//func (v Value) SetBytes(x []byte)
//func (v Value) SetCap(n int)
//func (v Value) SetComplex(x complex128)
//func (v Value) SetFloat(x float64)
//func (v Value) SetInt(x int64)
//func (v Value) SetLen(n int)
//func (v Value) SetMapIndex(key, val Value)
//func (v Value) SetPointer(x unsafe.Pointer)
//func (v Value) SetString(x string)
//func (v Value) SetUint(x uint64)

fmt.Println("Addr():", iOf.Elem().Addr())
fmt.Println("Bool():", reflect.ValueOf(false).Bool())
fmt.Println("Bytes():", reflect.ValueOf(make([]byte, 3)).Bytes())
//func (v Value) Addr() Value				// 获取地址信息:iOf.Elem().Addr())
//func (v Value) Bool() bool				// 获取bool型的反射:reflect.ValueOf(false).Bool()
//func (v Value) Bytes() []byte				// 获取字节数组: reflect.ValueOf(make([]byte, 3)).Bytes()

//func (v Value) Call(in []Value) []Value
//func (v Value) CallSlice(in []Value) []Value

fmt.Println("CanAddr():", reflect.ValueOf(1).CanAddr())
fmt.Println("CanInterface():", reflect.ValueOf(1).CanInterface())
fmt.Println("CanSet():", reflect.ValueOf(1).CanSet())
//func (v Value) CanAddr() bool				// 是否可以获取Addr:reflect.ValueOf(1).CanAddr()
//func (v Value) CanInterface() bool		// 是否可以转化为Interface:reflect.ValueOf(1).CanInterface()
//func (v Value) CanSet() bool				// 是否可以修改值:reflect.ValueOf(1).CanSet()
//func (v Value) Cap() int					// 获取容量值:如果是非数组、管道、切片,则会异常panic
//func (v Value) Close()					// 关闭channel对象,非channel时抛出异常panic
//func (v Value) Complex() complex128		// 返回复数的underlying value(实部?)
//func (v Value) Convert(t Type) Value		// 将v类型转为t类型
//func (v Value) Elem() Value				// 返回v的值、或v指向的值(指针场景)
//func (v Value) Field(i int) Value			// 返回struct的第i个字段
//func (v Value) FieldByIndex(index []int) Value	// 返回一个嵌套的field列表
//func (v Value) FieldByName(name string) Value		// 根据名字在struct中查找字段
//func (v Value) FieldByNameFunc(match func(string) bool) Value	// 按照函数规则查找字段(函数式编程)

var f1 float64 = 1.234
fmt.Println("f1 Float(): ", reflect.ValueOf(f1).Float())
//func (v Value) Float() float64			// 返回float64 Value的真实值
//func (v Value) Index(i int) Value			// 返回第i个Value,仅限于:数组、管道、切片
//func (v Value) Int() int64				// 返回int值,仅限于int类型:int8/16/32/64
//func (v Value) Interface() (i interface)// 将Value转为interface
//func (v Value) InterfaceData() [2]uintptr	// 没理解????
//func (v Value) IsNil() bool				// 判断Value是否为nil
//func (v Value) IsValid() bool				// 判断Value是否有值,如果为0时返回false
//func (v Value) Kind() Kind				// 返回kind类型
//func (v Value) Len() int					// 返回Value长度,仅限于string、数组、管道、切片
//func (v Value) MapIndex(key Value) Value	// 返回map类型的key对应的value
//func (v Value) MapKeys() []Value			// 返回map类型的key列表
//func (v Value) Method(i int) Value		// 返回第i个方法(struct中方法按照字母的字典序排序)
//func (v Value) MethodByName(name string) Value	// 按照name查找方法
//func (v Value) NumField() int				// 返回字段数
//func (v Value) NumMethod() int			// 返回方法数
//func (v Value) OverflowComplex(x complex128) bool	// 判断Value是否可以表示x complex128
//func (v Value) OverflowFloat(x float64) bool		// 判断Value是否可以表示x float64
//func (v Value) OverflowInt(x int64) bool			// 判断Value是否可以表示x int64
//func (v Value) OverflowUint(x uint64) bool		// 判断Value是否可以表示x uint64
//func (v Value) Pointer() uintptr			// 返回v的值,将v的值作为指针
//func (v Value) Recv() (x Value, ok bool)	// ???
//func (v Value) Send(x Value)				// ???

//func (v Value) Slice(i, j int) Value		// 返回v中到j的切片
//func (v Value) Slice3(i, j, k int) Value	// 返回v的三维的切片???
//func (v Value) String() string			// 返回v的值为string
//func (v Value) TryRecv() (x Value, ok bool)	// ???
//func (v Value) TrySend(x Value) bool		// ??/
//func (v Value) Type() Type				// 返回v的类型
//func (v Value) Uint() uint64				// 返回uint64的value值
//func (v Value) UnsafeAddr() uintptr		// 返回指向v的的值的指针---注意和Pointer()的区别

备注:方法里面含有一些channel的操作,这里先不做介绍

示例:操作struct
  • Value包含了很多方法,可以通过一个简单的struct操作来学习一下
  • 声明一个Student struct:
// 待反射的类,定义三个属性、三个方法
type Student struct 
	Name  string  `json:"name" nickname:"name"`
	Age   int     `json:"age" nickname:"old"`
	Place *string `json:"place" nickname:"pla"`


func (s Student) Say() 
	fmt.Println("Student say... ...")


func (s Student) Play(a string, b string) 
	fmt.Println("Student play with ", a, b)


func (s Student) Cal(a int, b int) int 
	fmt.Println("Student cal a+b=", a+b)
	return a + b

  • 测试:
func main() 
	fmt.Println("===========================================================")
	var str string = "杭州"
	student := Student
		Name:  "zhangsan",
		Age:   20,
		Place: &str,
	
	fmt.Println("获取的Student类型:", reflect.TypeOf(student))
	fmt.Println("获取的Student值为:", reflect.ValueOf(student))
	fmt.Println("==========================开始获取student的属性方法==========================")
	sOf := reflect.ValueOf(student)
	sTy := reflect.TypeOf(student)
	fmt.Println("Student的字段个数:", sOf.NumField())
	fmt.Println("Student的方法个数:", sOf.NumMethod())
	for i:=0; i<sOf.NumField(); i++ 
		fmt.Println("Student的第", i ,"个字段为:", sOf.Field(i))
		fmt.Println("Student的第", i ,"个方法为:", sOf.Method(i))
		get := sTy.Field(i).Tag.Get("json")
		if get != "" 
			fmt.Println("Student的第", i ,"个字段的tag为:", get)
		
	

	fmt.Println("==========================修改值==========================")
	// 下面两种方式都可以
	//reflect.ValueOf(&student).Elem().FieldByName("Name").SetString("lisi")
	reflect.ValueOf(&student).Elem().Field(0).SetString("lisi")
	fmt.Println("Student修改后:", student)

	fmt.Println("==========================反射调用方法==========================")
	fmt.Println("调用方法:", sOf.MethodByName("Say").Call(nil))
	var params []reflect.Value
	val1 := reflect.ValueOf(2)
	val2 := reflect.ValueOf(3)
	params = append(params, val1)
	params = append(params, val2)
	fmt.Println("调用方法:", sOf.MethodByName("Cal").Call(params))

  • 输出结果:
===========================================================
获取的Student类型: main.Student
获取的Student值为: zhangsan 20 0xc000010200
==========================开始获取student的属性方法==========================
Student的字段个数: 3
Student的方法个数: 3
Student的第 0 个字段为: zhangsan
Student的第 0 个方法为: 0x1087960
Student的第 0 个字段的tag为: name
Student的第 1 个字段为: 20
Student的第 1 个方法为: 0x1087960
Student的第 1 个字段的tag为: age
Student的第 2 个字段为: 0xc000010200
Student的第 2 个方法为: 0x1087960
Student的第 2 个字段的tag为: place
==========================修改值==========================
Student修改后: lisi 20 0xc000010200
==========================反射调用方法==========================
Student say... ...
调用方法: []
Student cal a+b= 5
调用方法: [<int Value>]

注意,当需要修改结构体里的字段时,需要传入指针类型

通过反射创建结构体
  • 代码:
fmt.Println("==========================反射创建Student==========================")
stuType := reflect.TypeOf(student)
value := reflect.New(stuType)
student1 := value.Interface().(*Student)
student1.Name = "fanshe"
student1.Age = 200
fmt.Println("反射创建的student", value)
  • 结果
    可以看到,正常的创建了Student类
==========================反射创建Student==========================
反射创建的student &fanshe 200 <nil>

其它

  • 关于reflect.Kind
    它是一个常量类集合:
const (
    Invalid Kind = iota // iota是一个常量0,用于const声明中,表示序列0,后续的每一行数会加一
    Bool // 1
    Int   // 2
    Int8  // 3     因为iota标识了序列起始为0,故下面的每一行都+1
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Ptr
    Slice
    String
    Struct
    UnsafePointer
)

这里有一点需要解释一下:iota
a. 在const中声明一次iota即可,后续的声明都会被覆盖掉
b. iota声明是以行为标识,一行中声明多个变量时,这些变量都是一样的结果

  • 测试一下iota:
fmt.Println("==========================测试iota==========================")
const (
	a = iota
	b
	c
	d string = "ssss"
	e
)
fmt.Printf("a=%v, b=%v, c=%v, d=%v, e=%v \\n", a, b, c, d, e)

const (
	a1 = iota
	b11, b12 = iota, iota
	c1 = iota
)
fmt.Printf("a1=%v, b11=%v, b12=%v, c1=%v \\n", a1, b11, b12, c1)

#########################################
//输出结果:
==========================测试iota==========================
a=0, b=1, c=2, d=ssss, e=ssss 
a1=0, b11=1, b12=1, c1=2 

可以看到:第二个声明的b11和b12在同一行,并且值都为1(本身声明的iota会被覆盖掉)

  • reflect.Method
    Method是一个结构体,暂无对外暴露的成员方法(在反射的内部调用)
type Method struct 
    // Name is the method name.
    // PkgPath is the package path that qualifies a lower case (unexported)
    // method name.  It is empty for upper case (exported) method names.
    // The combination of PkgPath and Name uniquely identifies a method
    // in a method set.
    // See http://golang.org/ref/spec#Uniqueness_of_identifiers
    Name    string
    PkgPath string

    Type  Type  // method type
    Func  Value // func with receiver as first argument
    Index int   // index for Type.Method

  • Type和Kind的区别:
    Type是类型,Kind是类别:Kind的范围大于Type:对于基本类型(int/float/string…),Kind和Type返回一样,但是对于struct,Kind返回“struct”,而Type返回具体的结构体名“Student”

参考

https://zhuanlan.zhihu.com/p/53114706
https://www.jianshu.com/p/9816a7a551cd
https://www.jianshu.com/p/32e4cf8ffffb
https://www.jianshu.com/p/444b55edf32e
https://zhuanlan.zhihu.com/p/53114706

以上是关于go学习-反射的主要内容,如果未能解决你的问题,请参考以下文章

go学习-反射

go语言学习笔记 — 进阶 — 反射:反射的类型对象(reflect.Type)— 什么是反射?

go语言学习笔记 — 进阶 — 反射:反射的类型对象(reflect.Type)— 使用反射获取结构体的成员变量类型

go语言学习笔记 — 进阶 — 反射:反射的类型对象(reflect.Type)— 指针和指针指向的元素

go语言学习笔记 — 进阶 — 反射:反射的类型对象(reflect.Type)— 反射类型对象的类型名(Type)和种类(Kind)

golang 关于 interface 的学习整理