(19)Go反射

一、概述 

       每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用。

       在讲反射之前,先来看看Golang关于类型设计的一些原则

  • 变量包括(type, value)两部分

    • 理解这一点就知道为什么nil != nil了
  • type 包括 static(静态)type和concrete(具体)type. 简单来说 static type是你在编码是看见的类型(如int、string),concrete type是runtime系统看见的类型

  • 类型断言能否成功,取决于变量的concrete type,而不是static type. 因此,一个 reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer.

      接下来要讲的反射,就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有反射一说。

      在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:

(value, type)

      value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。

      例如,创建类型为*os.File的变量,然后将其赋给一个接口变量r:

tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)

var r io.Reader
r = tty

     接口变量r的pair中将记录如下信息:(tty, *os.File),这个pair在接口变量的连续赋值过程中是不变的,将接口变量r赋给另一个接口变量w:

var w io.Writer
w = r.(io.Writer)

    接口变量w的pair与r的pair相同,都是:(tty, *os.File),即使w是空接口类型,pair也是不变的。

    interface及其pair的存在,是Golang中实现反射的前提,理解了pair,就更容易理解反射。反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。

二、TypeOf

func TypeOf(i interface{}) Type 

package main

import (
	"fmt"
	"reflect"
)

type MyStu struct {
	Name string
	Age int
	Sex bool
}

func (m MyStu) Read(p []byte) (n int, err error) {
	fmt.Println("myStu::Read()")
	return 0,nil
}

/**
TypeOf
 */
func main() {
	stu := MyStu{"jadeshu", 12, true}
	// 获取stu接口的类型
	t := reflect.TypeOf(stu)
	fmt.Println(t.Name())

	// 获取该接口类型的字段个数
	count := t.NumField()
	for i := 0; i < count; i++ {
		// 获取接口内具体一个字段结构体
		field := t.Field(i)
		fmt.Printf("字段名:%s---字段类型:%v\r\n", field.Name, field.Type)
	}
}

运行结果:

三、ValueOf

func ValueOf(i interface{}) Value

package main

import (
	"fmt"
	"reflect"
)

type myStu struct {
	//sex bool	// 注意这里是私有字段
	Name string
	Age int
	Sex bool
}

func (m myStu) Read(p []byte) (n int, err error) {
	fmt.Println("myStu::Read()")
	return 0,nil
}

/**
ValueOf
*/
func main() {
	stu := myStu{"jadeshu", 12, true}

	// 获取接口的值
	v := reflect.ValueOf(stu)

	// 等同fmt.Println(v.Interface())
	fmt.Println(v)

	// 获取该接口类型的字段个数
	count := v.NumField()
	for i := 0; i < count; i++ {
		// 获取字段对应的值
		vtmp := v.Field(i).Interface()
		fmt.Printf("值为:%v\r\n", vtmp)
	}
}

 运行结果:

四、获取父类字段的类型和值

 Value方法

 Type接口的方法:

    // 返回索引序列指定的嵌套字段的类型,
    // 等价于用索引中每个值链式调用本方法,如非结构体将会panic
    FieldByIndex(index []int) StructField
    // 返回该类型名为name的字段(会查找匿名字段及其子字段),
    // 布尔值说明是否找到,如非结构体将panic
    FieldByName(name string) (StructField, bool)
    // 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
    FieldByNameFunc(match func(string) bool) (StructField, bool)
package main

import (
"fmt"
"reflect"
)

type Base struct {
	Year int
	hours int
}
type Dirve struct {
	Base
	Name string
}

/**
ValueOf
*/
func main() {
	d := Dirve{Base{2019, 12},"jadeshu"}

	// 获取类型和值
	t := reflect.TypeOf(d)
	v := reflect.ValueOf(d)
	// 等同fmt.Println(v.Interface())
	fmt.Println(v)

	// 获取父类字段Base的字段的类型
	bt := t.FieldByIndex([]int{0,0})
	fmt.Printf("名称:%v; 类型:%v; ", bt.Name ,bt.Type)
	// 获取父类字段Base的字段的值
	bv := v.FieldByIndex([]int{0,0})
	fmt.Printf("值为:%v\r\n", bv.Interface())

	// 通过名称查看获取字段的类型
	sf, b := t.FieldByName("hours")
	fmt.Printf("名称:%v; 类型:%v; 是否读取成功:%v", sf.Name ,sf.Type, b)
}

  运行结果:

五、函数调用

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

func (v Value) MethodByName(name string) Value 

package main

import (
	"fmt"
	"reflect"
)

type Func struct {
	Name string
	Age int
}

// 不改变值
func (f Func)SetName(name string, age int)  {
	f.Name = name
	f.Age = age
}

// 改变值
func (f *Func)TestMethod(name string, age int)  {
	f.Name = name
	f.Age = age
}
/**
ValueOf
*/
func main() {
	d := Func{"jadeshu",12}

	// 获取实例反射值
	v := reflect.ValueOf(d)
	// 等同fmt.Println(v.Interface())
	fmt.Println("实例原始反射值:", v)

	// 获取SetName方法,但是值没有改变
	cal := v.MethodByName("SetName")
	if cal.IsValid() {
		fmt.Println("Func实例反射成功调用SetName函数,没有改变值")
		// 调用方法
		cal.Call([]reflect.Value{reflect.ValueOf("testFunc"),reflect.ValueOf(20)})
		fmt.Println("成功调用函数SetName后的值:", v)
	}

	//获取失败,因为Func实例没有实现以实例为接收者的TestMethod函数
	// 所以实例不能调用TestMethod函数
	cal = v.MethodByName("TestMethod")
	if cal.IsValid() {
		// 调用方法
		cal.Call([]reflect.Value{reflect.ValueOf("testFunc"),reflect.ValueOf(20)})
	} else {
		fmt.Println("实例反射纸获取函数TestMethod失败")
	}
	fmt.Println("===================================")

	// 获取实例指针反射值
	v2 := reflect.ValueOf(&d)
	fmt.Println("指针实例原始反射值:", v2)

	// SetName,该方法是实例接收者,所以值或者指针都可以调用
	// 但是不改变值
	cal = v2.MethodByName("SetName")
	if cal.IsValid() {
		fmt.Println("Func指针实例反射成功调用SetName函数,没有改变值")
		// 调用方法
		cal.Call([]reflect.Value{reflect.ValueOf("testFunc1"),reflect.ValueOf(100)})
		fmt.Println(v2)
	}

	// TestMethod,该方法是指针接收者,所以只能指针去调用
	// 改变了值
	cal = v2.MethodByName("TestMethod")
	if cal.IsValid() {
		fmt.Println("Func指针实例反射成功调用TestMethod函数,改变了值")
		// 调用方法
		cal.Call([]reflect.Value{reflect.ValueOf("testFunc2"),reflect.ValueOf(200)})
		fmt.Println(v2)
	}
}

六、附件参考:2个重要的类型

  • Type
  • Value

其中Type是interface类型,Value是struct类型,意识到这一点很重要

Type和Value拥有的同名方法

Type Value 备注
Kind Kind 返回指定对象的Kind类型
MethodByName MethodByName 根据方法名找方法
Method Method 返回第i个方法
NumMethod NumMethod 返回拥有的方法总数,包括unexported方法
Field Field 取struct结构的第n个field
FieldByIndex FieldByIndex 嵌套的方式取struct的field,比如v.FieldByIndex(1,2,3)等价于 v.field(1).field(2).field(3)
FieldByNameFunc FieldByNameFunc 返回名称匹配match函数的field
NumField NumField 返回struct所包含的field数量

Type独有的方法

方法名 备注
Align 分配内存时的内存对齐字节数
FieldAlign 作为struct的field时内存对齐字节数
Name type名 string类型
PkgPath 包路径, "encoding/base64", 内置类型返回empty string
Size 该类型变量占用字节数
String type的string表示方式
Implements 判断该类型是否实现了某个接口
AssignableTo 判断该类型能否赋值给某个类型
ConvertibleTo 判断该类型能否转换为另外一种类型
Comparable 判断该类型变量是否可以比较
ChanDir 返回channel的方向 recv/send/double
IsVariadic 判断函数是否接受可变参数
Elem 取该类型的元素
In 函数第n个入参
Out 函数第n个出参
NumIn 函数的入参数个数
NumOut 函数的出参个数
Key 返回map结构的key类型Type
Len 返回array的长度

Value独有的方法

方法名 备注
Addr v的指针,前提时CanAddr()返回true
Bool bool类型变量的值
Bytes []bytes类型的值
Call 调用函数
CallSlice 调用具有可变参的函数
CanAddr 判断能否取址
CanInterface 判断Interface方法能否使用
CanSet 判断v的值能否改变
Cap 判断容量 Array/Chan/Slice
Close 关闭Chan
Complex  
Convert 返回将v转换位type t的结果
Elem 返回interface包含的实际值
Float  
Index 索引操作 Array/Slice/String
Int  
Interface 将当前value以interface{}形式返回
IsNil 判断是否为nil,chan, func, interface, map, pointer, or slice value
IsValid 是否是可操作的Value,返回false表示为zero Value
Len 适用于Array, Chan, Map, Slice, or String
MapIndex 对map类型按key取值
MapKeys map类型的所有key的列表
OverflowComplex  
OverflowFloat 溢出判断
OverflowInt  
OverflowUint  
Pointer 返回uintptr 适用于slice
Recv chan接收
Send chan发送
Set 将x赋值给v,类型要匹配
SetBool  
SetBytes  
SetCap slice调整切片
SetMapIndex map赋值
SetUint  
SetPointer unsafe.Pointer赋值
SetString  
Slice return v[i:j] 适用于Array/Slict/String
String return value的string表示方法
TryRecv chan非阻塞接收
Try Send chan非阻塞发送
Type 返回value的Type
UnsafeAddr 返回指向value的data的指针

以上列出的方法及备注只是为了方便理解和记忆,使用时请参考go源码及package说明

Kind类型

type Kind uint

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer
)

猜你喜欢

转载自blog.csdn.net/jadeshu/article/details/106606426