Go-反射编程

目录

一、反射编程

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,而不是指针,源码如下:

Go-反射编程

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-单元测试

下一篇

上一篇:获取一天的最大值和最小值


下一篇:JavaScript 日期(Date)之ValueOf()方法