golang:reflect

reflect 包

golang中的反射是由 reflect 包提供支持的,

  • 它定义了两个重要的类型 Type 和 Value ,任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成,
  • reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取任意对象的 Value 和 Type。

反射的类型对象(reflect.Type)

使用 reflect.TypeOf() 函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息,下面通过示例来理解获取类型对象的过程:

package main
import (
    "fmt"
    "reflect"
)
func main() {
    var a int
    typeOfA := reflect.TypeOf(a)
    fmt.Println(typeOfA.Name(), typeOfA.Kind())
}

在这里插入图片描述
代码说明如下:

  • 第 9 行,定义一个 int 类型的变量。
  • 第 10 行,通过 reflect.TypeOf() 取得变量 a 的类型对象 typeOfA,类型为 reflect.Type()。
  • 第 11 行中,通过 typeOfA 类型对象的成员函数,可以分别获取到 typeOfA 变量的类型名为 int,种类(Kind)为 int。

反射的类型(Type)与种类(Kind)

在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)

1) 反射类型(Type)的定义

Go语言程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。

2) 反射种类(Kind)的定义

种类(Kind)指的是对象归属的品种,在 reflect 包中有如下定义:

type Kind uint
const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 无符号整型
    Uint8                // 无符号8位整型
    Uint16               // 无符号16位整型
    Uint32               // 无符号32位整型
    Uint64               // 无符号64位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64位复数类型
    Complex128           // 128位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 结构体
    UnsafePointer        // 底层指针
)

Map、Slice、Chan 属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于 Ptr。type A struct{} 定义的结构体属于 Struct 种类,*A 属于 Ptr。

从类型对象中获取类型名称和种类

  • 类型名称对应的反射获取方法是 reflect.Type 中的 Name() 方法,返回表示类型名称的字符串;
  • 类型归属的种类(Kind)使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 类型的常量。
package main

import (
	"fmt"
	"reflect"
)

type Enum int
const Zero Enum = 0

func main() {
	type cat struct {

	}

	typeOfCat := reflect.TypeOf(cat{})
	fmt.Println(typeOfCat.Name(), typeOfCat.Kind())

	typeOfA := reflect.TypeOf(Zero)
	fmt.Println(typeOfA.Name(), typeOfA.Kind())
}

在这里插入图片描述

获取float的值

package main

import (
	"fmt"
	"reflect"
)

func main(){
	var x float64 = 3.4
	v := reflect.ValueOf(x)
	fmt.Println("type:", v.Type()) //float64
	fmt.Println("kind is float64:", v.Kind() == reflect.Float64) //true
	fmt.Println("value:", v.Float()) //3.4
}

package main

import (
	"fmt"
	"reflect"
)

func main(){
	//1.“接口类型变量”=>“反射类型对象”
	var circle float64 = 6.28
	var icir interface{}

	icir = circle
	fmt.Println("Reflect : circle.Value = ", reflect.ValueOf(icir)) //Reflect : circle.Value =  6.28
	fmt.Println("Reflect : circle.Type  = ", reflect.TypeOf(icir)) //Reflect : circle.Type =  float64

	// 2. “反射类型对象”=>“接口类型变量
	v1 := reflect.ValueOf(icir)
	fmt.Println(v1) //6.28
	fmt.Println(v1.Interface()) //6.28

	y := v1.Interface().(float64)
	fmt.Println(y) //6.28

   //	v1.SetFloat(4.13) //panic: reflect: reflect.Value.SetFloat using unaddressable value

   //3.修改
	fmt.Println(v1.CanSet())//是否可以进行修改 false
	v2 := reflect.ValueOf(&circle) // 传递指针才能修改
	v4:=v2.Elem()// 传递指针才能修改,获取Elem()才能修改
	fmt.Println(v4.CanSet()) //true

	v4.SetFloat(3.14)
	fmt.Println(circle) //3.14
}

指针与指针指向的元素

Go语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作,代码如下:

package main

import (
	"fmt"
	"reflect"
)


func main() {
	type cat struct {

	}

	ins := &cat{}   // 创建cat的实例
	typeOfCat := reflect.TypeOf(ins)  //  // 获取结构体实例的反射类型对象
	fmt.Printf("name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())

	typeOfCat = typeOfCat.Elem()   // 取类型的元素
	fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}

在这里插入图片描述
代码说明如下:

  • 第 13 行,创建了 cat 结构体的实例,ins 是一个 *cat 类型的指针变量。
  • 第 15 行,对指针变量获取反射类型信息。
  • 第 17 行,输出指针变量的类型名称和种类。Go语言的反射中对所有指针变量的种类都是 Ptr,但需要注意的是,指针变量的类型名称是空,不是 *cat。
  • 第 19 行,取指针类型的元素类型,也就是 cat 类型。这个操作不可逆,不可以通过一个非指针类型获取它的指针类型。
  • 第 21 行,输出指针变量指向元素的类型名称和种类,得到了 cat 的类型名称(cat)和种类(struct)。

使用反射获取结构体的成员类型

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

与成员获取相关的 reflect.Type 的方法如下表所示。

golang:reflect_第1张图片

结构体字段类型

reflect.Type 的 Field() 方法返回 StructField 结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(StructTag)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。

StructField 的结构如下:

type StructField struct {
    Name string          // 字段名
    PkgPath string       // 字段路径
    Type      Type       // 字段反射类型对象
    Tag       StructTag  // 字段的结构体标签
    Offset    uintptr    // 字段在结构体中的相对偏移
    Index     []int      // Type.FieldByIndex中的返回的索引值
    Anonymous bool       // 是否为匿名字段
}

获取成员反射信息

下面代码中,实例化一个结构体并遍历其结构体成员,再通过 reflect.Type 的 FieldByName() 方法查找结构体中指定名称的字段,直接获取其类型信息。

反射访问结构体成员类型及信息:

package main
import (
	"fmt"
	"reflect"
)
func main() {
	// 声明一个空结构体
	type cat struct {
		Name string
		Type int `json:"type" id:"100"`  // 带有结构体tag的字段
	}

	ins := cat{Name: "mimi", Type: 1}   	// 创建cat的实例
	
	typeOfCat := reflect.TypeOf(ins)  	// 获取结构体实例的反射类型对象

	for i := 0; i < typeOfCat.NumField(); i++ {  	// 遍历结构体所有成员
		fieldType := typeOfCat.Field(i)  // 获取每个成员的结构体字段类型
		fmt.Printf("name: %v  tag: '%v'\n", fieldType.Name, fieldType.Tag) // 输出成员名和tag
	}

	if catType, ok := typeOfCat.FieldByName("Type"); ok {  	// 通过字段名, 找到字段类型信息
		fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id")) 	// 从tag中取出需要的tag
	}
}

在这里插入图片描述

  • 第 13 行,Type 是 cat 的一个成员,这个成员类型后面带有一个以 ` 开始和结尾的字符串。这个字符串在Go语言中被称为 Tag(标签)。一般用于给字段添加自定义信息,方便其他模块根据信息进行不同功能的处理。
  • 第 20 行,使用 reflect.Type 类型的 NumField() 方法获得一个结构体类型共有多少个字段。如果类型不是结构体,将会触发宕机错误。
  • 第 22 行,reflect.Type 中的 Field() 方法和 NumField 一般都是配对使用,用来实现结构体成员的遍历操作。
  • 第 24 行,使用 reflect.Type 的 Field() 方法返回的结构不再是 reflect.Type 而是 StructField 结构体。
  • 第 27 行,使用 reflect.Type 的 FieldByName() 根据字段名查找结构体字段信息,catType 表示返回的结构体字段信息,类型为 StructField,ok 表示是否找到结构体字段的信息。
  • 第 29 行中,使用 StructField 中 Tag 的 Get() 方法,根据 Tag 中的名字进行信息获取。

结构体标签(Struct Tag)

通过 reflect.Type 获取结构体成员信息 reflect.StructField 结构中的 Tag 被称为结构体标签(StructTag)。结构体标签是对结构体字段的额外信息标签。

JSON、BSON 等格式进行序列化及对象关系映射(Object Relational Mapping,简称 ORM)系统都会用到结构体标签,这些系统使用标签设定字段在处理时应该具备的特殊属性和可能发生的行为。这些信息都是静态的,无须实例化结构体,可以通过反射获取到。

结构体标签的格式

Tag 在结构体字段后方书写的格式如下:

`key1:"value1" key2:"value2"`

结构体标签由一个或多个键值对组成;键与值使用冒号分隔,值用双引号括起来;键值对之间使用一个空格分隔。

从结构体标签中获取值

StructTag 拥有一些方法,可以进行 Tag 信息的解析和提取,如下所示:

  • func (tag StructTag) Get(key string) string:根据 Tag 中的键获取对应的值,例如key1:"value1" key2:"value2"的 Tag 中,可以传入“key1”获得“value1”。
  • func (tag StructTag) Lookup(key string) (value string, ok bool):根据 Tag 中的键,查询值是否存在。

结构体标签格式错误导致的问题

编写 Tag 时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,示例代码如下:

package main
import (
    "fmt"
    "reflect"
)
func main() {
    type cat struct {
        Name string
        Type int `json: "type" id:"100"`
    }
    typeOfCat := reflect.TypeOf(cat{})
    if catType, ok := typeOfCat.FieldByName("Type"); ok {
        fmt.Println(catType.Tag.Get("json"))
    }
}

运行上面的代码会输出一个空字符串,并不会输出期望的 type。

代码第 11 行中,在 json: 和 “type” 之间增加了一个空格,这种写法没有遵守结构体标签的规则,因此无法通过 Tag.Get 获取到正确的 json 对应的值。这个错误在开发中非常容易被疏忽,造成难以察觉的错误。所以将第 12 行代码修改为下面的样子,则可以正常打印。

type cat struct {
    Name string
    Type int `json:"type" id:"100"`
}

FieldByIndex

package main

import (
	"fmt"
	"reflect"
)

type Animal struct {
	Name string
	Age int
}
type Cat struct {
	Animal
	Color string
}


func main(){
	c1 := Cat{Animal{"猫咪", 1}, "白色"}
	t1 := reflect.TypeOf(c1)

	for i:=0;i<t1.NumField();i++{
		fmt.Println("Field(", i , "): ", t1.Field(i))
	}

	// FiledByIndex()的参数是一个切片,第一个数是Animal字段,第二个参数是Animal的第一个字段
	f1:=t1.FieldByIndex([]int{0,0})
	f2:=t1.FieldByIndex([]int{0,1})
	fmt.Println("f1: ", f1)
	fmt.Println("f2: ",f2)

	v1:=reflect.ValueOf(c1)
	fmt.Println(v1.Field(0)) //{猫咪 1}
	fmt.Println(v1.FieldByIndex([]int{0,0})) //猫咪

}

golang:reflect_第2张图片

通过反射修改结构体的数据

package main

import (
	"reflect"
	"fmt"
)

type Student struct {
	Name string
	Age int
	School string
}
func main()  {
	/*
	修改内容
	 */
	s1:= Student{"王二狗",18,"清华大学"}
	v1 := reflect.ValueOf(&s1)

	if v1.Kind() ==reflect.Ptr && v1.Elem().CanSet(){
		v1 = v1.Elem()
		fmt.Println("可以修改。。")
	}
	f1:=v1.FieldByName("Name")
	fmt.Println(f1.CanSet())
	f1.SetString("王三狗")
	f2:=v1.FieldByName("Age")
	fmt.Println(f2.CanSet())
	f2.SetInt(20)
	fmt.Println(s1)
}

package main

import (
	"fmt"
	"reflect"
)

//1.提供一个结构体
type Person struct {
	Name string
	Age int
	Sex string
}
//2.提供一个方法
func (p Person) Say(msg string)  {
	fmt.Println("Hello..", msg)
}

func (p Person) PrintInfo()  {
	fmt.Println("姓名:",p.Name,"年龄:",p.Age,"性别:",p.Sex)
}

func main(){
	p1:=Person{"王二狗",30,"男"}

	// 获取对象的类型
	t1 := reflect.TypeOf(p1)
	fmt.Println(t1, t1.Name(), t1.Kind()) //main.Person   Person   struct
	fmt.Println(t1.NumField(), t1.NumMethod()); //3个字段,2个方法


	//获取值,如果是结构体类型,获取的是字段的值
	v1:=reflect.ValueOf(p1)
	fmt.Println(v1) //{王二狗 30 男}
	if t1.Kind() == reflect.Struct{
		for i:=0;i<t1.NumField();i++{
			field := t1.Field(i)
			fmt.Printf("字段名字:%s,字段类型:%s,字段数值:%v\n",field.Name,field.Type,v1.Field(i).Interface()) //通过interface方法来取出这个字段所对应的值
		}
	}

	//2.操作方法
	for i:=0;i<t1.NumMethod();i++{
		m:=t1.Method(i)
		fmt.Println(m.Name,m.Type)
		/*
		PrintInfo func(main.Person)
		Say func(main.Person, string)
		 */
	}
	m2:=v1.MethodByName("PrintInfo")
	m2.Call(nil) //姓名: 王二狗 年龄: 30 性别: 男

	m1 := v1.MethodByName("Say")
	args:=[]reflect.Value{reflect.ValueOf("干啥呢?")}
	m1.Call(args)
}

你可能感兴趣的:(golang)