go语言:反射

go语言:反射

1. 反射的应用

1.1 反射简介

  • go语言通过reflect包提供运行时反射机制
  • 静态类型interface{}、Type类型、Value类型

1.2 反射应用场景

  1. go语言API库封装。 Fmt.Printf
  2. go框架开发。
  3. 结构体标签的实现。

1.3 反射的基本使用

  1. 在运行时,动态的获取变量信息。值、类型、类别

  2. 在运行时,获取结构体的信息。字段名、方法

  3. 在运行时,修改变量指,调用方法。

2. 反射的原理

变量 – reflect.TypeOf() – reflect.Type 类型 – interface{kind() Field() NumField() … }

变量 – reflect.ValueOf() – reflect.Value 类型 – interface{kind() Type() Elem() Field() NumField() … }

在这里插入图片描述

3. 反射的实际编程使用

  • 变量 —— 空接口传参 —— interface —— reflect.ValueOf() —— reflect.Value类型 —— Interface() 方法—— interface —— 类型断言 —— 变量
  • 变量、interface 、reflect.Value类型 可以相互转换。

在这里插入图片描述

3.1 基础数据类型的类型转换

func reflectForint(i interface{}) {
	// 获取reflect.Value 类型
	reVal := reflect.ValueOf(i)

	// n := reVal + 100    // 不能使用value类型与int类型运算
	// fmt.Printf("%T, %v\n", reVal, reVal)

	// reflect.Value 类型 -- interface{}
	iVal := reVal.Interface()
	fmt.Printf("%T, %v\n", iVal, iVal)

	// n := iVal + 10     // 不能使用interface类型与int类型运算
	// 解决办法:使用类型断言将interface转换成int
	val := iVal.(int)
	n := val + 10
	fmt.Println(n)
}

func main() {
	// 定义基础类型变量
	var num int = 10

	// 变量 --> interface{}
	reflectForint(num)
}

3.2 结构体类型的转换

type Student struct {
	Name string
	Id int
	Addr string
}

func ReflectForStruct(i interface{}) {
	// interface -> reflect.Value
	reVal := reflect.ValueOf(i)
	reType := reflect.TypeOf(i)
	fmt.Printf("type=%v, val=%v\n", reType, reVal)

	// reflect.Value --> interface{}
	iVal := reVal.Interface()

	// 类型断言 interface{} --> struct
	// 只能用下面的val来索引成员
	if val, ok := iVal.(Student); ok {
		fmt.Printf("val=%v, iVal=%v, reVal=%v, val.Name=%v\n",
			val, iVal, reVal, val.Name)
	}
}

func main() {
	stu := Student{"alin", 2017002297, "北京"}
	// 从变量 --> interface{}
	ReflectForStruct(stu)
}

3.3 不使用类型断言进行类型转换

使用reflect.Kind()函数

func (v Value) Kind() Kind
const (
    Invalid Kind = iota
    Bool
    Int
    Int8
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Ptr
    Slice
    String
    Struct
    UnsafePointer
)
func reflectTest(i interface{}) {
	// 获取reflect.Value
	reVal := reflect.ValueOf(i)

	if reVal.Kind() == reflect.Int {
		n := reVal.Int() + 100
		fmt.Println("判断是一个int, n=", n)
	}
	if reVal.Kind() == reflect.Bool {
		fmt.Println("判断是一个bool, ", reVal.Bool())
	}
	if reVal.Kind() == reflect.Float64 {
		fmt.Println("判断是一个Float64, ", reVal.Float())
	}
	if reVal.Kind() == reflect.String {
		fmt.Println("判断是一个String, ", reVal.String()+"haha")
	}
}

func main() {
	var a int = 10
	var f float64 = 3.12
	var str string = "hoho"
	var bl bool = true

	reflectTest(a)
	reflectTest(f)
	reflectTest(str)
	reflectTest(bl)
}

3.4 kind和type区别

  • kind:类别。较type而言更大的范畴,更官方的范畴。

  • type:(不是reflect.Type,是数据类型)具体类型。较kind而言表示的范畴更具体

type Student4 struct {
	Name string
	Id int
	Addr string
}

func reflectForKind(i interface{}) {
	fmt.Printf("%T\n", i)
	reVal := reflect.ValueOf(i)
	fmt.Printf("%v\n", reVal.Kind())
}

func main() {
	// var num int = 10
	var stu Student4
	reflectForKind(stu)
}

输出结果:
main.Student4
struct

3.5 反射修改变量值

  1. 传参时需要传递地址值(引用)。 eg:reflectForSetInt(&num)
  2. 使用reflect.Value 类型的 SetXxx() 方法修改对应类型值。
  3. 必须借助 reflect.Value.Elem()来获取Value类型对应的变量的封装。有点像"解引用"。
func reflectForSetInt(iptr interface{}) {
	// 获取变量对应的reflect.Value
	reVal := reflect.ValueOf(iptr)

	fmt.Println("kind1:", reVal.Kind())
	// fmt.Printf("%T\n", iptr)
	fmt.Println("kind2:", reVal.Elem().Kind())

	if reVal.Kind() == reflect.Ptr && reVal.Elem().Kind() == reflect.Int {
		reVal.Elem().SetInt(666)
	}
}

func main() {
	var num int = 10
	reflectForSetInt(&num)
	fmt.Println("num=", num)
}

4. 反射操作结构体

  1. 定义带Tag的结构体类型

  2. 初始化结构体test := {xxx, xxx, ...}

  3. 获取reflect.Type类型和reflect.Value类型

  4. 循环获取结构体字段的信息

    1. 获取字段个数,使用NumField()方法

    2. 循环获取每一个字段的信息。 – StructField

      type StructField struct {
         // Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。
         // 参见http://golang.org/ref/spec#Uniqueness_of_identifiers
         Name    string
         PkgPath string
         Type      Type      // 字段的类型
         Tag       StructTag // 字段的标签
         Offset    uintptr   // 字段在结构体中的字节偏移量
         Index     []int     // 用于Type.FieldByIndex时的索引切片
         Anonymous bool      // 是否匿名字段
      }
      
      type StructTag string
      
      func (tag StructTag) Get(key string) string
      // 传入key值,获取tag的value
      
    3. 获取字段名 structField.Name

    4. 获取字段类型 structField.Type

    5. 获取字段值 reVal.Field(i)

const tagName = "Testing"

type Test struct {
	Name string `Testing:"-"`    // 序列化结果中没有这个属性
	Age int `Testing:"age,omitempty"`
	Id int `Testing:"idx,string"`
	Sex string `Testing:"sex"`
}

func main() {
	test := Test{"AAA", 18, 1001, "male"}

	reType := reflect.TypeOf(test)

	reVal := reflect.ValueOf(test)

	for i:=0; i<reType.NumField(); i++ {
		// 获取每一个字段的结构体描述信息StructField
		StructField := reType.Field(i)
		// 获取tag的值
		tagVal := StructField.Tag.Get(tagName)

		fmt.Printf("%d. %v(%v):%v, TAG:`%v`\n",
			i + 1, StructField.Name, StructField.Type, reVal.Field(i), tagVal)
	}
}
// 输出结果
1. Name(string):AAA, TAG:`-`
2. Age(int):18, TAG:`age,omitempty`
3. Id(int):1001, TAG:`idx,string`
4. Sex(string):male, TAG:`sex`

5. 反射使用欧冠结构体Tag

  1. 创建结构体
  2. 初始化对象 test, 传参(&test)
  3. 封装实现函数
  4. 获取reflect.Type,借助kind判断是否传参为ptr,进一步判断是否为struct
  5. 获取reflect.Value类型,使用Elem()方法得到Value对象reVal
  6. for 获取字段,以NumField()为循环上限
  7. 处理输出的标签名
    1. 获取结构体字段信息structField := reVal.Type().Field()
    2. 有tag标签
      1. 获取structField成员 – StructTag
      2. 根据已知的tag的key(“label”),获取对应的tag的value值。 Get()方法
    3. 没有tag标签的
      1. 判断Get()方法的返回值,如果为""
      2. 使用字段名,拼接标签名。structField.Name+":"
  8. 处理输入的数据值
    1. 取出字段的数据值保存成字符串。 Sprintf()/reVal.Field(i)
    2. 判断字段类型为string进一步处理if structField.Type.Kind() == reflect.String
    3. 进一步判断类型为string的字段的tag的key为"uppercase"的value是否为"true"
    4. 根据判断结果,使用stings.Toupper()、string.ToLower()
  9. 拼接标签名、数据值输出
type Person struct {
	Name        string `label:"Person Name: " uppercase:"true"`
	Age         int    `label:"Age is: "`
	Sex         string `label:"Sex is: "`
	Description string
}

func myMarshal(iptr interface{}) {
	// 获取Type
	reType := reflect.TypeOf(iptr)
	// 容错
	if reType.Kind() != reflect.Ptr || reType.Elem().Kind() != reflect.Struct {
		fmt.Println("输入的数据非指针类型")
		return
	}

	// 获取value
	reVal := reflect.ValueOf(iptr).Elem()

	// 获取字段信息及对应值
	for i:=0; i<reVal.NumField(); i++ {
		// fmt.Println(reVal.Field(i))
		// 获取字段信息 StructField
		structField := reVal.Type().Field(i)
		// 获取Tag成员
		tag := structField.Tag
		// 获取tag 中key对应的value值
		tagValue := tag.Get("label")
		// 判断如果tagValue为空,使用字段名作为tagValue
		if tagValue == "" {
			tagValue = structField.Name + ":"
		}
		// fmt.Println(tagValue)

		// 获取字段的数据值
		dataVal := fmt.Sprintf("%v", reVal.Field(i))
		// fmt.Println(dataVal)
		// 判断是否是string
		if structField.Type.Kind() == reflect.String {
			// 进一步判断是否tag中的uppercase的值为true
			if tag.Get("uppercase") == "true" {
				// 将对应的数据值转大写
				dataVal = strings.ToUpper(dataVal)
			} else {
				dataVal = strings.ToLower(dataVal)
			}
		}

		// 拼接输出
		fmt.Println(tagValue+dataVal)
	}
}

func main() {
	person := Person{"alin", 21, "Male", "COOL"}
	myMarshal(&person)
}
// 输出结果
Person Name: ALIN
Age is: 21
Sex is: male
Description:cool

6. 反射调用结构体方法

  1. 获取方法的个数
func (v Value) NumMethod() int
  1. 指定方法。参数从0开始计数。方法名:按ASCII码排序
func (v Value) Method(i int) Value
  1. 调用Method指定的方法。
func (v Value) Call(in []Value) []Value
  • 参数:传递给要调用的那个方法的参数
  • 返回值:要调用的方法的返回值。通过方法的下标(从0开始)指定。
type Student2 struct {
	Id int 		`json:"index"`
	Name string `json:"name"`
	Age int 	`json:"学员年龄"`
	Score float32
}

func (stu Student2) Print() {
	fmt.Println("------------stu:", stu)
}

// 注意:方法名,首字母必须大写。否则 reflect.Value 的 Method 找不到该方法。
func (stu Student2) Zdd(a, b int) int {
	return a + b
}

func (stu Student2) ResetInfo(id int, name string, age int, score float32) {
	stu.Id = id
	stu.Name = name
	stu.Age = age
	stu.Score = score
}

func useReflect(ref interface{})  {
	reType := reflect.TypeOf(ref)
	reVal := reflect.ValueOf(ref)

	reKind := reVal.Kind()
	if reKind != reflect.Struct {
		fmt.Println("Kind err!")
		return
	}
	numField := reVal.NumField()
	fmt.Println("Student 结构体包含字段数为:", numField)

	for i:=0; i<numField; i++{
		tagVal := reType.Field(i).Tag.Get("json")

		if tagVal != "" {
			fmt.Printf("%d. %v (%v) : %v, Tag : '%v'\n",
				i+1, reType.Field(i).Name, reType.Field(i).Type, reVal.Field(i), tagVal)
		} else {
			fmt.Printf("%d. %v (%v) : %v\n",
				i+1, reType.Field(i).Name, reType.Field(i).Type, reVal.Field(i))
		}
	}
	numMethod := reVal.NumMethod()
	fmt.Printf("Student 结构体含有 %d 个方法。\n", numMethod)

	// 调用一个方法 --- print
	reVal.Method(0).Call(nil)

	var params []reflect.Value
	params = append(params, reflect.ValueOf(123))
	params = append(params, reflect.ValueOf(543))

	// 调用一个方法  --- Add
	sum := reVal.Method(2).Call(params)
	fmt.Println("sum =", sum[0].Int())
}

func main() {
	stu := Student2{9527, "Andy", 18, 90.5}

	useReflect(stu)
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值