命令记不住

登陆

标准库 reflect

标准库reflect

1.reflect简介

一个接口类型的变量存储了2个信息,值和类型,reflect.TYpeOf()和reflect.VlueOf(),返回被检查对象的类型和值。

当调用reflect.TypeOf(x)时,x被当作一个接口变量来处理,然后reflect.TypeOf()对接口变量进行拆解,返回其反射类型信息

  • reflect.Type和reflect.Value还有许多其他检查和操作方法

Type的主要方法有:

Kind()返回一个常量,表示具体类型的底层类型

Elem()返回指针、数组、切片、字典、通道等类型。这个方法要慎用,如果用于其他类型会出现运行时异常

Value的主要方法有:

Kind()返回一个常量,表示具体类型的底层类型、

Type()返回具体类型所对应的reflect.Type(静态类型)

package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    name string
}

type MyInt int

func main() {

    var a int = 9
    v := reflect.ValueOf(a)                         // 返回Value类型对象,值为9
    t := reflect.TypeOf(a)                          // 返回Type类型对象,值为int
    fmt.Println(v, t, v.Type(), v.Kind(), t.Kind()) // kind()返回底层基础类型

    var mi MyInt = 99
    mv := reflect.ValueOf(mi)                            // 返回Value类型对象,值为99
    mt := reflect.TypeOf(mi)                             // 返回Type类型对象,值为MyInt
    fmt.Println(mv, mt, mv.Type(), mv.Kind(), mt.Kind()) // kind()返回底层基础类型

    var b [5]int = [5]int{5, 6, 7, 8}
    fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(), reflect.TypeOf(b).Elem()) // [5]int array int

    var Pupil Student
    p := reflect.ValueOf(Pupil) // 使用ValueOf()获得结构体的Value对象

    fmt.Println(p.Type()) // 输出:Student
    fmt.Println(p.Kind()) // 输出:struct

}

reflect.Type对应的值,而kind()对应的是基础类型

Kind()大概会返回切片、字典、数组、指针、结构体、接口、字符串、函数、整型或其他基础类型

Type()返回的是静态类型,而Kind()返回的是基础类型

2.反射的应用

  • 通过反射可以修改对象
  • 通过反射可以创建基础类型和用户自定义类型变量

3.通过反射可以修改对象

对象必须是可寻址,实际上要修改的是指针指向的数据,需要调用Value类型的Elem()方法。Elem()方法能够对指针进行间接引用,将结果存储到reflect.Value类型对象中

v := reflect.Value.Elem()   表示获取原始值对应的反射对象

通过CanSet()方法来判断原始反射对象v reflect.Value是否可写,CanAddr()方法判断它是否可被取地址,这里的v是通过Elem()得到的。CanSet()和CanAddr()这两个方法的签名如下

func (v Value) CanAddr()
func (v Value) CanSet() bool

示例来访问、修改值

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	name string
	Age  int
}

func main() {
	var a int = 99
	v := reflect.ValueOf(a) // 返回Value类型对象,值为9
	t := reflect.TypeOf(a)  // 返回Type类型对象,值为int

	fmt.Println(v.Type(), t.Kind(), reflect.ValueOf(&a).Elem())
	fmt.Println(reflect.ValueOf(a).CanSet(), reflect.ValueOf(a).CanAddr())
	fmt.Println(reflect.ValueOf(&a).CanSet(), reflect.ValueOf(&a).CanAddr())

	pa := reflect.ValueOf(&a).Elem() // reflect.Value.Elem()表示获取原始值对应的反射对象
	fmt.Println("CanSet:", pa.CanSet(), "CanAddr:", pa.CanAddr())
	//fmt.Println(reflect.ValueOf(a).Elem().CanSet(), reflect.ValueOf(a).Elem().CanAddr())
	fmt.Println(pa, pa.CanSet())

	pa.SetInt(100)
	fmt.Println(pa)

	var Pupil Student = Student{"Jim", 8}
	Pupilv := reflect.ValueOf(Pupil) // 使用ValueOf()获取结构体的Value对象

	fmt.Println(Pupilv.Type()) // 输出:Student
	fmt.Println(Pupilv.Kind()) // 输出:struct

	p := reflect.ValueOf(&Pupil).Elem() // 获取原始值对应的反射对象
	fmt.Println(reflect.ValueOf(&Pupil))
	fmt.Println("CanSet:", p.CanSet(), "CanAddr:", p.CanAddr())

	//p.Field(0).SetString("Mike") // 未导出字段,不能修改,会发生异常
	p.Field(1).SetInt(10)
	fmt.Println(p)

}

要通过反射的方式来修改对象,重点是通过方法Elem()获取原始值对应的反射对象,一个结构体中只有被导出的字段才是可写的。

reflect.ValueOf(&a)得到的是原始变量a的指针地址,这个指针地址再通过Elem()方法得到反射对象。

如果在结构体中有tag标签,通过反射可获取结构体成员变量的tag信息,如下示例

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	name string
	Age  int `json:"years"`
}

func main() {
	var Pupil Student = Student{"joke", 18}
	setStudent := reflect.ValueOf(&Pupil).Elem()

	sSAge, _ := setStudent.Type().FieldByName("Age")
	fmt.Println(sSAge.Tag.Get("json")) // years
}

4.通过反射可以创建基础类型和用户自定义类型变量

除了可以通过反射创建基础类型和用户自定义类型,还可以使用反射来创建切片、字典、通道,甚至包括函数类型。常见函数有:reflect.Makeslice(),reflect.Makemap()和reflect.Makechan()。

要想创建变量,需要首先确定类型,下面代码中根据reflect.Type(t)得到t的静态类型,接着使用reflect.New(vartype)生成了新变量。新变量通过方法Elem()获取的反射对象来设置变量值,最后使用Elem().interface()来反引用reflect的指针,得到新变量的值。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    t := 9
    // 反射创建int 变量
    varType := reflect.TypeOf(t)

    v1 := reflect.New(varType)
    v1.Elem().SetInt(1)
    varNew := v1.Elem().Interface()
    fmt.Printf("int Var: 指针:%d 值:%d\n", v1, varNew)

    // 反射创建map slice
    newSlice := make([]int, 5)
    newmap := make(map[string]int)
    sliceType := reflect.TypeOf(newSlice)
    mapType := reflect.TypeOf(newmap)

    // 创建新值
    ReflectSlice := reflect.MakeSlice(sliceType, 5, 5)
    Reflectmap := reflect.MakeMap(mapType)

    // 使用新创建的变量
    V := 99
    SliceV := reflect.ValueOf(V)
    ReflectSlice = reflect.Append(ReflectSlice, SliceV)
    intSlice := ReflectSlice.Interface().([]int)
    fmt.Println("Slice:", intSlice)

    Key := "Rose"
    Value := 999
    MapKey := reflect.ValueOf(Key)
    MapValue := reflect.ValueOf(Value)
    Reflectmap.SetMapIndex(MapKey, MapValue)
    mapStringInt := Reflectmap.Interface().(map[string]int)
    fmt.Println("Map:", mapStringInt)
}

通过反射机制,能对一个结构体类型的大致结构如方法、字段的情况有较为全面的了解,代码如下

package main

import (
    "fmt"
    "reflect"
)

// 结构体
type ss struct {
    int
    string
    bool
    float64
}

func (s ss) Method1(i int) string  { return "结构体方法1" }
func (s *ss) Method2(i int) string { return "结构体方法2" }

var (
    structValue = ss{ // 结构体
        20,
        "结构体",
        false,
        64.0,
    }
)

func main() {
    // 反射结构体
    fmt.Println("=========引用=========")
    v := reflect.ValueOf(&structValue)
    fmt.Println("String             :", v.String())  // 反射值的字符串形式
    fmt.Println("Type               :", v.Type())    // 反射值的类型
    fmt.Println("Kind               :", v.Kind())    // 反射值的类别
    fmt.Println("CanAddr            :", v.CanAddr()) // 是否可以获取地址
    fmt.Println("CanSet             :", v.CanSet())  // 是否可以修改
    if v.CanAddr() {
        fmt.Println("Addr               :", v.Addr())       // 获取地址
        fmt.Println("UnsafeAddr         :", v.UnsafeAddr()) // 获取自由地址
    }
    // 获取方法数量
    fmt.Println("可用方法数量  :", v.NumMethod())
    if v.NumMethod() > 0 {
        i := 0
        for ; i < v.NumMethod()-1; i++ {
            fmt.Printf("    ┣ %v\n", v.Method(i).String())
        }
        fmt.Printf("    ┗ %v\n", v.Method(i).String())
        // 通过名称获取方法
        fmt.Println("Method1 MethodByName:", v.MethodByName("Method1").String())
        fmt.Println("Method2 MethodByName:", v.MethodByName("Method2").String())
    }

    fmt.Println("=========值变量=========")
    v = reflect.ValueOf(structValue)
    fmt.Println("String             :", v.String())  // 反射值的字符串形式
    fmt.Println("Type               :", v.Type())    // 反射值的类型
    fmt.Println("Kind               :", v.Kind())    // 反射值的类别
    fmt.Println("CanAddr            :", v.CanAddr()) // 是否可以获取地址
    fmt.Println("CanSet             :", v.CanSet())  // 是否可以修改
    if v.CanAddr() {
        fmt.Println("Addr               :", v.Addr())       // 获取地址
        fmt.Println("UnsafeAddr         :", v.UnsafeAddr()) // 获取自由地址
    }
    // 获取方法数量
    fmt.Println("方法数量          :", v.NumMethod())
    fmt.Println("指针变量可用方法数量  :", reflect.ValueOf(&structValue).NumMethod())
    if v.NumMethod() > 0 {
        i := 0
        for ; i < v.NumMethod()-1; i++ {
            fmt.Printf("    ┣ %v\n", v.Method(i).String())
        }
        fmt.Printf("    ┗ %v\n", v.Method(i).String())
        // 通过名称获取方法
        fmt.Println("Method1 MethodByName:", v.MethodByName("Method1").String())
        fmt.Println("Method2 MethodByName:", v.MethodByName("Method2").String())
    }

    switch v.Kind() {
    // 结构体:
    case reflect.Struct:
        fmt.Println("=== 结构体 ===")
        // 获取字段个数
        fmt.Println("NumField           :", v.NumField())
        if v.NumField() > 0 {
            var i int
            // 遍历结构体字段
            for i = 0; i < v.NumField()-1; i++ {
                field := v.Field(i) // 获取结构体字段
                fmt.Printf("    ├ %-8v %v\n", field.Type(), field.String())
            }
            field := v.Field(i) // 获取结构体字段
            fmt.Printf("    └ %-8v %v\n", field.Type(), field.String())
            // 通过名称查找字段
            if v := v.FieldByName("ptr"); v.IsValid() {
                fmt.Println("FieldByName(ptr)   :", v.Type().Name())
            }
            // 通过函数查找字段
            v := v.FieldByNameFunc(func(s string) bool { return len(s) > 3 })
            if v.IsValid() {
                fmt.Println("FieldByNameFunc    :", v.Type().Name())
            }
        }
    }
}

程序输出:

=========引用=========
String             : <*main.ss Value>
Type               : *main.ss
Kind               : ptr
CanAddr            : false
CanSet             : false
可用方法数量  : 2
    ┣ <func(int) string Value>
    ┗ <func(int) string Value>
Method1 MethodByName: <func(int) string Value>
Method2 MethodByName: <func(int) string Value>
=========值变量=========
String             : <main.ss Value>
Type               : main.ss
Kind               : struct
CanAddr            : false
CanSet             : false
方法数量          : 1
指针变量可用方法数量  : 2
    ┗ <func(int) string Value>
Method1 MethodByName: <func(int) string Value>
Method2 MethodByName: <invalid Value>
=== 结构体 ===
NumField           : 4
    ├ int      <int Value>
    ├ string   结构体
    ├ bool     <bool Value>
    └ float64  <float64 Value>

推荐阅读