5

golang反射 - 木子七

 6 months ago
source link: https://www.cnblogs.com/Mickey-7/p/18032760
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

golang反射

反射#

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

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

反射的功能#

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 与原始值之间可以互相转换

image-20240225153112288
通过反射获取原始值#
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()方法获得结构体成员的详细信息

image-20240225160352855
通过类型变量获取结构体的属性信息#
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)

}


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK