《Go语言学习笔记》 - 第十章 - 反射

反射能够让我们在程序的运行期间探知对象的类型信息和内存结构。比如我们用一个空接口来接收用户输入的内容,但是如果想知道用户输入内容的类型的话就只能在程序运行过程中动态的去判断,这个时候就要用到反射,反射能够在程序运行时动态的获取一个变量的类型信息和值信息

reflect包中有两个重要的类型:TypeValue,这两个类型都具有很多定义好的方法,我们可以直接调用。

Typeof

Go语言中可以通过reflect.Typeof获取任意值的类型对象,我们可以通过返回的类型对象来访问任意值的类型信息。

func main() {
	a := 100
	v := reflect.TypeOf(a)
	fmt.Println(v)  // int
}

在反射中类型分为两类,一种是Type,另一种是Kind。因为在Go语言中我们可以使用type关键字构造很多自定义类型,前者表示真实的、定义的类型,后者表示其基础的、底层的结构的类型。

func main() {
	type X int
	var a X
	a = 100
	v := reflect.TypeOf(a)
	fmt.Println(v.Name(),v.Kind())  // X int
}

需要注意的是,在Go语言中,数组、切片、Map、指针等类型的变量,它们的.Name()都是

func main() {
	var a = [3]int{0,1,2}
	v := reflect.TypeOf(a)
	fmt.Println(v.Name())  // 输出为空
}

reflect的传入对象应该区分基类型和指针类型,因为它们并不属于同一类型。使用方法Elem可以返回指针、数组、切片、字典(值)或通道的基类型

func main() {
	a := 100
	v1 := reflect.TypeOf(a)
	v2 := reflect.TypeOf(&a)
	fmt.Println(v1,v2,v1==v2)  // int *int false
	fmt.Println(v1.Kind(),v2.Kind())  // int ptr
	fmt.Println(v1,v2.Elem(),v1==v2.Elem())  // int int true
	fmt.Println(reflect.TypeOf(map[string]int{}).Elem())  // int  返回字典值的基类型
}

在Go语言包中定义的Kind类型如下:

type Kind uint
const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 无符号整型
    Uint8                // 无符号8位整型
    Uint16               // 无符号16位整型
    Uint32               // 无符号32位整型
    Uint64               // 无符号64位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64位复数类型
    Complex128           // 128位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 结构体
    UnsafePointer        // 底层指针
)

Valueof

Type获取类型信息不同,Value专注于对象实例数据的读写。

reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。reflect.Value与原始值之间可以互相转换。

reflect.Value类型提供的获取原始值的方法如下:

方法 说明
Interface() interface {} 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64 将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64 将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool 将值以 bool 类型返回
Bytes() []bytes 将值以字节数组 []bytes 类型返回
String() string 将值以字符串类型返回

通过反射动态的获取值

func GetTypeAndValue(a interface{})  {
	v := reflect.ValueOf(a)
	k := v.Kind()
	switch k {
	case reflect.Float64:
		// v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
		fmt.Printf("type is float64,value is %v\n",float64(v.Float()))
	case reflect.Float32:
		// v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换
		fmt.Printf("type is float32,value is %v\n",float32(v.Float()))
	case reflect.Int64:
		// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
		fmt.Printf("type is int64,value is %v\n",int64(v.Int()))
	}
}

func main() {
	a := 3.1415
	var b int64 = 120
	c := 2.33333333
	GetTypeAndValue(a)  // type is float64,value is 3.1415
	GetTypeAndValue(b)  // type is int64,value is 120
	GetTypeAndValue(c)  // type is float64,value is 2.33333333

}

通过反射动态的设置变量的值
由于反射的两个入口函数:
func TypeOf (i interface{} ) Type
func ValueOf (i interface{} ) Value
的参数均为空接口,而将一个对象赋值给空接口,也就是将变量传入这两个函数时,接口变量会复制对象,所以想要通过反射来修改目标对象的话,就必须使用指针。

func ChangeValue1(a interface{}) {
	v := reflect.ValueOf(a)
	if v.Kind() == reflect.Int64 {
		v.SetInt(200) // panic: reflect: reflect.flag.mustBeAssignable using unaddressable value 因为这样修改的是副本
	}
}

func ChangeValue2(a interface{}) {
	v := reflect.ValueOf(a)
	if v.Elem().Kind() == reflect.Int64 {
		v.Elem().SetInt(200)
	}
}

func main() {
	var a int64 = 10
	ChangeValue2(&a)
	fmt.Println(a)  // 200

}

Go语言中接口有两种nil状态,nilGo语言中只能被赋值给指针和接口。接口在底层的实现有两个部分:typedata。在源码中,显式地将 nil 赋值给接口时,接口的 typedata 都将为 nil。此时,接口与 nil 值是相等的。但如果将一个带有类型的 nil 赋值给接口时,只有 datanil,而 type 不为 nil,此时,接口与 nil 将不相等。解决办法是用IsNil判断值是否为nil

IsNil()常被用于判断指针是否为空;

func main() {
	var a interface{} = nil
	var b interface{} = (*int)(nil)
	fmt.Println(a == nil,b == nil)  // true false
	fmt.Println(b == nil,reflect.ValueOf(b).IsNil())  // false true

}

IsValid()常被用于判定返回值是否有效。

func main() {
	b := struct {
	}{} // 实例化一个匿名结构体
	// 尝试在这个结构体中找出不存在的字段"abc"
	fmt.Println(reflect.ValueOf(b).FieldByName("abc").IsValid())  // false
	// 尝试在这个结构体中找出不存在的方法"abc"
	fmt.Println(reflect.ValueOf(b).MethodByName("abc").IsValid())  // false
	// 尝试在一个字段中找出一个不存在的key
	c := map[string]int{}
	fmt.Println(reflect.ValueOf(c).MapIndex(reflect.ValueOf("啦啦啦啦")).IsValid())  // false

}

结构体反射

任意值通过reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)NumField()Field()方法获得结构体成员的详细信息。

reflect.Type中与获取结构体成员相关的的方法如下表所示。

方法 说明
Field(i int) StructField 根据索引,返回索引对应的结构体字段的信息。
NumField() int 返回结构体成员字段数量。
FieldByName(name string) (StructField, bool) 根据给定字符串返回字符串对应的结构体字段的信息。
FieldByIndex(index []int) StructField 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。
FieldByNameFunc(match func(string) bool) (StructField,bool) 根据传入的匹配函数匹配需要的字段。
NumMethod() int 返回该类型的方法集中方法的数目
Method(int) Method 返回该类型方法集中的第i个方法
MethodByName(string)(Method, bool) 根据方法名返回该类型方法集中的方法

StructField类型
StructField类型用来描述结构体中的一个字段的信息。定义如下:

type StructField struct {
    // Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。
    // 参见http://golang.org/ref/spec#Uniqueness_of_identifiers
    Name    string
    PkgPath string
    Type      Type      // 字段的类型
    Tag       StructTag // 字段的标签
    Offset    uintptr   // 字段在结构体中的字节偏移量
    Index     []int     // 用于Type.FieldByIndex时的索引切片
    Anonymous bool      // 是否匿名字段
}

结构体反射示例:
当我们使用反射得到一个结构体对象之后可以通过索引依次获取其字段信息,也可以通过字段名去获取指定的字段信息。

type Student struct {
	name string
	age int
	school string
}

func main() {
	stu1 := Student{
		name:"小明",
		age:18,
		school:"北京大学",
	}
	v := reflect.TypeOf(stu1)
	fmt.Println(v.Name(),v.Kind())  // Student struct
	for i:=0;i<v.NumField();i++{
		field := v.Field(i)
		fmt.Println(field.Name)  // name,age,school
	}
}

编写一个函数遍历打印结构体中包含的方法:

type Student struct {
	name   string
	age    int
	school string
}

func (Student) Study() {
	fmt.Println("好好学习,天天向上")
}

func (Student) Sleep() {
	fmt.Println("好好休息,天天向上")
}

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)
		// 通过反射调用方法传递的参数必须是 []reflect.Value 类型
		var args = []reflect.Value{}
		v.Method(i).Call(args)
	}
}

func main() {
	stu1 := Student{
		name:   "小明",
		age:    18,
		school: "北京大学",
	}
	printMethod(stu1)
}

输出结果为:

2
method name:Sleep
method:func()
好好休息,天天向上
method name:Study
method:func()
好好学习,天天向上

性能

反射会对性能造成很大的损失,如果对性能有要求,谨慎使用反射。

你可能感兴趣的:(Golang,-,《Go语言学习笔记》)