首页 > 文章列表 > Go语言学习之反射的用法详解

Go语言学习之反射的用法详解

golang
209 2022-12-17

反射指的是运行时动态的获取变量的相关信息

1. reflect 包

类型是变量,类别是常量

reflect.TypeOf,获取变量的类型,返回reflect.Type类型

reflect.ValueOf,获取变量的值,返回reflect.Value类型

reflect.Value.Kind,获取变量的类别,返回一个常量

reflect.Value.Interface(),转换成interface{}类型

1.1 获取变量类型

package main



import (

	"fmt"

	"reflect"

)



func Test(i interface{}) {

	//反射数据类型

	t := reflect.TypeOf(i)

	fmt.Println("类型是", t)

	//反射数据值

	v := reflect.ValueOf(i)

	fmt.Println("值是", v)

}



func main() {

	a := "hello"

	Test(a)

}

输出结果如下

类型是 string

值是 hello

package main



import (

	"fmt"

	"reflect"

)



type Student struct {

	Name  string

	Age   int

	Score float32

}



func Test(i interface{}) {

	//反射获取类型

	t := reflect.TypeOf(i)

	fmt.Println("类型是", t)



	//反射获取值

	v := reflect.ValueOf(i)

	//判断值的类别

	c := v.Kind()

	fmt.Println("类别是", c)

}



func main() {

	var stu Student = Student{

		Name:  "张三",

		Age:   18,

		Score: 80,

	}

	Test(stu)



	fmt.Println("-------------")

	var num int = 10

	Test(num)

}

输出结果如下

类型是 main.Student

类别是 struct

-------------

类型是 int

类别是 int

1.2 断言处理类型转换

package main



import (

	"fmt"

	"reflect"

)



type Student struct {

	Name  string

	Age   int

	Score float32

}



func Test(i interface{}) {

	t := reflect.TypeOf(i)

	fmt.Println("类型是", t)



	//类别

	v := reflect.ValueOf(i)

	c := v.Kind()

	fmt.Println("类别是", c)

	fmt.Printf("c的类型是%T\n", c)

	fmt.Printf("v的类型是%T\n", v)



	//转换成接口

	iv := v.Interface()

	fmt.Printf("iv的类型%T\n", iv)

	//断言处理

	stu_iv, err := iv.(Student)

	if err {

		fmt.Printf("stu_iv的类型%T\n", stu_iv)

	}

}



func main() {

	var stu Student = Student{

		Name:  "张三",

		Age:   18,

		Score: 80,

	}

	Test(stu)



}

输出结果如下

类型是 main.Student

类别是 struct

c的类型是reflect.Kind

v的类型是reflect.Value

iv的类型main.Student

stu_iv的类型main.Student

2. ValueOf

2.1 获取变量值

reflect.valueof(x).Float()

reflect.valueof(x).Int()

reflect.valueof(x).String()

reflect.Valueof(x).Bool()

2.2 类型转换

package main



import (

	"fmt"

	"reflect"

)



func Test(i interface{}) {

	v := reflect.ValueOf(i)

	fmt.Printf("v的类型是%T\n", v)

	//转换成指定类型

	t := v.Int()

	fmt.Printf("t的类型是%T\n", t)

}



func main() {

	//类型不同的话会报错

	var num int = 100

	Test(num)

}

输出结果如下

v的类型是reflect.Value

t的类型是int64

3. Value.Set

3.1 设置变量值

reflect.Value.SetFloat(),设置浮点数

reflect.value.SetInt(),设置整数

reflect.Value.SetString(),设置字符串

3.2 示例

package main



import (

	"fmt"

	"reflect"

)



func Test(i interface{}) {

	v := reflect.ValueOf(i)

	//更新值需要value的地址,否则会保存,Elem()表示指针*

	v.Elem().SetInt(100)

	result := v.Elem().Int()

	fmt.Printf("result类型为 %T, 值为 %d\n", result, result)

}



func main() {

	var num int = 10

	Test(&num)

}

输出结果如下

result类型为 int64, 值为 100

4. 结构体反射

反射出结构体的属性和方法数量

方法名需大写,需要被跨包调用识别

4.1 查看结构体字段数量和方法数量

package main



import (

	"fmt"

	"reflect"

)



//结构体

type Student struct {

	Name  string

	Age   int

	Score float32

}



//结构体方法

func (s Student) Run() {

	fmt.Println("Running")

}



func (s Student) Sleep() {

	fmt.Println("Sleeping")

}



//使用反射查看结构体字段数量和方法数量

func Test(i interface{}) {

	v := reflect.ValueOf(i)

	//类别判断

	if v.Kind() != reflect.Struct {

		fmt.Println("不是结构体")

		return

	}

	//获取结构体中字段数量

	stu_num := v.NumField()

	fmt.Println("字段数量: ", stu_num)

	//获取结构体中方法数量

	stu_meth := v.NumMethod()

	fmt.Println("方法数量: ", stu_meth)

}



func main() {

	var stu Student = Student{

		Name:  "张三",

		Age:   18,

		Score: 88,

	}

	Test(stu)

}

输出结果如下

字段数量:  3

方法数量:  2

4.2 获取结构体属性

package main



import (

	"fmt"

	"reflect"

)



type Student struct {

	Name  string

	Age   int

	Score float32

}



func Test(i interface{}) {

	v := reflect.ValueOf(i)

	//获取结构体中每个属性

	for i := 0; i < v.NumField(); i++ {

		//输出属性值

		fmt.Printf("%d %v\n", i, v.Field(i))

		//输出属性值的类型

		fmt.Printf("%d %v\n", i, v.Field(i).Kind())

	}

}



func main() {

	var stu Student = Student{

		Name:  "张三",

		Age:   18,

		Score: 88,

	}

	Test(stu)

}

输出结果如下

0 张三

0 string

1 18

1 int

2 88

2 float32

4.3 更改属性值

package main



import (

	"fmt"

	"reflect"

)



type Student struct {

	Name  string

	Age   int

	Score float32

}



func Test(i interface{}, name string) {

	v := reflect.ValueOf(i)

	vk := v.Kind()

	//判断是都为指针并指向结构体类型

	if vk != reflect.Ptr && v.Elem().Kind() == reflect.Struct {

		fmt.Println("expect struct")

		return

	}

	//更改属性值

	v.Elem().Field(0).SetString(name)

	//获取结构体中每个属性

	for i := 0; i < v.Elem().NumField(); i++ {

		//输出属性值

		fmt.Printf("%d %v\n", i, v.Elem().Field(i))

		//输出属性值的类型

		fmt.Printf("%d %v\n", i, v.Elem().Field(i).Kind())

	}

}



func main() {

	var stu Student = Student{

		Name:  "张三",

		Age:   18,

		Score: 88,

	}

	Test(&stu, "李四")

}

输出结果如下

0 李四

0 string

1 18

1 int

2 88

2 float32

4.4 Tag原信息处理

package main



import (

	"encoding/json"

	"fmt"

	"reflect"

)



type Student struct {

	Name  string `json:"stu_name"`

	Age   int

	Score float32

}



func Test(i interface{}, name string) {

	v := reflect.ValueOf(i)

	vk := v.Kind()

	//判断是都为指针并指向结构体类型

	if vk != reflect.Ptr && v.Elem().Kind() == reflect.Struct {

		fmt.Println("expect struct")

		return

	}

	//更改属性值

	v.Elem().Field(0).SetString(name)

	//获取结构体中每个属性

	for i := 0; i < v.Elem().NumField(); i++ {

		//输出属性值

		fmt.Printf("%d %v\n", i, v.Elem().Field(i))

		//输出属性值的类型

		fmt.Printf("%d %v\n", i, v.Elem().Field(i).Kind())

	}

}



func main() {

	var stu Student = Student{

		Name:  "张三",

		Age:   18,

		Score: 88,

	}

	Test(&stu, "李四")

	fmt.Println("----------------json原信息----------------")

	result, _ := json.Marshal(stu)

	fmt.Println("json原信息: ", string(result))

	//反射获取类型

	st := reflect.TypeOf(stu)

	s := st.Field(0)

	fmt.Printf("Name原信息名称: %s\n", s.Tag.Get("json"))

}

输出结果如下

0 李四

0 string

1 18

1 int

2 88

2 float32

----------------json原信息----------------

json原信息:  {"stu_name":"李四","Age":18,"Score":88}

Name原信息名称: stu_name

5. 函数反射

Go 中函数是可以赋值给变量的

示例:

既然函数可以像普通的类型变量一样,那么在反射机制中就和不同的变量是一样的,在反射中函数和方法的类型(Type)都是reflect.Func,如果要调用函数,通过 Value 的Call() 方法

package main



import (

	"fmt"

	"reflect"

)



func hello() {

	fmt.Println("hello world")

}



func main() {

	//反射使用函数

	v := reflect.ValueOf(hello)

	//类型判断是否属于reflect.func类型

	if v.Kind() == reflect.Func {

		fmt.Println("函数")

	}

	//反射调用函数

	v.Call(nil)   //Call中需要传入的是切片

}

输出结果如下

函数

hello world

package main



import (

	"fmt"

	"reflect"

	"strconv"

)



//反射调用传参和返回值函数

func Test(i int) string {

	return strconv.Itoa(i)

}



func main() {

	v := reflect.ValueOf(Test)

	//定义参数切片

	params := make([]reflect.Value, 1)

	//切片元素赋值

	params[0] = reflect.ValueOf(20)

	//反射调函数

	result := v.Call(params)

	fmt.Printf("result的类型是 %T\n", result)

	//[]reflect.Value切片转换string

	s := result[0].Interface().(string)

	fmt.Printf("s的类型是 %T ,值为 %s\n", s, s)

}

输出结果如下

result的类型是 []reflect.Value

s的类型是 string ,值为 20

6. 方法反射

反射中方法的调用,函数和方法可以说其实本质上是相同的,只不过方法与一个“对象”进行了“绑定”,方法是“对象”的一种行为,这种行为是对于这个“对象”的一系列操作,例如修改“对象”的某个属性

6.1 使用 MethodByName 名称调用方法

package main



import (

	"fmt"

	"reflect"

	"strconv"

)



//反射方法

type Student struct {

	Name string

	Age  int

}



//结构体方法

func (s *Student) SetName(name string) {

	s.Name = name

}



func (s *Student) SetAge(age int) {

	s.Age = age

}



func (s *Student) String() string {

	return fmt.Sprintf("%p", s) + ",Name:" + s.Name + ",Age:" + strconv.Itoa(s.Age)

}



func main() {

	//实例化

	stu := &Student{"张三", 19}

	//反射获取值:指针方式

	stuV := reflect.ValueOf(&stu).Elem()

	fmt.Println("修改前: ", stuV.MethodByName("String").Call(nil)[0])

	//修改值

	params := make([]reflect.Value, 1)		//定义切片

	params[0] = reflect.ValueOf("李四")

	stuV.MethodByName("SetName").Call(params)

	params[0] = reflect.ValueOf(20)

	stuV.MethodByName("SetAge").Call(params)

	fmt.Println("修改后: ", stuV.MethodByName("String").Call(nil)[0])

}

输出结果如下

修改前:  0xc000004078,Name:张三,Age:19

修改后:  0xc000004078,Name:李四,Age:20

6.2 使用 method 索引调用方法

package main



import (

	"fmt"

	"reflect"

	"strconv"

)



//反射方法

type Student struct {

	Name string

	Age  int

}



//结构体方法

func (s *Student) B(name string) {

	s.Name = name

}



func (s *Student) A(age int) {

	s.Age = age

}



func (s *Student) C() string {

	return fmt.Sprintf("%p", s) + ",Name:" + s.Name + ",Age:" + strconv.Itoa(s.Age)

}



func main() {

	//实例化

	stu := &Student{"张三", 19}

	//反射获取值:指针方式

	stuV := reflect.ValueOf(&stu).Elem()



	//索引调用方法

	fmt.Println("修改前: ", stuV.Method(2).Call(nil)[0])



	params := make([]reflect.Value, 1)

	params[0] = reflect.ValueOf("李四")

	stuV.Method(1).Call(params)



	params[0] = reflect.ValueOf(20)

	stuV.Method(0).Call(params)

	fmt.Println("修改后: ", stuV.Method(2).Call(nil)[0])

	//调用索引大小取决于方法名称的ASCII大小进行排序

}

输出结果如下

修改前:  0xc000004078,Name:张三,Age:19

修改后:  0xc000004078,Name:李四,Age:20