Go进阶-Go语言反射

TOC

了解反射

什么是反射?
反射(reflection)是一种在运行时检查、操作变量的类型和值的机制或能力。

为什么需要用反射?
需要反射的 2 个常见场景:

  • 1.有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
  • 2.有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。

变量有三个重要组成部分:

  • Type(类型): int、string、struct、切片等
  • Value(值): 具体存储的数据,例如 42、“hello”
  • Kind(种类): 类型的底层分类(int、float、slice、struct、map…)

基本数据类型反射

我们一般用到的包是reflect包。

Type和Value

它提供了两种类型(或者说两个方法)让我们可以很容易的访问接口变量内容,分别是reflect.ValueOf()reflect.TypeOf()
【示例】代码如下:

package main

import (
	"fmt"
	"reflect"
)

func lookReflect(data interface{}) {
	dataType := reflect.TypeOf(data)   // 获取a变量的类型,返回reflect.Type类型
	dataValue := reflect.ValueOf(data) // 获取a变量的值,返回reflect.Value类型
	fmt.Println(data, "的Type是:", dataType, ",", data, "的Value:", dataValue)
}

func main() {
	var (
		a int     = 100
		b string  = "apple"
		c float64 = 3.1415
	)
	lookReflect(a)
	lookReflect(b)
	lookReflect(c)
}
/* 运行结果如下
100 的Type是: int , 100 的Value: 100
apple 的Type是: string , apple 的Value: apple
3.1415 的Type是: float64 , 3.1415 的Value: 3.1415
*/

反射的使用

反射的使用语法:

// 方法一:使用Interface()方法 
realValue := value.Interface().(已知的类型)
// 方法二:直接使用转换方法比如:Int()、String()等
realValue := value.Int() // 转换成int64类型
realValue := value.String() // 转换成string类型
......

【示例】代码如下:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var (
		a int = 100
		b int = 200
	)
	aValue := reflect.ValueOf(a) // 获取a变量的值,返回reflect.Value类型
	aData := aValue.Interface().(int) // 获得接口变量的真实内容再转换已知类型
	bValue := reflect.ValueOf(b) // 获取b变量的值,返回reflect.Value类型
	bData := bValue.Int() // 直接转换已知类型
	fmt.Printf("aData的值为: %d,aData的数据类型为: %T\n" + 
		"bData的值为: %d,bData的数据类型为: %T\n", aData, aData, bData, bData)
}
/* 运行结果如下
aData的值为: 100,aData的数据类型为: int
bData的值为: 200,bData的数据类型为: int64
*/

补充说明:
1.转换的时候,如果转换的类型不完全符合,则直接panic,类型要求非常严格!
2.转换的时候,要区分是指针还是指
3.也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”

结构体反射

结构体的反射操作跟基本数据类型反射差不多,只需在类型断言时类型为结构体名字即可。
【示例】代码如下:

package main

import (
	"fmt"
	"reflect"
)

func structReflect(data interface{}) {
	dataType := reflect.TypeOf(data)   // 获取a变量的类型,返回reflect.Type类型
	dataValue := reflect.ValueOf(data) // 获取a变量的值,返回reflect.Value类型
	fmt.Println(data, "的Type是:", dataType, ",", data, "的Value:", dataValue)

	// reValue转成空接口
	s1 := dataValue.Interface()
	// 类型断言
	s, ok := s1.(Student)
	if ok {
		fmt.Println("学生的名字是:", s.Name, "学生的年龄是:", s.Age)
	}
}

// Student 是定义的一个结构体做试验
type Student struct {
	Name string
	Age  int
}

func main() {
	// 初始化结构体值
	student := Student{
		Name: "张三",
		Age:  18,
	}
	structReflect(student) // 调用方法传入赋值后结构体
}
/* 运行结果如下
{张三 18} 的Type是: main.Student , {张三 18} 的Value: {张三 18}
学生的名字是: 张三 学生的年龄是: 18
*/

反射操作

获取变量体类别

使用语法:

typeKind := reflect.TypeOf(变量名).Kind() // Type.Kind()查看
valueKind := reflect.ValueOf(变量名).Kind() // Value.Kind()查看

【示例】代码如下:

package main

import (
	"fmt"
	"reflect"
)

func lookKind(data interface{}) {
	typeKind := reflect.TypeOf(data).Kind() // Type.Kind()查看
	fmt.Println("data的类别:", typeKind)
	valueKind := reflect.ValueOf(data).Kind() // Value.Kind()查看
	fmt.Println("data的类别:", valueKind)
}

// Student 是定义的一个结构体做试验
type Student struct {
	Name string
	Age  int
}

func main() {
	// 初始化结构体值
	student := Student{
		Name: "张三",
		Age:  18,
	}
	lookKind(student) // 调用方法传入赋值后结构体查看变量类别
}
/* 运行结果如下
data的类别: struct
data的类别: struct
*/

修改变量值

使用语法:

// 修改基本变量值
reflect.ValueOf(变量指针).Elem().SetInt(修改值)
// 修改结构体属性值
reflect.ValueOf(结构体变量指针).Elem().Field(结构体属性索引).SetString(修改值)
reflect.ValueOf(结构体变量指针).Elem().Field(结构体属性索引).SetInt(修改值)

【示例】代码如下:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// 定义初始化变量值
	var (
		a int    = 100
		b string = "apple"
	)
	aValue := reflect.ValueOf(&a)     // 获取a变量的值,返回reflect.Value类型,一定为指针
	bValue := reflect.ValueOf(&b)     // 获取b变量的值,返回reflect.Value类型,一定为指针
	aValue.Elem().SetInt(40)          // 修改a变量的值
	bValue.Elem().SetString("banana") // 修改b变量的值
	fmt.Println("a修改后的值:", a, "b修改后的值:", b)
	// 修改结构体的值
	// 定义和初始化结构体值
	type Student struct {
		Name string
		Age  int
	}
	student := Student{
		Name: "张三",
		Age:  20,
	}
	studentValue := reflect.ValueOf(&student)
	// 修改结构体第一个索引变量的值
	studentValue.Elem().Field(0).SetString("李四")
	// 修改结构体第二个索引变量的值
	studentValue.Elem().Field(1).SetInt(21)
	fmt.Println("修改后的Student结构体的值:", student)
}
/* 运行结果如下
a修改后的值: 40 b修改后的值: banana
修改后的Student结构体的值: {李四 21}
*/

操作结构体属性和方法

使用语法:

// 获取结构体的字段数量
reflect.ValueOf(结构体对象).NumField()
// 结构体第几个变量的值
reflect.ValueOf(结构体对象).Field(字段索引)
// 获取结构体内部方法数量
reflect.ValueOf(结构体对象).NumMethod()
// 调用结构体内部方法,多个变量传入[]reflect.Value切片类型
dataValue.Method(方法索引).Call(变量参数)

【示例】代码如下:

package main

import (
	"fmt"
	"reflect"
)

type People struct { // 定义一个结构体
	Name string
	Age  int
}

func (p People) PrintInfo() {
	fmt.Println("调用了PrintInfo()方法...")
	fmt.Printf("name: %s, age: %d\n", p.Name, p.Age)
}

func (p People) Add(a, b int) int {
	fmt.Println("调用了Add()方法...")
	return a + b
}

func main() {
	// 初始化结构体
	people := People{
		Name: "alice",
		Age:  20,
	}
	dataValue := reflect.ValueOf(people)
	fmt.Println("结构体的字段数量:", dataValue.NumField())
	for i := 0; i < dataValue.NumField(); i++ {
		fmt.Printf("第%d个字段的值是: %v\n", i+1, dataValue.Field(i))
	}

	// 通过reflect.Value类型操作结构体内部的方法
	methodNums := dataValue.NumMethod()
	fmt.Println("People结构体的方法数量:", methodNums)

	/* 调用的方法必须首字母大写才能有对应的反射的访问权限
	   方法的顺序按照ASCII的顺序排列,索引:0,1,2,3
	*/
	// 调用PrintInfo()方法
	dataValue.Method(1).Call(nil)

	// 调用Add()方法
	// 由于Call()方法调用传入多个变量需要传入切片类型,所以需要定义一个切片
	var params []reflect.Value
	params = append(params, reflect.ValueOf(7))
	params = append(params, reflect.ValueOf(9))
	addNum := dataValue.Method(0).Call(params) // 返回值类型是[]reflect.Value,需要处理转型
	fmt.Println("调用Add()返回的值:", addNum[0].Int())
}
/* 运行结果如下
结构体的字段数量: 2
第1个字段的值是: alice
第2个字段的值是: 20
People结构体的方法数量: 2
调用了PrintInfo()方法...
name: alice, age: 20
调用了Add()方法...
调用Add()返回的值: 16
*/