ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Go-反射编程

2021-07-15 22:59:45  阅读:253  来源: 互联网

标签:反射 编程 Name ValueOf Elem reflect TypeOf Go FieldByName


目录

一、反射编程

1、reflect

2、反射的特点

3、kind

4、获取反射回来的类型

5、利用反射编写灵活的程序

a、按名字访问结构的成员

b、按名字访问结构体的方法

c、Elem()

6、Struct Tag

a、struct tag 的格式

b、用途

二、万能程序

1、DeepEqual

a、用 DeepEqual() 比较 map

b、用 DeepEqual() 比较 slice

3、用反射实现万能程序

三、总结


一、反射编程

反射最大的用途可能就是通过字符串或者以字符的形式来调用类型中的某一个方法,或者通过传入变量或者方法的名字访问某一个成员。

1、reflect

反射类型:reflect.TypeOf() 

反射值:reflect.Value()

2、反射的特点

  • 提高了程序的灵活性;
  • 降低了程序的可读性;
  • 降低了程序的性能。

3、kind

当我们需要对反射回来的类型做判断时,Go 语言内置了一个枚举,可以通过 kind() 来返回这个枚举值。

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	//...
)

4、获取反射回来的类型

package reflect_test

import (
	"fmt"
	"reflect"
	"testing"
)

//检查反射类型
//用空接口接收任意类型
func CheckType(v interface{})  {
	t := reflect.TypeOf(v)
	switch t.Kind() {
	case reflect.Int, reflect.Int32, reflect.Int64:
		fmt.Println("Int")
	case reflect.Float32, reflect.Float64:
		fmt.Println("Float")
	default:
		fmt.Println("unknown type")
	}
}

func TestBasicType(t *testing.T)  {
	var f float32 = 1.23
	CheckType(f)
}
/*
=== RUN   TestBasicType
Float
--- PASS: TestBasicType (0.00s)
PASS
*/

5、利用反射编写灵活的程序

a、按名字访问结构的成员

reflect.TypeOf() 和 reflect.ValueOf() 都有 FieldByName()方法。

//s必须是一个 struct 类型

//reflect.ValueOf()只会返回一个值
reflect.ValueOf(s).FieldByName("Name")

//reflect.TypeOf()可以返回两个值,第二个值可以用来判断这个值有没有;
reflect.TypeOf(s).FieldByName("Name")

 FieldByName() 方法返回的是一个 StructField 类型的值。

//FieldByName returns the struct field with the given name
//and a boolean indicating if the field was found.
FieldByName(name string) (StructField, bool)

FieldByName() 方法调用者必须是一个 struct,而不是指针,源码如下:

b、按名字访问结构体的方法

//访问 MethodByName() 必须是指针类型
reflect.ValueOf(&s).MethodByName("method_name").Call([]reflect.Value{reflect.ValueOf("new_value")})
package reflect_test

import (
	"fmt"
	"reflect"
	"testing"
)

type Employee struct {
	EmployeeId int
	//注意后面的 struct tag 的写法,详情见第5点讲解
	Name string		`format:"normal"`
	Age int
}

//更新名字,注意这里的 e 是指针类型
func (e *Employee) UpdateName (newVal string) {
	e.Name = newVal
}

//通过反射调用结构体的方法
func TestInvokeByName(t *testing.T)  {
	e := Employee{1, "Jane", 18}
	//reflect.TypeOf()可以返回两个值,,第二个值可以用来判断这个值有没有;
	//儿reflect.ValueOf()只会返回一个值
	if nameField, ok := reflect.TypeOf(e).FieldByName("Name"); !ok {
		t.Error("Failed to get 'Name' field")
	} else {
        //获取反射取到的字段的 tag 的值
		t.Log("Tag:Format", nameField.Tag.Get("format"))
	}

	reflect.ValueOf(&e).MethodByName("UpdateName").Call([]reflect.Value{reflect.ValueOf("Mike")})

	t.Log("After update name: ", e)
}
/*
=== RUN   TestInvokeByName
reflect_test.go:28: Tag:Format normal
reflect_test.go:33: After update name:  {1 Mike 18}
--- PASS: TestInvokeByName (0.00s)
PASS
*/

c、Elem()

因为 FieldByName() 必须要结构才能调用,如果参数是一个指向结构体的指针,我们需要用到 Elem() 方法,它会帮忙获得指针指向的结构。

  • Elem() 用来获取指针指向的值
  • 如果参数不是指针,会报 panic 错误
  • 如果参数值是 nil, 获取的值为 0
//reflect.ValueOf(demoPtr)).Elem() 返回的是字段的值
reflect.ValueOf(demoPtr).Elem()

//reflect.ValueOf(st)).Elem().Type() 返回的是字段类型
reflect.ValueOf(demoPtr).Elem().Type()

//在指针类型参数调用 FieldByName() 方法。
reflect.ValueOf(demoPtr).Elem().FieldByName("Name")

//在指针类型参数调用 FieldByName() 方法。
reflect.ValueOf(demoPtr).Elem().Type().FieldByName("Name")

6、Struct Tag

结构体里面可以对某些字段做特殊的标记,它是一个 key, value的格式。

a、struct tag 的格式

type Demo struct {
	//先用这个符号(``)包起来,然后写上 key,value的格式
	Name string		`format:"normal"`
}

b、用途

Go 内置的 Json 解析会用到 tag 来做一些标记。

二、万能程序

1、DeepEqual

 根据我前面的文章(Go Map)和(Go数组和切片),我们都知道两个map类型之间是不能互相比较的,两个slice类型之间也不能进行比较,但是有没有什么办法能让他们可以进行比较呢?发射里面的 DeepEqual() 可以帮我们实现这个功能。

a、用 DeepEqual() 比较 map

package flexible_reflect

import (
	"reflect"
	"testing"
)

//用 DeepEqual() 比较两个 map 类型
func TestMapComparing(t *testing.T) {
	m1 := map[int]string{1:"one", 2:"two", 3:"three"}
	m2 := map[int]string{1:"one", 2:"two", 3:"three"}

	if reflect.DeepEqual(m1, m2) {
		t.Log("yes")
	} else {
		t.Log("no")
	}
}

/*
=== RUN   TestMapComparing
reflect_test.go:77: yes
--- PASS: TestMapComparing (0.00s)
PASS
*/

b、用 DeepEqual() 比较 slice

package flexible_reflect

import (
	"reflect"
	"testing"
)

//用 DeepEqual() 比较两个切片类型
func TestSliceComparing(t *testing.T) {
	s1 := []int{1, 2, 3, 4}
	s2 := []int{1, 2, 3, 4}

	if reflect.DeepEqual(s1, s2) {
		t.Log("yes")
	} else {
		t.Log("no")
	}
}
/*
=== RUN   TestSliceComparing
flexible_reflect_test.go:32: yes
--- PASS: TestSliceComparing (0.00s)
PASS
*/

3、用反射实现万能程序

场景:我们有 Employee 和 Customer 两个结构体,二者有两个相同的字段(Name 和 Age),我们希望写一个通用的程序,可以同时填充这两个不同的结构体。

package flexible_reflect

import (
	"errors"
	"fmt"
	"reflect"
	"testing"
)

type Employee struct {
	EmployeeId int
	Name string
	Age int
}

type Customer struct {
	CustomerId int
	Name string
	Age int
}

//用同一个数据填充不同的结构体
//思路:既然是不同的结构体,那么要想通用,所以参数必须是一个空接口才行。
//因为是空接口,所有我们需要对参数类型写断言
func fillDifferentStructByData(st interface{}, data map[string]interface{}) error {
	//先判断传过来的类型是不是指针
	if reflect.TypeOf(st).Kind() != reflect.Ptr {
		return errors.New("第一个参数必须传一个指向结构体的指针")
	}
	//Elem() 用来获取指针指向的值
	//如果参数不是指针,会报 panic 错误
	//如果参数值是 nil, 获取的值为 0
	if reflect.TypeOf(st).Elem().Kind() != reflect.Struct {
		return errors.New("第一个参数必须是一个结构体类型")
	}

	if data == nil {
		return errors.New("填充用的数据不能为nil")
	}

	var (
		field reflect.StructField
		ok bool
	)

	for key, val := range data  {
		//如果结构体里面没有 key 这个字段,则跳过
		//reflect.ValueOf(st)).Elem().Type() 返回的是字段类型
		//reflect.ValueOf(st)).Elem().Type() 等价于 reflect.TypeOf(st)).Elem()
		if field, ok = reflect.TypeOf(st).Elem().FieldByName(key); !ok {
			continue
		}

		//如果字段的类型相同,则用 data 的数据填充这个字段的值
		if field.Type == reflect.TypeOf(val) {
			//reflect.ValueOf(st)).Elem() 返回的是字段的值
			reflect.ValueOf(st).Elem().FieldByName(key).Set(reflect.ValueOf(val))
		}
	}

	return nil
}

//填充姓名和年龄
func TestFillNameAndAge(t *testing.T) {
	//声明一个 map,用来存放数据,这些数据将会填充到 Employee 和 Customer 这两个结构体中
	data := map[string]interface{}{"Name":"Jane", "Age":18}

	e := Employee{}
	if err := fillDifferentStructByData(&e, data); err != nil {
		t.Fatal(err)
	}

	c := Customer{}
	if err := fillDifferentStructByData(&c, data); err != nil {
		t.Fatal(err)
	}

	fmt.Println(e, "\n", c)
}
/*
=== RUN   TestFillNameAndAge
{0 Jane 18}
{0 Jane 18}
--- PASS: TestFillNameAndAge (0.00s)
PASS
*/

三、总结

  • 反射类型:reflect.TypeOf() 
  • 反射值:reflect.Value()
  • reflect.ValueOf() 只会返回一个值; 
  • reflect.TypeOf()可以返回两个值,第二个值可以用来判断这个值有没有。
  • kind() 可以对反射回来的类型做判断值。
  • reflect.TypeOf(s).FieldByName("Name");
  • 调用 reflect.ValueOf(s).FieldByName() 方法,必须是结构体进行调用。
  • 调用 reflect.ValueOf(&s).MethodByName(),必须是指针进行调用。
  • reflect.ValueOf(&s)).Elem().Type() 等价于 reflect.TypeOf(&s)).Elem()。
  • Elem() 方法,它会帮忙获得指针指向的结构。
  • struct tag 的格式:Name string    `format:"normal"`。
  • 反射的 DeepEqual() 方法可以比较两个 map ,或者两个 slice 类型。
  • 反射提高了程序的灵活性;降低了程序的可读性;降低了程序的性能。 

:这篇博文是我学习中的总结,如有转载请注明出处:

https://blog.csdn.net/DaiChuanrong/article/details/118712207

上一篇Go-单元测试

下一篇

标签:反射,编程,Name,ValueOf,Elem,reflect,TypeOf,Go,FieldByName
来源: https://blog.csdn.net/DaiChuanrong/article/details/118712207

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有