Go语言反射

背景

先看官方Doc中Rob Pike给出的关于反射的定义:

Reflection in computing is the ability of a program to examine its own structure, particularly through types; it’s a form of metaprogramming. It’s also a great source of confusion.
(在计算机领域,反射是一种让程序——主要是通过类型——理解其自身结构的一种能力。它是元编程的组成之一,同时它也是一大引人困惑的难题。)

网上关于Golang反射的教程、博客,早已汗牛充栋。可遍数之下,有些偏于底层实现,有些偏于单一问题,有些偏于性能,有些是官方Doc的翻译,而且质量参差不齐。

在这种情况下,对于想学反射编程的人来说,很难看到一个完整的、系统的关于Golang反射编程的教程,甚至于在某段时间下好像学会了Golang反射,过段时间又忘了,好像从没学过一样。

本文的出现就是为了解决上述问题,争取对Golang反射编程,做一个尽量完善、系统的阐述。本文主要关注点在于:如何学会并使用Golang反射。因此不会讲述太多底层实现和性能方面的问题。

为了便于理解、记忆,本文在阐述过程中,会通过对比Golang一般语法和反射“语法”的方式,来说明如何使用反射编程。

最后提醒一句:有限制地用反射,即便你用的非常6。

基础

上面已经说到,反射主要通过类型,来理解其自身结构,所以反射中,比较关键的一点在于:类型。
另外,编程中所有的操作,都是对数据的操作,亦即值,所以反射中,另外一点在于:值。
在反射编程时,一定要时刻注意,你在操作变量的类型是什么,要对值做什么操作。
Golang反射中,类型的起点是reflect.TypeOf(x),值的起点是reflect.ValueOf(x)

可读与可写

先看一个简单的例子(后面例子会省略package和import部分):

package main

import "reflect"

func main() {
    var i int = 5
    v := reflect.ValueOf(i)
    println(v.Int()) // Output: 5
}

一定要注意一点,reflect.ValueOf(i)是个函数调用,所以对比看一下下面这个没有反射的例子:

func echo(v int) {
    println(v)
}

func main() {
    var i int = 5
    echo(i)
}

这两个例子是等价的,在上面反射中,reflect.ValueOf(i)是函数调用,函数调用分为传值和传址,该写法明显是在传值,它是i的一份拷贝,就像在调用echo(i)的时候,给echo传的是i的值,即在echo内部,i的值是只读的。
所以在下面例子中:

func echo(v int) {
    v = 100
    println(v) // Output: 100
}

func main() {
    var i int = 5
    echo(i)
    println(i) // Output: 5
}

echo中,将v的值改成了100,却改不了maini的值,因为调用echo的本质是传值。传值的意思即只读,因此如果在反射中,对只读的值做任何Set操作,都会panic

func main() {
    var i int = 5
    v := reflect.ValueOf(i)
    v.SetInt(100) // panic
}

那如何改变值呢?很明显,应该传址:

func change(v *int) {
    *v = 100
}

func main() {
    var i int = 5
    change(&i)
    println(i) // Output: 100
}

注意在该程序中:

  1. 为了在函数change中改变i的值,我们将i的内存地址传给了change
  2. 在改变i的值时,change先给v做了解指针操作*v

所以,对应的,反射也是函数调用,写出来,和上面逻辑上应该是一样的:

func main() {
    var i int = 5
    v := reflect.ValueOf(&i)
    v.Elem().SetInt(100)
    println(i) // Output: 100
}

Golang反射简易之处在于:只要你学会了该语言,你就学会了反射。
比较一下上面两个程序,有什么不同吗?

v := reflect.ValueOf(&i)var v *int = &i是一样的;
v.Elem().SetInt(100)*v = 100是一样的,先解指针,再赋值。
那有没有方法能判断是否可写呢?reflect.Value.CanSet() bool该函数可以判断一个值是否可写:

func main() {
    var i int = 5
    v := reflect.ValueOf(i)
    println(v.CanSet()) // Output: false
    v = reflect.ValueOf(&i)
    println(v.CanSet()) // Output: false // 标记1
    println(v.Elem().CanSet()) // Output: true
}

标记1处为什么是false呢?因为你要改的是指针指向的值,不是指针本身的值。

那具体有哪些是CanSet的呢?文档:

A Value can be changed only if it is addressable and was not obtained by the use of unexported struct fields.

注意文档中的一句:未被导出的struct fields是不可被Set的。

取址

通过上面例子,我们知道,想要改变值,其前提是:取址。那什么样的变量是可取址的呢?反射包里有一个函数:reflect.Value.CanAddr() bool,该函数可以判断是否可取址。

func main() {
    var i int = 5
    v := reflect.ValueOf(i)
    println(v.CanAddr()) // Output: false
    v = reflect.ValueOf(&i)
    println(v.CanAddr()) // Output: false
    println(v.Elem().CanAddr()) // Output: true
}

在该函数的文档中,已经明确了哪些可以被取址:

A value is addressable if it is an element of a slice, an element of an addressable array, a field of an addressable struct, or the result of dereferencing a pointer.

为方便理解,我将上述文档翻译成代码:

func main() {
    ls := []int{1}
    println(reflect.ValueOf(ls).Index(0).CanAddr())         // Output: true
    println(reflect.ValueOf(&ls).Elem().Index(0).CanAddr()) // Output: true

    var t struct{ a, B int }
    println(reflect.ValueOf(&t).Elem().Field(0).CanAddr()) // Output: true
    println(reflect.ValueOf(&t).Elem().Field(0).CanSet())  // Output: false
    println(reflect.ValueOf(&t).Elem().Field(1).CanAddr()) // Output: true
    println(reflect.ValueOf(&t).Elem().Field(1).CanSet())  // Output: true

    var p *int
    println(reflect.ValueOf(p).Elem().CanSet()) // Output: false
    p = &ls[0]
    println(reflect.ValueOf(p).Elem().CanSet()) // Output: true
}

既然知道了哪些可以取址,那么也就知道了哪些不可被取址,比如map[k]v中的kv都不可被取址,没有传址进来的变量都不可被取址……

一定要注意:可取址CanAddr和可赋值CanSet不完全等价,主要区别在于未被导出的struct fields

对指针和结构体相关的比较好理解,关键问题在于:为什么an element of a slice是可被取址和赋值的呢?

func change(ls []int) {
    ls[0] = 100
}

func main() {
    ls := []int{5}
    change(ls)
    println(ls[0]) // Output: 100
}

在Golang里面,slicemapchan默认是传址的!
请看:

func main() {
    ls := []int{0}
    reflect.ValueOf(ls).Index(0).SetInt(123)
    println(ls[0]) // Output: 123

    m := make(map[string]int)
    reflect.ValueOf(m).SetMapIndex(reflect.ValueOf("abc"), reflect.ValueOf(456))
    println(m["abc"]) // Output: 456

    ch := make(chan int, 1)
    reflect.ValueOf(ch).Send(reflect.ValueOf(789))
    println(<-ch) // Output: 789
}

注意slicemapchan默认传址的前提是:已经提前分配好内存空间!

细节知识点:array默认是传值

func main() {
    arr := [...]int{1}
    println(reflect.ValueOf(arr).Index(0).CanAddr()) // Output: false
    println(reflect.ValueOf(arr).Index(0).CanSet())  // Output: false
}

类型

到目前为止,还没接触到类型。获取数据的类型可以用reflect.ValueOf(x).Kind()获取真实类型。所以有:

func main() {
    var i int
    println(reflect.ValueOf(i).Kind().String()) // Output: int

    var s string
    println(reflect.ValueOf(s).Kind().String()) // Output: string

    var iface interface{}
    println(reflect.ValueOf(iface).Kind().String())                // Output: invalid // 标记1
    println(reflect.ValueOf(&iface).Elem().Kind().String())        // Output: interface // 标记2
    println(reflect.ValueOf(&iface).Elem().Elem().Kind().String()) // Output: invalid // 标记3

    var t time.Time
    println(reflect.ValueOf(t).Kind().String()) // Output: struct // 标记4
}

标记1、2、3处怎么理解?

  • 再次强调reflect.ValueOf(x)是函数调用;
  • 注意函数签名:
    func reflect.ValueOf(interface{}) reflect.Value
    参数是interface{},指所有调用该函数的地方,都会将参数类型转换成interface{}
  • interface{}typ, val对,空的interface{}(nil, nil),类型和值都是空,所以Kind()invalid
  • 所有类型转换到interface{}的过程,都是(typ, val)拷贝的过程;
  • 标记2处,&iface是一个interface类型的指针;
  • 指针指向对象的类型是interface类型的变量;
  • 该interface类型的变量的Kind()invalid
  • 所以标记3处是invalid
  • 标记4处,time.Time类型的底层是struct,即:type Time struct { ... }

那如果说想获得类型名,而不是底层类型,该怎么处理呢?reflect.TypeOf(x)

func main() {
    var i int
    println(reflect.TypeOf(i).String()) // Output: int

    var s string
    println(reflect.TypeOf(s).String()) // Output: string

    var iface interface{}
    println(reflect.TypeOf(iface).String())         // panic
    println(reflect.TypeOf(&iface).String())        // Output: *interface {}
    println(reflect.TypeOf(&iface).Elem().String()) // Output: interface {}

    var t time.Time
    println(reflect.TypeOf(t).String()) // Output: time.Time
}

reflect.TypeOf(x).String()一般用来处理time.Time这种想要特殊处理的结构。

构造常用类型:

func main() {
    intTyp := reflect.TypeOf(123)
    strTyp := reflect.TypeOf("")
    println(reflect.ArrayOf(10, intTyp).String())             // Output: [10]int
    println(reflect.ChanOf(reflect.BothDir, intTyp).String()) // Output: chan int
    println(reflect.MapOf(strTyp, intTyp).String())           // Output: map[string]int
    println(reflect.PtrTo(intTyp).String())                   // Output: *int
    println(reflect.SliceOf(intTyp).String())                 // Output: []int

    println(reflect.StructOf([]reflect.StructField{
        {Name: "IntA", Type: intTyp},
        {Name: "StrB", Type: strTyp},
    }).String()) // Output: struct { IntA int; StrB string }

    var err error
    errTyp := reflect.TypeOf(&err).Elem()
    println(reflect.FuncOf([]reflect.Type{intTyp}, nil, false).String())                            // Output: func(int)
    println(reflect.FuncOf(nil, []reflect.Type{intTyp}, false).String())                            // Output: func() int
    println(reflect.FuncOf([]reflect.Type{intTyp}, []reflect.Type{strTyp, errTyp}, false).String()) // Output: func() (string, error)
    println(reflect.FuncOf([]reflect.Type{reflect.SliceOf(strTyp)}, nil, true).String())            // Output: func(...string)
}

变量的内在机制

Go语言中的变量是分为两部分的:

  • 类型信息:预先定义好的元信息。
  • 值信息:程序运行过程中可动态变化的。

反射主要使用reflect包

在Go语言的反射机制中,任何接口值都由是一个具体类型和具体类型的值两部分组成的(我们在上一篇接口的博客中有介绍相关概念)。 在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Typereflect.Value两部分组成,并且reflect包提供了reflect.TypeOfreflect.ValueOf两个函数来获取任意对象的ValueType

TypeOf

在Go语言中,使用reflect.TypeOf()函数可以获得任意值的类型对象reflect.Type,程序通过类型对象可以访问任意值的类型信息。

func retType(i interface{}) {
    //v数据类型*reflect.rtype
    v := reflect.TypeOf(i)
    fmt.Printf("Type:%T \nValue:%v \n", v, v)
}
func main() {
    var a [2]string
    retType(a)
}

另外,类型别名的话,只能拿到原类型。结构体等用type定义的类型拿到的是type后面的名字。具体看下例:

func retType(i interface{}) {
    //v数据类型*reflect.rtype
    v := reflect.TypeOf(i)
    fmt.Printf("Type:%T \nValue:%v \n", v, v)
}
func main() {
    var b [2]string
    retType(b)
    fmt.Println("--------------------")

    //map类型
    var c map[string]int16
    retType(c)
    fmt.Println("--------------------")

    //也可以识别自定义数据类型
    type t int64
    var a t
    retType(a)
    fmt.Println("--------------------")

    //结构体,拿到的是main.u
    type u struct{}
    d := u{}
    retType(d)
    fmt.Println("--------------------")

    //类型别名拿到的是int8
    type v = int8
    var e v
    retType(e)
    fmt.Println("--------------------")

    //传入自定义类型t会报错,编译不通过
    //main.go|49 col 9 error| type t is not an expression
    // retType(t)
    fmt.Println("--------------------")
}

map类型的b,单纯的TypeOf(b)显示map[string]int,如果加上Key的话,显示int。结构体的话会带上包名,比如main.dog

type name和type kind

在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。

func main() {

    type dog struct {
        eyes string
    }
    b := dog{eyes: "眼睛"}

    // type a int64
    // var b a
    // b = 87

    // a := "aaa"
    // b := &a

    ref := reflect.TypeOf(b)
    fmt.Printf("ref \nType:%T Value:%v \n", ref, ref)
    fmt.Printf("ref.Name \nType:%T Value:%v \n", ref.Name(), ref.Name())
    fmt.Printf("ref.Kind \nType:%T Value:%v \n", ref.Kind(), ref.Kind())
}

如上例,TypeOf返回的是main.dogName返回的是不带包名的dogKind返回的是种类struct。Go语言的反射中像数组切片Map指针等类型的变量,它们的.Name()都是返回空。
在reflect包中定义的Kind类型如下:

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        // 底层指针
)

具体看如下代码,仅演示了结构体类型:

func main() {

    type dog struct {
        eyes string
        fool int
    }
    b := dog{eyes: "眼睛", fool: 4}

    ref := reflect.TypeOf(b)
    // ref := reflect.TypeOf(&b)

    //ref保存的是带有包名的b的数据类型main.dog,自身数据类型是reflect.Type
    fmt.Printf("ref \nType:%T Value:%v \n", ref, ref)

    //Name()保存的是不带包名的b的数据类型dog,自身数据类型是string
    fmt.Printf("ref.Name \nType:%T Value:%v \n", ref.Name(), ref.Name())

    //Kind保存的是b的种类struct,自身数据类型是reflect.Kind
    fmt.Printf("ref.Kind \nType:%T Value:%v \n", ref.Kind(), ref.Kind())

    //注意如果TypeOf(b)中的b是指针,需要使用Elem()方法找到指针指向的真实的结构体实体,才能进行下面两个操作
    //NumField保存的是b中字段数量2,自身数据类型是int
    fmt.Printf("ref.NumField \nType:%T Value:%v \n", ref.NumField(), ref.NumField())
    //如果b是指针,效果同上
    // fmt.Printf("ref.NumField \nType:%T Value:%v \n", ref.Elem().NumField(), ref.Elem().NumField())

    //Field(0)保存的是第0个字段的各种信息,包括字段名,字段类型,字段tag等各种信息,自身数据类型是reflect.StructField
    fmt.Printf("ref.Field(0) \nType:%T Value:%v \n", ref.Field(0), ref.Field(0))
    fmt.Printf("ref.Field(1) \nType:%T Value:%v \n", ref.Field(1), ref.Field(1))
    //如果b是指针,效果同上
    // fmt.Printf("ref.Field(1) \nType:%T Value:%v \n", ref.Elem().Field(1), ref.Elem().Field(1))
}

ValueOf

reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。reflect.Value与原始值之间可以互相转换。

方法 说明
Interface() interface {} 将值以interface{}类型返回,可以通过类型断言转换为指定类型
Int() int64 将值以int类型返回,所有有符号整型均可以此方式返回
Uint() uint64 将值以uint类型返回,所有无符号整型均可以此方式返回
Float() float64 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool 将值以bool类型返回
Bytes() []bytes 将值以字节数组[]bytes类型返回
String() string 将值以字符串类型string返回

类型、值操作

所有的类型都可以用Set方法修改值。

类型 方法
实现接口 Type.Implements(Type) bool
assignable Type.AssignableTo(Type) bool
可转换类型 Type.ConvertibleTo(Type) bool
可比较 Type.Comparable() bool

basic types

类型 获取值 修改值
int, int8, int16, int32, int64 Int() int64 SetInt(int64)
uint, uint8, uint16, uint32, uint64 Uint() uint64 SeUint(uint64)
string String() string SetString(string)
bool Bool() bool SetBool(bool)
float32, float64 Float() float64 SetFloat(float64)
[]byte Bytes() []byte SetBytes([]byte)
complex64, complex128 Complex() complex128 SetComplex(complex128)

slice

操作 方法
创建 gMakeSlice(Type, int, int) Value
添加 gAppend(Value, ...Value) Value、AppendSlice(Value, Value) Value
获取长度 gValue.Len() int
设置长度 gValue.SetLen(int)
获取容量 gValue.Cap() int
设置容量 gValue.SetCap(int)
切片 gValue.Slice(int, int) Value(也可用于string、array)、Slice3(也可用于array)
下标获取 gValue.Index(int) Value
拷贝 gCopy(Value, Value)
生成交换函数(给sort包用) gSwapper(Value) func(int, int)
是否为空 gValue.IsNil() bool
类型 方法
元素类型 Type.Elem() Type

array

操作 方法
创建 New(Type) Value
获取长度 Value.Len() int
类型 方法
长度 Type.Len() int
元素类型 Type.Elem() Type

map

操作 方法
创建 MakeMap(Type) Value、MakeMapWithSize(Type) Value
添加/删除 Value.SetMapIndex(key, val Value)(删除时val为zero Value,可以写为reflect.Value{})
所有的键 Value.MapKeys() []Value
获取值 Value.MapIndex(Value) Value
获取长度 Value.Len() int
是否为空 Value.IsNil() bool
类型 方法
键类型 Type.Key() Type
元素类型 Type.Elem() Type

chan

操作 方法
创建 MakeChan(Type, int) Value
发送 Value.Send(Value)、Value.TrySend(Value) bool
接收 Value.Recv() (Value, bool)、Value.TryRecv() (Value, bool)
关闭 Value.Close()
获取长度 Value.Len() int
是否为空 Value.IsNil() bool
类型 方法
方向 Type.ChanDir() ChanDir
元素类型 Type.Elem() Type

func

操作 方法
创建 MakeFunc(Type, func([]Value) []Value) Value
调用 Value.Call([]Value)[]Value、Value.CallSlice([]Value) []Value
是否为空 Value.IsNil() bool
类型 方法
变长参数 Type.IsVariadic() bool
参数个数 Type.NumIn() int
参数类型 Type.In(int) Type
返回值个数 Type.NumOut() int
返回值类型 Type.Out(int) Type

struct

操作 方法
创建 New(Type) Value(New也可以用于创建基础类型如int等)
字段数量 Value.NumField() int
获取字段 Value.Field(int) Value、Value.FieldByName(string) Value、Value.FieldByIndex([]int) Value、FieldByFunc(func(string) bool) Value
方法数量 Value.NumMethod() int
获取方法 Value.Method(int) Value、Value.MethodByName(string) Value
类型 方法
方法数量 Type.NumMethod
获取方法详情 Type.Method(int) Method·Type.MethodByName(string) Method
是否实现接口 Type.Implements(Type) bool
字段详情 Type.Field(int) StructField、Type.FieldByIndex([]int) StructField、Type.FieldByName(string) (StructField, bool)、Type.FieldByNameFunc(func(string) bool) (StructField, bool)

ptr (interface类似)

操作 方法
创建 New(Type) Value(NewAt不列,不推荐用unsafe包)
解指针 Value.Elem() Value
是否为空 Value.IsNil() bool

实例

链表

func ceshiSteuct(na string, age int, a interface{}) {
    v := reflect.ValueOf(a)
    // t := reflect.TypeOf(a)

    //确保参数a是一个指针
    if v.Type().Kind() != reflect.Ptr {
        fmt.Println("结构体类型传参是传的值,所以必须要传指针进来")
        return
    }

    //确保参数a是一个struct类型
    if v.Elem().Type().Kind() != reflect.Struct {
        fmt.Println("请确保参数a是一个struct类型")
        return
    }

    if v.Elem().Type().Name() != "boy" {
        fmt.Println("请确保参数a是一个结构体boy类型")
    }

    // //获取指定字段名的值
    // // fmt.Println(v.Elem().FieldByName("Name"))
    // x, ok := v.Elem().Type().FieldByName("Name")
    // if ok {
    //   fmt.Printf("已经找到参数a中字段名为'Name'的字段\n值为:[%v] \n", x)
    // }

    //返回参数a的子类boy,字符串类型
    // fmt.Printf("Type:%T \n", v.Elem().Type().Name())

    //把参数a中的字段取出来存储到切片vfield中,方便后续操作
    var vfield []reflect.Value
    //返回参数a的内部字段数量
    // fmt.Println(v.Elem().Type().NumField())
    for i := 0; i < v.Elem().Type().NumField(); i++ {

        //获取参数a的字段名字,如Name,Age等
        // fmt.Println(v.Elem().Type().Field(i).Name)

        //获取参数a的字段数据类型
        // fmt.Println(v.Elem().Type().Field(i).Type)

        //获取参数a的字段的tag
        // fmt.Println(v.Elem().Type().Field(i).Tag)

        //获取参数a的字段的指定Tag,例如下面获取的是ini的Tag
        // fmt.Println(v.Elem().Type().Field(i).Tag.Get("ini"))

        //v.Elen().Field(i)取出参数a中的第i个字段,reflect.Value类型
        vfield = append(vfield, v.Elem().Field(i))
    }

    for _, n := range vfield {
        //取得参数a各字段的值
        // fmt.Printf("vfield: \nType:%T Value:%v \n", v, v)

        //取得各字段的数据类型
        // fmt.Printf("vfield: \nType:%T Value:%v \n", n.Type().Kind(), n.Type().Kind())

        //修改参数a的各字段值
        switch n.Kind() {
        case reflect.String:
            n.SetString(na)
        case reflect.Int:
            n.SetInt(int64(age))
        default:
            fmt.Println("暂时不支持的数据类型")
            return
        }

    }

}

func main() {
    type boy struct {
        Name string `ini:"name"`
        Age  int    `ini:"age"`
    }
    var li = boy{Name: "李雷", Age: 22}
    fmt.Println(li)
    //结构体和slice,map,chan不一样,结构体默认传值,必须要传指针进去才能进行下一步修改操作
    ceshiSteuct("韩梅梅", 20, &li)
    fmt.Println(li)
}

实例

用反射法值的简单实例:

func ceshiInt(a interface{}) {
    //v取得动态值信息
    v := reflect.ValueOf(a)
    //查看v的动态值的类型信息
    if v.Elem().Type().Kind() == reflect.Int {
        //修改v的动态值
        v.Elem().SetInt(29)
        return
    }
    fmt.Println("失败")
}
func main() {
    var i int = 3
    fmt.Println(i)
    ceshiInt(&i)
    fmt.Println(i)
}

实例

p是一个bool指针,把b的地址传进函数怎么用反射法修改*p的值

func ceshiPrt(a interface{}) {
    v := reflect.ValueOf(a)
    //直接传p就走这里
    if v.Elem().Kind() == reflect.Bool {
        v.Elem().SetBool(true)
        return
    }
    //这里是&p
    fmt.Printf("不带Elem是p的地址 \nType:%T Value:%v \n", v, v)
    //这里是&i
    fmt.Printf("1个Elem是p的值,也就是i的地址 \nType:%T Value:%v \n", v.Elem(), v.Elem())
    //这里才是真正的i的值
    fmt.Printf("2个Elem才是i的值 \nType:%T Value:%v \n", v.Elem().Elem(), v.Elem().Elem())
    if v.Elem().Elem().Kind() == reflect.Bool {
        fmt.Println("找到根儿了")
        v.Elem().Elem().SetBool(true)
        return
    }
    fmt.Println("失败")
}
func main() {
    //i的值是bool类型的false
    var i bool = false
    //p的值是i的地址
    p := &i
    fmt.Printf("&i:[%v] \n", &i)
    fmt.Printf("&p:[%v] \n", &p)
    //未修改的*p
    fmt.Println(*p)
    //传p的地址进去
    ceshiPrt(&p)
    //修改后的*p
    fmt.Println(*p)
}

实例

数组类型用反射法修改数组元素的值:

func ceshiArray(a interface{}) {
    //v取得动态值信息
    v := reflect.ValueOf(a)

    //也能拿到a的类型array
    // fmt.Println(v.Elem().Kind())
    //也能拿到a的元素长度3
    // fmt.Println(v.Elem().Len())
    //Index取得数组i的第0个元素
    // fmt.Printf("Index \nType:%T Value:%v \n", v.Elem().Index(0), v.Elem().Index(0))
    //获取数组i第0个元素的数据类型reflect.String
    // fmt.Printf("Index.Kind \nType:%T Value:%v \n", v.Elem().Index(0).Kind(), v.Elem().Index(0).Kind())

    //确保参数a是一个数组Array
    if v.Elem().Kind() != reflect.Array {
        fmt.Println("请确保参数a是一个数组")
        return
    }

    //循环修改参数a的各元素的值
    for i := 0; i < v.Elem().Len(); i++ {
        if v.Elem().Index(i).Kind() != reflect.String {
            fmt.Printf("请确保参数a的第%d元素是'string'类型。", i)
            return
        }
        //修改数组元素的值
        v.Elem().Index(i).SetString("mm")
    }
}
func main() {
    var i = [3]string{"aa", "bb", "cc"}
    fmt.Println(i)
    ceshiArray(&i)
    fmt.Println(i)
}

实例

用反射法修改切片各元素的值

func ceshiSlice(a interface{}) {

    v := reflect.ValueOf(a)

    //确保参数a是一个指针
    if v.Kind() != reflect.Ptr {
        fmt.Println("参数a必须是一个指针,否则不能修改数据~")
    }
    //确认参数是slice类型
    if v.Elem().Kind() != reflect.Slice {
        fmt.Println("参数a必须是一个slice类型")
    }

    // 返回切片的len()
    // fmt.Println(v.Elem().Len())
    // 返回切片的cap()
    // fmt.Println(v.Elem().Cap())
    // 设置len(),不能超出cap,否则报错
    // v.Elem().SetLen(3)
    // 设置cap,不能小于len也不能大于原来的cap。也就是说cap只能变小,不能变大
    // v.Elem().SetCap(3)

    for i := 0; i < v.Elem().Len(); i++ {
        //确保参数a的各元素都是string
        if v.Elem().Index(i).Kind() != reflect.String {
            fmt.Println("请确保参数a的元素都是string类型")
            return
        }
        //修改参数a的各元素数据
        v.Elem().Index(i).SetString("!xyz!")
    }

}

func main() {
    var i = []string{"aa", "bb", "cc", "dd"}

    fmt.Printf("Type:%T Len:%d Cap:%d \nValue:%v \n", i, len(i), cap(i), i)
    ceshiSlice(&i)
    fmt.Printf("Type:%T Len:%d Cap:%d \nValue:%v \n", i, len(i), cap(i), i)

}

实例

用反射法操作map。修改key的对应值,删除key,清空所有key,value,添加key,value等操作

func ceshiMap(sli []string, a interface{}) {

    //确保t的一致性,参数a可以是指针也可以不是指针
    t := reflect.TypeOf(a)
    v := reflect.ValueOf(a)

    if t.Kind() == reflect.Ptr {
        t = t.Elem()
        v = v.Elem()
    }

    //确保参数a的类型为map,key的类型为string
    if t.Kind() != reflect.Map || t.Key().Kind() != reflect.String {
        fmt.Println("请确保参数a是map类型,且key是string类型")
        return
    }

    //确保map类型键值对儿的值为bool类型
    if t.Elem().Kind() != reflect.Bool {
        fmt.Println("请确保键值对儿的值为bool类型")
        return
    }

    //获取v中所有的key以切片形式保存在keySlice中,keySlice中元素的数据类型是reflect.Value类型
    keySlice := v.MapKeys()

    //修改原有键值对儿的值
    if len(keySlice) != 0 {
        // //创建一个reflect.Bool类型的val,他的值为0。
        val := reflect.New(reflect.TypeOf(true))
        // //设置val的值为true
        val.Elem().SetBool(false)
        //修改一个原有key的值。
        v.SetMapIndex(keySlice[0], val.Elem())
        fmt.Printf("已将原有key为[%v]的值修改为[%v] \nValue:%v \n", keySlice[0], val.Elem(), v)
    }

    //删除一个原有键值对
    if len(keySlice) != 0 {
        //删除一个键值对儿,
        v.SetMapIndex(keySlice[0], reflect.ValueOf(nil))
        fmt.Printf("已删除了一个原有key为[%v]的键值对儿 \nValue:%v \n", keySlice[0], v)
    }

    //删除所有键值对儿
    for _, s := range keySlice {
        v.SetMapIndex(s, reflect.ValueOf(nil))
    }
    fmt.Printf("已清空了原有键值对儿 \nValue:%v \n", v)

    // //创建一个reflect.String类型的key,它的值为0。注意,这里TypeOf("aa")中的aa可以是任意string类型字符串,不管是啥字符串,key的值都是0
    // //key的值为空字符串,需要在遍历切片sli中对其赋值
    key := reflect.New(reflect.TypeOf("aa"))
    // //创建一个reflect.Bool类型的val,他的值为0。
    val := reflect.New(reflect.TypeOf(true))
    // //设置val的值为true
    val.Elem().SetBool(true)

    // 遍历切片sli
    for _, s := range sli {
        //设置key的值为"aa"
        key.Elem().SetString(s)
        //给v添加一个键值对,key是键,val是值
        v.SetMapIndex(key.Elem(), val.Elem())
        fmt.Println("已更新了所有原键值对儿")
    }

    // fmt.Println(t.Elem() == "book")
    // fmt.Printf("keySlice \nType:%T Len:%d Cap:%d \n", keySlice, len(keySlice), cap(keySlice))

    //创建一个reflect.String类型的key,它的值为0
    // key := reflect.New(reflect.TypeOf("aa"))
    //设置key的值为"aa"
    // key.Elem().SetString("aa")
    //创建一个reflect.Bool类型的val,他的值为0
    // val := reflect.New(reflect.TypeOf(true))
    //设置val的值为true
    // val.Elem().SetBool(true)
    //给v添加一个键值对,key是键,val是值
    // v.SetMapIndex(key.Elem(), val.Elem())
    //清空所有键值对
    // v.SetMapIndex(keySlice[0], reflect.ValueOf(nil))

}

func main() {
    i := make(map[string]bool, 5)
    i = map[string]bool{"name": true, "eyes": true}
    slice := []string{"aa", "bb", "cc"}

    fmt.Printf("Type:%T len:%d \nValue:%v \n", i, len(i), i)
    //这里注意,slice,map,chan默认就是传地址
    ceshiMap(slice, i)
    fmt.Printf("Type:%T len:%d \nValue:%v \n", i, len(i), i)
    fmt.Println("--------------------")
    fmt.Printf("Type:%T len:%d \nValue:%v \n", i, len(i), i)
    ceshiMap(slice, &i)
    fmt.Printf("Type:%T len:%d \nValue:%v \n", i, len(i), i)
}

实例

反射法操作结构体

func ceshiSteuct(na string, age int, a interface{}) {
    v := reflect.ValueOf(a)
    // t := reflect.TypeOf(a)

    //确保参数a是一个指针
    if v.Type().Kind() != reflect.Ptr {
        fmt.Println("结构体类型传参是传的值,所以必须要传指针进来")
        return
    }

    //确保参数a是一个struct类型
    if v.Elem().Type().Kind() != reflect.Struct {
        fmt.Println("请确保参数a是一个struct类型")
        return
    }

    if v.Elem().Type().Name() != "boy" {
        fmt.Println("请确保参数a是一个结构体boy类型")
    }

    // //获取指定字段名的值
    // // fmt.Println(v.Elem().FieldByName("Name"))
    // x, ok := v.Elem().Type().FieldByName("Name")
    // if ok {
    //   fmt.Printf("已经找到参数a中字段名为'Name'的字段\n值为:[%v] \n", x)
    // }

    //返回参数a的子类boy,字符串类型
    // fmt.Printf("Type:%T \n", v.Elem().Type().Name())

    //把参数a中的字段取出来存储到切片vfield中,方便后续操作
    var vfield []reflect.Value
    //返回参数a的内部字段数量
    // fmt.Println(v.Elem().Type().NumField())
    for i := 0; i < v.Elem().Type().NumField(); i++ {

        //获取参数a的字段名字,如Name,Age等
        // fmt.Println(v.Elem().Type().Field(i).Name)

        //获取参数a的字段数据类型
        // fmt.Println(v.Elem().Type().Field(i).Type)

        //获取参数a的字段的tag
        // fmt.Println(v.Elem().Type().Field(i).Tag)

        //获取参数a的字段的指定Tag,例如下面获取的是ini的Tag
        // fmt.Println(v.Elem().Type().Field(i).Tag.Get("ini"))

        //v.Elen().Field(i)取出参数a中的第i个字段,reflect.Value类型
        vfield = append(vfield, v.Elem().Field(i))
    }

    for _, n := range vfield {
        //取得参数a各字段的值
        // fmt.Printf("vfield: \nType:%T Value:%v \n", v, v)

        //取得各字段的数据类型
        // fmt.Printf("vfield: \nType:%T Value:%v \n", n.Type().Kind(), n.Type().Kind())

        //修改参数a的各字段值
        switch n.Kind() {
        case reflect.String:
            n.SetString(na)
        case reflect.Int:
            n.SetInt(int64(age))
        default:
            fmt.Println("暂时不支持的数据类型")
            return
        }

    }

}

func main() {
    type boy struct {
        Name string `ini:"name"`
        Age  int    `ini:"age"`
    }
    var li = boy{Name: "李雷", Age: 22}
    fmt.Println(li)
    //结构体和slice,map,chan不一样,结构体默认传值,必须要传指针进去才能进行下一步修改操作
    ceshiSteuct("韩梅梅", 20, &li)
    fmt.Println(li)
}

ini配置文件解析程序实例

ini配置文件

# 这里也是注释
[mysql]
address=192.168.1.12
port=1990
username=root
password=abcdefg

;[
#[]
;[  ]

# 这里是注释
[redis]
host=125.34.89.46 
port=1990
username =root
password= abcdefg
moren=true

ini解析程序

package main

import (
    "errors"
    "fmt"
    "io/ioutil"
    "reflect"
    "strconv"
    "strings"
)

//反射解析ini配置文件联系
//mysql配置
type MysqlConfig struct {
    Address  string `ini:"address"`
    Port     int    `ini:"port"`
    Username string `ini:"username"`
    Password string `ini:"password"`
}

//redis配置
type RedisConfig struct {
    Host     string `ini:"host"`
    Port     int    `ini:"port"`
    Username string `ini:"username"`
    Password string `ini:"password"`
    Moren    bool   `ini:"moren"`
}

//把mysqlConfig和redisConfig嵌套进一个大结构体Config内
type Config struct {
    MysqlConfig `ini:"mysql" json:"MySql"`
    RedisConfig `ini:"redis" json:"Redis"`
}

//打开文件读取文件内容
func ParsIniFile(fileName string, date interface{}) error {

    //校验a的有效性
    v := reflect.ValueOf(date)

    //参数date必须是一个指针,因为在函数内要对date进行赋值
    if v.Kind() != reflect.Ptr {
        return errors.New("参数date必须是一个结构体指针")
    }

    //date必须是一个结构体
    if v.Elem().Kind() != reflect.Struct {
        return errors.New("参数date必须是一个结构体指针")
    }

    //打开文件读取文件,把文件全部读取到lineBytr切片中
    fileByte, err := ioutil.ReadFile(fileName)
    if err != nil {
        return fmt.Errorf("读取%s文件失败。Err:%s \n", fileName, err)
    }

    // 把lineByte转换为字符串,并且以回车符"\n"分割为字符串切片
    fileSlice := strings.Split(string(fileByte), "\n")

    //保存参数data中的字段name,string类型
    var fieldName string

    //遍历读取到的文件内容
    for index, filestr := range fileSlice {
        //去除开头结尾的空白符
        filestr = strings.TrimSpace(filestr)

        //跳过空白行
        if len(filestr) == 0 {
            continue
        }

        //跳过";"和"#"开头的注释行
        if strings.HasPrefix(filestr, ";") || strings.HasPrefix(filestr, "#") {
            continue
        }

        //找到以"["开头的行当做"[mysql]"节处理
        if strings.HasPrefix(filestr, "[") {

            if !strings.HasSuffix(filestr, "]") {
                return fmt.Errorf("文件[%s]第[%d]语法错误,缺少']'", fileName, index+1)
            }

            //拿到"[]"的内容,以去除首尾空格。
            // 保存了从ini配置文件读取到的MysqlConfig结构体的Tag,数据类型是string
            structNameTag := strings.TrimSpace(filestr[1 : len(filestr)-1])

            //去掉首尾"["和"]"后,去掉首尾空格后计算长度,空字符串提示语法错误
            if len(structNameTag) == 0 {
                return fmt.Errorf("文件[%s]第[%d]语法错误,'[]'内长度为0", fileName, index+1)
            }

            //从主结构体参数data中取出各字段的Tag,和ini配置文件中取得的Tag做对比
            for i := 0; i < v.Elem().NumField(); i++ {

                //field依次获取到Config结构体内字段MysqlConfig和RedisConfig
                if structNameTag == v.Elem().Type().Field(i).Tag.Get("ini") {
                    //取得参数data字段的name,string类型
                    fieldName = v.Elem().Type().Field(i).Name
                    //这里如果注释掉break,ini配置文件如有相同内容,下方内容优先。
                    break
                }
            }

        } else {

            //存储MysqlConfig结构体,数据类型reflect.Value
            structField := v.Elem().FieldByName(fieldName)

            //date必须是一个结构体
            if structField.Type().Kind() != reflect.Struct {
                return fmt.Errorf("参数date的字段%s必须是一个结构体", fieldName)
            }

            //处理=号左右的内容
            //如果以"="开头提示语法错误
            if strings.HasPrefix(filestr, "=") {
                return fmt.Errorf("文件%s第%d行语法错误", fileName, index+1)
            }

            // 取出ini文件=号左右的内容,以=号分隔为切片
            fieldTag := strings.Split(filestr, "=")

            //按照从ini文件中读取到的fieldTag找到对应的结构体字段
            for i := 0; i < structField.NumField(); i++ {

                //判断从ini配置文件获取的MysqlConfig字段是否有效
                if structField.Type().Field(i).Name != strings.TrimSpace(fieldTag[0]) && structField.Type().Field(i).Tag.Get("ini") != strings.TrimSpace(fieldTag[0]) {
                    continue
                }

                // //取得最终的字段例如Host,Uesename等信息,reflect.Value类型
                field := structField.Field(i)

                // fmt.Println(structField.Type().Field(i).Type)
                // fmt.Println(field.Type().Kind())
                //给结构体赋值
                switch field.Type().Kind() {
                //string类型直接赋值操作
                case reflect.String:
                    field.SetString(strings.TrimSpace(fieldTag[1]))

                    //布尔类型赋值操作
                case reflect.Bool:
                    var v bool
                    //用strconc包把字符串转换为系统支持的布尔类型
                    v, err = strconv.ParseBool(strings.TrimSpace(fieldTag[1]))
                    if err != nil {
                        return fmt.Errorf("ini配置文件%s第%d行语法错误,Bool类型仅支持如下语法:\n1,0,t,f,T,F,true,false,True,False,TRUE,FALSE\n。", fieldName, index+1)
                    }
                    //结构体字段赋值操作
                    field.SetBool(v)

                //int类型用strconv包转换string为int类型后赋值操作
                case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
                    var v int64
                    v, err = strconv.ParseInt(strings.TrimSpace(fieldTag[1]), 10, 64)
                    if err != nil {
                        return fmt.Errorf("ini配置文件%s第%d行语法错误,这里应该是一串数字\n。", fieldName, index+1)
                    }
                    field.SetInt(v)

                //uint类型用strconv包转换string为int类型后赋值操作
                case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
                    var v uint64
                    v, err = strconv.ParseUint(strings.TrimSpace(fieldTag[1]), 10, 64)
                    if err != nil {
                        return fmt.Errorf("ini配置文件%s第%d行语法错误,这里应该是一串数字\n。", fieldName, index+1)
                    }
                    field.SetUint(v)
                }

            }

        }
    }
    return nil
}
func main() {
    var iniConfig Config
    // var cfg string
    err := ParsIniFile("./a.ini", &iniConfig)
    if err != nil {
        fmt.Printf("Err:%v \n", err)
    }
    fmt.Println(iniConfig)

}

实例

func aaa(a interface{}) {
    // ref := reflect.ValueOf(a)
    // ref0 := ref.Elem().Field(0)
    // ref.Type()
    // fmt.Printf("ref: \nType:%T Value:%v \n", ref0, ref0.Type())
}

func main() {

    // type dog struct {
    //   eyes string
    //   sex  bool
    //   fool int
    // }
    // b := dog{eyes: "眼睛", fool: 4, sex: true}

    // ret := reflect.ValueOf(b)
    //ValueOf保存的是b中各字段的值:{眼睛 true 4}
    // fmt.Printf("ret: \nType:%T Value:%v \n", ret, ret)
    //Kind保存的是结构体b的种类struct,数据类型是reflect.Kind
    // fmt.Printf("Kind: \nType:%T Value:%v \n", ret.Kind(), ret.Kind())
    //NumField中结构体b中的字段数量3,数据类型是int
    // fmt.Printf("NumField: \nType:%T Value:%v \n", ret.NumField(), ret.NumField())
    //field中保存的是结构体b中第0个字段的值"眼睛",数据类型是reflect.Value
    // fmt.Printf("Field: \nType:%T Value:%v \n", ret.Field(0), ret.Field(0))
    //Field.Type保存的是结构体b中第0个字段的类型信息string
    // fmt.Printf("Field: \nType:%T Value:%v \n", ret.Field(0), ret.Field(0).Type())

    // fmt.Printf("Field: \nType:%T Value:%v \n", ret.Field(0), ret.Field(0).Elem())
    // fmt.Printf("Elem \nType:%T Value:%v \n", ret.Elem(), ret.Elem())
    // fmt.Printf("%v \n", ret.Field(0).CanSet())
    //
    // fmt.Println(b)
    // aaa(&b)
    // fmt.Println(b)

}

  转载请注明: So Cold Go语言反射

 上一篇
Time包 Time包
time包学习练习测试时间类型:time.Time获取当前时间对象time.Now(),然后就可以通过这个时间对象获得详细的时间信息了,包括年月日时分秒等信息。 func main() { //初始化一个新的时间对象 now
2020-05-04
下一篇 
接口 接口
在Go语言中接口interface是一种类型,一种抽象的类型。interface是一组method的集合,是duck-type programming的一种体现。接口做的事情就像是定义一个协议规则,只要一台机器有洗衣服和甩干的功能,我就称它
2020-04-22
  目录