Go 反射

本文最后更新于:2 小时前

有时候我们需要写一个函数,这个函数有能力统一处理各种值类型。而这些类型可能无法共享同一个接口,也可能布局位置,也有课呢呢个这个类型在我们设计函数时候还不存咋,这个时候,我们就可以使用反射

1 空接口可以存储任意类型的变量,那我们如何知道这个接口保存的数据的类型是什么?值是什么?

  • 可以使用类型断言
  • 可以使用反射实现,也就是在程序运行时动态的获取一个变量的类型信息和值信息

2 把结构体序列化成json 字符串,自定义结构体Tab标签的时候 就用到了反射

反射的介绍

反射是指在程序运行期间对程序本身进行访问和修改的能力,正常情况程序在编译时候,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时候,程序无法获取自身的信息,支持反射的语言可以在程序编译期间将变量的发射信息。如字段信息,类型信息,结构体信息,等 整合到可执行文件中,并给程序员提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改他们

反射实现的功能

  • 反射可以在程序运行期间动态的获取变量的各种信息, 比如变量的类型
  • 如果是结构体,通过反射还可以获取结构体本身的信息, 比如结构体的字段结构体的方法
  • 通过反射, 可以修改变量的值,可以调用关联的方法

Go 语言中的变量是分为俩部分的

  • 类型信息:预先定义好的元信息
  • 值信息: 程序运行过程中可动态变化的

在Go语言中的反射机制中,任何接口值都是由一个具体类型和具体类型的值俩部分组成的

在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Type 和 reflect.Value

并且reflect包提供了reflect.TypeOf 和 reflect.ValueOf 俩个重要函数来获取任意对象的Value和Type

package main

import (
	"fmt"
	"reflect"
)

type myInt int
type Stu struct {
	Name string
	Age  int
}

func reflectFunc(x interface{}) {
	v := reflect.TypeOf(x)
	fmt.Println(v)
}
func main() {
	a := 10
	b := 23.4
	c := true
	d := "hello golang"
	reflectFunc(a)
	reflectFunc(b)
	reflectFunc(c)
	reflectFunc(d)
	var e myInt = 1

	reflectFunc(e)
	var s1 = Stu{
		Name: "bds",
		Age:  19,
	}
	reflectFunc(s1)
}
/*package main

import (
	"fmt"
	"reflect"
)

type myInt int
type Stu struct {
	Name string
	Age  int
}

func reflectFunc(x interface{}) {
	v := reflect.TypeOf(x)
	fmt.Println(v)
}
func main() {
	a := 10
	b := 23.4
	c := true
	d := "hello golang"
	reflectFunc(a)
	reflectFunc(b)
	reflectFunc(c)
	reflectFunc(d)
	var e myInt = 1

	reflectFunc(e)
	var s1 = Stu{
		Name: "bds",
		Age:  19,
	}
	reflectFunc(s1)
}
*/

type Name 和type Kind

在反射中关于类型划分为俩种: 类型(Type) 和 种类(Kind)。因为在Go语言中我们可以使用Type 关键字构造很多自定义类型,而种类(Kind) 就是底层数据类型,但是在反射中 当需要却分指针,结构体等大品种的类型时,就会用到种类(Kind)

举个列子我们定义了俩个指针类型和俩个结构体类型 通过反射查看他们的类型和种类。

Go语言中的反射 像数组 切牌呢 Map 指针等类型的变量 他们.Name()都是返回空

package main

import (
	"fmt"
	"reflect"
)

type myInt int
type Stu struct {
	Name string
	Age  int
}

func reflectFunc(x interface{}) {
	v := reflect.TypeOf(x)
	fmt.Printf("类型:%v类型名称:%v 底层类型种类:%v\n", v, v.Name(), v.Kind())
}
func main() {
	a := 10
	b := 23.4
	c := true
	d := "hello golang"
	reflectFunc(a)
	reflectFunc(b)
	reflectFunc(c)
	reflectFunc(d)
	var e myInt = 1

	reflectFunc(e)
	var s1 = Stu{
		Name: "bds",
		Age:  19,
	}
	reflectFunc(s1)
	var p1 = 1
	reflectFunc(&p1)
  var i = [3]int{1, 2, 3}
	var j = []int{11, 22, 33}
	reflectFunc(i)
	reflectFunc(j)
}
/*
类型:int类型名称:int 底层类型种类:int
类型:float64类型名称:float64 底层类型种类:float64
类型:bool类型名称:bool 底层类型种类:bool
类型:string类型名称:string 底层类型种类:string
类型:main.myInt类型名称:myInt 底层类型种类:int
类型:main.Stu类型名称:Stu 底层类型种类:struct
类型:*int类型名称: 底层类型种类:ptr
类型:[3]int类型名称: 底层类型种类:array
类型:[]int类型名称: 底层类型种类:slice

*/

通过ValueOf判断类型

package main

import (
	"fmt"
	"reflect"
)

func reflectFunc(x interface{}) {
	//我们直接进行想加 会报错
	//c := x + 10    // invalid operation: x + 10 (mismatched types interface {} and int

	//通过类型断言的方式 求值
	b, _ := x.(int)
	c := b + 10
	fmt.Println(c)
	//通过反射的方式   求值
	v := reflect.ValueOf(x)
	c1 := v.Int() + 11 //通过v.Int()获取原始值, 还支持其他类型 类型名称首字母大写
	fmt.Println(c1)

}
func main() {
	var a = 10
	reflectFunc(a)
}

通过ValueOf 和Kind 判断类型 获取变量的值

package main

import (
	"fmt"
	"reflect"
)

func reflectFunc(x interface{}) {
	v := reflect.ValueOf(x)
	kind := v.Kind()
	switch kind {
	case reflect.String:
		fmt.Printf("string类型原始值:%v\n", v.String())
	case reflect.Int:
		fmt.Printf("int类型的原始值:%v\n", v.Int()+10)

	default:
		fmt.Printf("没有匹配")
	}
}
func main() {
	var a = 10
	reflectFunc(a)
	b := "你好 golang"
	reflectFunc(b)
}

通过反射设置变量的值

package main

import (
	"fmt"
	"reflect"
)

func setFunc(x interface{}) {

	v := reflect.ValueOf(x)
  //如果是指针类型的变量 获取底层数据类型需要加Elem方法
	kind := v.Elem().Kind()
	if kind == reflect.Int {
    //设置值的时候 也需要加上Elem 
		v.Elem().SetInt(100)
	}
}
func main() {
	var a int = 10
  //修改值的话 我们这里传入指针类型变量
	setFunc(&a)
	fmt.Println(a)
}

结构体反射

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


package main

import (
	"fmt"
	"reflect"
)

type Stu1 struct {
	Name string `json:"name1"`
	Age  int    `json:"age1"`
	Sex  string `json:"sex1"`
}

func (s Stu1) GetInfo() string {
	str := fmt.Sprintf("姓名:%v, 年龄:%v,性别: %v", s.Name, s.Age, s.Sex)
	return str
}
func (s *Stu1) SetInfo(name string, age int, sex string) {
	s.Name = name
	s.Age = age
	s.Sex = sex
}
func (s Stu1) Print() {
	fmt.Println("我是一个Print打印方法")
}
func reflectStruct(s interface{}) {
	//判断传入的参数是不是结构体

	t := reflect.TypeOf(s)
	v := reflect.ValueOf(s)
	fmt.Println(t.Kind())
	if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
		fmt.Println("传入的不是结构体类型")
		return
	}
	//通过类型变量里面的Field可以获取结构体的字段
	field0 := t.Field(0)
	fmt.Printf("%#v\n", field0)
	fmt.Printf("字段名称:%v\n", field0.Name)
	fmt.Printf("字段类型:%v\n", field0.Type)
	fmt.Printf("tag标签:%v\n", field0.Tag.Get("json"))

	//通过类型变量里面的FieldByName可以获取结构体的字段
	fmt.Println("--------")
	field1, ok := t.FieldByName("Age")
	if ok {
		fmt.Println("字段名称:", field1.Name)
		fmt.Println("字段类型:", field1.Type)
		fmt.Println("字段Tag:", field1.Tag.Get("json"))

	}
	//通过类型变量里面的NumField获取该结构体有几个字段
	fmt.Println("--------")
	var count = t.NumField()
	fmt.Printf("结构体有 %v 个属性\n", count)
	//通过值变量 获取结构体属性对应的值
	fmt.Println("--------")
	fmt.Println(v.FieldByName("Name"))
	fmt.Println(v.FieldByName("Age"))
	fmt.Println("--------")
	for i := 0; i < count; i++ {
		fmt.Printf("属性名称:%v,属性的值:%v,属性类型:%v,属性Tag值:%v\n", t.Field(i).Name, v.Field(i), t.Field(i).Type, t.Field(i).Tag.Get("json"))
	}

}
func pStruct(s interface{}) {
	t := reflect.TypeOf(s)
	v := reflect.ValueOf(s)
	fmt.Println(t.Kind())

	if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
		fmt.Println("传入的不是结构体")
		return
	}
	//通过类型变量里面的Method方法可以获取结构体的方法
	fmt.Println("--------")
	m0 := t.Method(0)
	fmt.Println(m0.Name)
	fmt.Println(m0.Type)
	//通过类型变量获取这个结构体有多少方法
	fmt.Println("--------")
	m1, ok := t.MethodByName("Print")
	if ok {
		fmt.Println(m1.Name)
		fmt.Println(m1.Type)
	} else {
		fmt.Println("none")
	}
	//通过<<值变量>> 执行方法 (注意需要使用值变量,并且要注意参数)v.Method(0).Call(nil)
	fmt.Println("--------")
	v.Method(1).Call(nil)

	info := v.MethodByName("GetInfo").Call(nil)
	fmt.Println(info)

	//执行方法传入参数,(注意需要值变量,并且要注意参数,接收的参数是[]reflect.Value的切片)
	fmt.Println("--------")
	var params []reflect.Value
	params = append(params, reflect.ValueOf("李四"))
	params = append(params, reflect.ValueOf(19))
	params = append(params, reflect.ValueOf("girl"))

	v.MethodByName("SetInfo").Call(params) //给方法函数 传入参数
	info2 := v.MethodByName("GetInfo").Call(nil)
	//再次通过GetInfo 方法打印 结构体 看看是上面修改值 是否 已经改变
	fmt.Println(info2)
	//获取方法的数量
	fmt.Println("--------")
	fmt.Printf("方法数量:%v\n", t.NumMethod())

}
func changeStruct(s interface{}) {
	t := reflect.TypeOf(s)
	v := reflect.ValueOf(s)
	if t.Kind() != reflect.Ptr {
		fmt.Println("传入的不是结构体指针")
		return
	} else if t.Elem().Kind() != reflect.Struct {
		fmt.Println("传入的不是结构体")
		return
	}
	fmt.Println("----change struct-----")
	//修改结构体属性的值 (通过值变量)
	name := v.Elem().FieldByName("Name")
	name.SetString("李武")

	age := v.Elem().FieldByName("Age")
	age.SetInt(20)

}

func main() {
	var s1 = Stu1{
		Name: "bds",
		Age:  18,
		Sex:  "man",
	}
	fmt.Println("--- main1 -----")
	reflectStruct(s1)
	fmt.Println("--- main2 -----")
	pStruct(&s1) //因为要修改结构体 ,所以要传入指针类型
	fmt.Println("--- main3 -----")
	fmt.Println(s1)
	fmt.Println("--- main4 -----")
	changeStruct(&s1)
	fmt.Println(s1)  //再次打印 看值 是否修改成功
}
/*
--- main1 -----
struct
reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x10d1760), Tag:"json:\"name1\"", Offset:0x0, Index:[]int{0}, Anonymous:false}
字段名称:Name
字段类型:string
tag标签:name1
--------
字段名称: Age
字段类型: int
字段Tag: age1
--------
结构体有 3 个属性
--------
bds
18
--------
属性名称:Name,属性的值:bds,属性类型:string,属性Tag值:name1
属性名称:Age,属性的值:18,属性类型:int,属性Tag值:age1
属性名称:Sex,属性的值:man,属性类型:string,属性Tag值:sex1
--- main2 -----
ptr
--------
GetInfo
func(*main.Stu1) string
--------
Print
func(*main.Stu1)
--------
我是一个Print打印方法
[姓名:bds, 年龄:18,性别: man]
--------
[姓名:李四, 年龄:19,性别: girl]
--------
方法数量:3
--- main3 -----
{李四 19 girl}
--- main4 -----
----change struct-----
{李武 20 girl}

*/

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!