golang反射

来自:博客园
时间:2024-02-26
阅读:

反射

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

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

反射的功能

1、反射可以在程序运行期间动态的获取变量的各种信息,比如变量的类型 类别

2、如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、结构体的方法、结构体的 tag。

3、通过反射,可以修改变量的值,可以调用关联的方法

Go语言中的变量是分为两部分的:

类型信息:预先定义好的元信息。

值信息:程序运行过程中可动态变化的。

在 GoLang 的反射机制中,任何接口值都由是一个具体类型具体类型的值两部分组成的。

在 GoLang 中,反射的相关功能由内置的 reflect 包提供,任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成,并 且 reflect 包 提 供 了 reflect.TypeOfreflect.ValueOf 两个重要函数来获取任意对象的 Value 和 Type

reflect.TypeOf() 获取任意值类型对象

使用 reflect.TypeOf()函数可以接受任意 interface{}参数,可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息

在反射中关于类型还划分为两种:类型(Type)和种类(Kind)

因为在 Go 语言中我们可以使用 type 关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,

当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind。 举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。

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


import (
	"fmt"
	"reflect"
)



func reflectFn(x interface{}) {
	// 反射获取任意变量类型
	v := reflect.TypeOf(x)
	name := v.Name() // 类型名称
  kind := v.Kind() // 种类(底层类型)
	fmt.Printf("类型是%v,类型名称是%v,种类是%v \n", v, name, kind)

}

// 自定义一个myInt类型
type myInt int

// Person结构体
type Person struct {
	Name string
	Age  int
}

func mAIn() {
	a := 10
	b := "s"

	// 打印基本类型
	reflectFn(a) // 类型是int,类型名称是int,种类是int
	reflectFn(b) // 类型是string,类型名称是string,种类是string

	// 打印自定义类型和结构体类型
	var i myInt = 3
	var p = Person{
		"1",
		2,
	}

	reflectFn(i) // 类型是main.myInt,类型名称是myInt,种类是int
	reflectFn(p) // 类型是main.Person,类型名称是Person,种类是struct

	// 打印指针类型
	var f = 25
	reflectFn(&f) // 类型是*int,类型名称是,种类是ptr

}


reflect.ValueOf() 获取原始值

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

golang反射

通过反射获取原始值
func reflectValue(x interface{}) {
   // 通过反射获取到值
   v := reflect.ValueOf(x)
   fmt.Printf("%v, %T \n", v, v) // 10, reflect.Value,获取到的类型是reflect.Value

   fmt.Printf("%v,%T", v.Int(), v.Int()) // 10,int64,获取到原始值和类型,因为传入是数字,所以是v.Int()
}

func main() {
   var i = 10
   reflectValue(i)
func reflectValue(x interface{}) {
	v := reflect.ValueOf(x)
	kind := v.Kind() // 获取种类

	// 判断底层种类  
	Switch kind {
	// 是int类型
	case reflect.Int64:
		fmt.Println(v.Int())
	case reflect.Float64:
		fmt.Println(v.Float())
	case reflect.String:
		fmt.Println(v.String())
		
	
	}

}
通过反射设置变量的值
import (
	"fmt"
	"reflect"
)

func reflectValue(x interface{}) {
	v := reflect.ValueOf(x)

  // 如果传入的值是一个指针地址,那需要通过Elem().Kind()获取具体种类,Elem()返回 Type 对象所表示的指针指向的数据
	// 如果只是v.kind,获取的到的是指针类型
	kind := v.Elem().Kind() // int64
	if kind == reflect.Int64 {
		//  int是SetInt  string是SetString等
		v.Elem().SetInt(3)
	}
}

func main() {
	var i = 10

	// 值类型修改副本不会影响原始值,所以需要通过指针地址
	reflectValue(&i)
	fmt.Println(i) // 3

}

结构体反射

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

golang反射

通过类型变量获取结构体的属性信息
import (
	"fmt"
	"reflect"
)

// studen结构体
type Student struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	Score int    `json:"score"`
}

// 结构体方法 获取学生信息
func (s Student) GetInfo() string {
	return fmt.Sprintf("姓名%v 年龄%v 成绩%v", s.Name, s.Age, s.Score)

}

// 结构体方法 修改学生信息
func (s *Student) SetInfo(name string, age, score int) {
	s.Name = name
	s.Age = age
	s.Score = score

}

// 结构体方法 打印
func (s Student) PrintInfo() {
	fmt.Println("print info")

}

func PrintStructField(s interface{}) {
	// 获取类型对象
	t := reflect.TypeOf(s)
  /*

		通过类型变量里面的Field获取结构体字段

	*/
	// 通过类型变量.Field(下标)可以获取结构体的字段对象
	field0 := t.Field(0)


	// 获取到结构体的Name字段对象
	fmt.Println(field0) // {Name  string json:"name" 0 [0] false}

	fmt.Printf("%#v \n", field0) // reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0xe748c80), Tag:"json:\"name\"", Offset:0x0, Index:[]int{0}, Anonymous:false}
	// 获取字段名称
	fmt.Println(field0.Name) // Name
	// 获取字段类型
	fmt.Println(field0.Type) // string
	// 获取字段tag - json类型
	fmt.Println(field0.Tag.Get("json")) // name

	/*
		通过类型变量里面的FieldByName获取结构体字段
	*/
	// FieldByName返回两个值,一个是具体的值,一个是是否成功
	field1, ok := t.FieldByName("Age")
	if ok {

		fmt.Println(field1.Name)            // 字段名称:Age
		fmt.Println(field1.Type)            // 字段类型:int
		fmt.Println(field1.Tag.Get("json")) // 字段的json类型的tag :age
	}

	/*
	  通过类型变量的NumField获取该结构体有多少个字段
	*/

	var count = t.NumField()
	fmt.Println(count) // 3
}

func main() {

	var student = Student{"小李", 15, 100}
	PrintStructField(student)

}

通过值变量获取结构体值的值信息
func PrintStructField(s interface{}) {	
	/*
	 通过值变量获取结构体属性对应的值
	*/
	v := reflect.ValueOf(s)
	// 方式一
	fmt.Println(v.FieldByName("Name")) // 小李
	fmt.Println(v.FieldByName("Age"))  // 15
	// 方式二  可以通过循环获取所有的属性值
	fmt.Println(v.Field(2)) // 100
  
}
通过反射修改结构体的属性值
func ReflectSetValue(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
	}
	// 获取Name字段的指针值
	name := v.Elem().FieldByName("Name")
	// 修改属性值
	name.SetString("小王")
}

func main() {

	var student = Student{"小李", 15, 100}
	// 如果传入一个值接收者,没有办法修改结构体的值,如果要修改对应值,需要传入指针接收者
	ReflectSetValue(&student)
	fmt.Println(student.Name) // 小王

}

通过类型变量获取结构体方法
// 打印结构体方法
func PrintStructFn(s interface{}) {
	t := reflect.TypeOf(s)

	// 通过类型变量的Method(下标)获取结构体的方法
	method0 := t.Method(0)    // 下标取的方法和结构体的顺序没有关系,和结构体的ASCII码有关系
	fmt.Println(method0.Name) // 下标为0的方法名:GetInfo
	fmt.Println(method0.Type) // func(main.Student) string

	// 通过MethodByName 获取结构体方法
	// 该方法两个返回值,一个是方法名,一个是是否有该方法的状态
	method1, ok := t.MethodByName("PrintInfo")
	if ok {
		fmt.Println(method1.Name) // PrintInfo

		fmt.Println(method1.Type) // func(main.Student)

	}
	// 获取结构体一共有几个方法
	fmt.Println(t.NumMethod()) // 2 Student结构体定义了3个方法,有两个接收者类型是结构体,有一个接收者类型是指针接收者,如果s接收的是值接收者,只能统计值接收者的方法数量,如果是指针接收者,可以统计所有的方法的数量

}

func main() {

	var student = Student{"小李", 15, 100}
	PrintStructFn(student)

}

通过值变量执行结构体方法
func RunStructFn(s interface{}) {
	v := reflect.ValueOf(s)
	// 方法一 通过下标执行对应的方法,Call传递对应参数,nil代表没有参数
	v.Method(1).Call(nil) // print info

	// 方法二 通过MethodByName执行对应方法,返回值是一个切片
	// 传入参数调用,Call方法传入的参数需要是一个reflect.Value类型的切片
	var params []reflect.Value
	params = append(params, reflect.ValueOf("小李"))
	params = append(params, reflect.ValueOf(20))
	params = append(params, reflect.ValueOf(95))
	v.MethodByName("SetInfo").Call(params)

	fmt.Println(v.MethodByName("GetInfo").Call(nil)) // [姓名小李 年龄20 成绩95]

}

func main() {

	var student = Student{"小李", 15, 100}
	// 如果传入一个值接收者,没有办法修改结构体的值,如果要修改对应值,需要传入指针接收者
	RunStructFn(&student)

}

返回顶部
顶部