Golang reflect详解

1. 什么是反射

        通俗来讲就是, go 语言中提供一种机制,可以在代码运行时获取变量的类型和值,这种机制就是反射。

        反射是由 reflect 包提供支持. 它定义了两个重要的类型, Type 和 Value. 一个 Type 表示一个Go类型. 函数 reflect.TypeOf 接受任意的 interface{} 类型, 并返回对应动态类型的reflect.Type.

        reflect 包中另一个重要的类型是 Value. 一个 reflect.Value 可以持有一个任意类型的值. 函数 reflect.ValueOf 接受任意的 interface{} 类型, 并返回对应动态类型的reflect.Value. 和 reflect.TypeOf 类似, reflect.ValueOf 返回的结果也是对于具体的类型, 但是 reflect.Value 也可以持有一个接口值.

        反射的使用场景:写测试用例的时候可以使用反射 

        其他场景尽量不要使用反射,原因如下: 
        1. 业务代码中写反射,会增加复杂性,让人难以理解 
        2. 反射性能比较低,比正常代码要慢一到两个数量级 
        3. 反射并不能在编译时检查出错误,在运行时可能会出现panic 
        所以在正常代码中尽量不要使用反射。

2. 反射使用示例及分析

2.1 已知变量值,转化为对应的struct

  • 示例如下:
package main

import (
    "fmt"
    "reflect"
)

type Addr struct {
    Province string
    City string
    Telephone string
}

func main() {
    addr := Addr {
        Province: "HuNan",
        City: "ShaoYang",
        Telephone: "15511111111",
    }
    reflectValue := reflect.ValueOf(addr)
    refectValueOfAddr, _ := reflectValue.Interface().(Addr)
    fmt.Printf("Province: %s, City: %s, Phone: %s\n", refectValueOfAddr.Province, refectValueOfAddr.City, refectValueOfAddr.Telephone)
}
  • TypeOf方法,ValueOf方法和FieldByName方法
type Addr struct {
    Province string
    City string
    Telephone string
}

type Student struct {
    Name string
    Age  int
    Sex  string
    Address Addr
}

func testTypeOfAndValueOf() {
    fmt.Println("--------------start testTypeOfAndValueOf----------------")

    student := new(Student)
    student.Name = "python"
    fmt.Println(reflect.TypeOf(student))

    var student2 Student
    student2.Name = "golang"
    fmt.Println(reflect.TypeOf(student2))

    fmt.Println(reflect.ValueOf(student2).FieldByName("Name"))

    fmt.Println("--------------end testTypeOfAndValueOf----------------")
}
  • CanSet方法
func testCanSet() {
    fmt.Println("--------------start testCanSet----------------")

    var student Student
    student.Name = "golang"
    fmt.Println("reflect.ValueOf(student).FieldByName(\"Name\").CanSet()", reflect.ValueOf(student).FieldByName("Name").CanSet())
    fmt.Println("reflect.ValueOf(&(student.Name)).Elem().CanSet()", reflect.ValueOf(&(student.Name)).Elem().CanSet())

    c := "golang"
    p := reflect.ValueOf(&c)
    fmt.Println("p.CanSet() = ", p.CanSet())
    fmt.Println("p.Elem().CanSet() = ", p.Elem().CanSet())
    p.Elem().SetString("newName")
    fmt.Println("c = ", c)

    fmt.Println("--------------end testCanSet----------------")
}

2.2 type相关方法

package main

import (
	"reflect"

	"fmt"
)

type lx interface {
	SayHi()
}

type User struct {
	Name string
	Age  int64
	Sex  string
}

func (u *User) SayHi() {
	fmt.Println("hello world")
}

func main() {
	user := User{"张三", 25, "男"}
	FillStruct(user)
}

func FillStruct(obj interface{}) {
	t := reflect.TypeOf(obj)       //反射出一个interface{}的类型
	fmt.Println(t.Name())          //类型名
	fmt.Println(t.Kind().String()) //Type类型表示的具体分类
	fmt.Println(t.PkgPath())       //反射对象所在的短包名
	fmt.Println(t.String())        //包名.类型名
	fmt.Println(t.Size())          //要保存一个该类型要多少个字节
	fmt.Println(t.Align())         //返回当从内存中申请一个该类型值时,会对齐的字节数
	fmt.Println(t.FieldAlign())    //返回当该类型作为结构体的字段时,会对齐的字节数

	var u User
	fmt.Println(t.AssignableTo(reflect.TypeOf(u)))  // 如果该类型的值可以直接赋值给u代表的类型,返回真
	fmt.Println(t.ConvertibleTo(reflect.TypeOf(u))) // 如该类型的值可以转换为u代表的类型,返回真

	fmt.Println(t.NumField())             // 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
	fmt.Println(t.Field(0).Name)          // 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
	fmt.Println(t.FieldByName("Age"))     // 返回该类型名为name的字段(会查找匿名字段及其子字段),布尔值说明是否找到,如非结构体将panic
	fmt.Println(t.FieldByIndex([]int{0})) // 返回索引序列指定的嵌套字段的类型,等价于用索引中每个值链式调用本方法,如非结构体将会panic
}

2.3 value相关方法

package main

import (
	"reflect"

	"fmt"
)

type User struct {
	Name  string
	Age   int
	Sex   bool
	Phone *string
	Qian  float64
	Atest uint
	Group interface{}
	Btest interface{}
}

func (u *User) Hello() {
	fmt.Println("hello world 你好世界")
}

func main() {

	a := "hello world 你好世界"
	user := &User{"张三", 25, true, &a, 88.8, 9, 99, nil}

	var obj interface{} = user
	v := reflect.ValueOf(obj)

	method := v.MethodByName("Hello") //返回v的名为Hello的方法
	method.Call([]reflect.Value{})    //执行反射的方法

	fmt.Println(v.IsValid()) //返回v是否持有值,如果v是value零值会返回假,此时v除了IsValid String Kind之外的方法都会导致panic
	fmt.Println(v.Kind())    //返回v持有值的类型,如果是结构体类型,返回 struct
	fmt.Println(v.Type())    //返回v持有值的类型Type表示,返回具体定义类型, 例如User

	v = v.Elem() //返回持有的接口的值,或者指针的值,如果不是interface{}或指针会panic,实际上是从 *User到User
	var u User
	fmt.Println(v.Convert(reflect.TypeOf(u)).FieldByName("Name")) //转换为其他类型的值,如果无法使用标准Go转换规则来转换,那么panic

	fmt.Println(v.FieldByName("Name").CanSet())   //是否可以设置Name的值
	v.FieldByName("Name").SetString("把Name值修改一下") //设置v的持有值,如果v的kind不是string或者v.Canset()返回假,会panic
	v.FieldByName("Name").Set(reflect.ValueOf(a)) //将v的持有值修改为a的反射值,如果Canset返回假,会panic

	fmt.Println(v.FieldByName("Group").Elem())     //返回持有的接口的值,或者指针的值,如果不是interface{}或指针会panic
	fmt.Println(v.FieldByName("Phone").Elem())     //或者指针的值
	fmt.Println(v.FieldByName("Name").Interface()) //把Name当做interface{}值

	fmt.Println(v.FieldByName("Name").String()) //返回v持有的值的字符串表示,如果v的值不是string也不会panic
	fmt.Println(v.FieldByName("Sex").Bool())    //返回持有的布尔值,如果v的kind不是bool会panic
	fmt.Println(v.FieldByName("Age").Int())     //返回持有的int64,如果v的kind不是int int8-int64会panic

	var x int64
	fmt.Println(v.FieldByName("Age").OverflowInt(x)) //如果v持有值的类型不能无一出的表示x,会返回真,如果v的kind不是int int8-int64会panic
	fmt.Println(v.FieldByName("Atest").Uint())       //返回v持有的无符号整数,如果v的kind不是uint uintptr uint8 uint16 uint32 uint64会panic

	var x2 uint64
	fmt.Println(v.FieldByName("Atest").OverflowUint(x2)) //如果v持有的值的类型不能无溢出的表示x2,会返回真,如果v的kind不是uint uintptr uint8 uint16 uint32 uint64会panic
	fmt.Println(v.FieldByName("Qian").Float())           //返回v持有的浮点数float64,如果v的kind不是float32 float64会panic

	var x3 float64
	fmt.Println(v.FieldByName("Qian").OverflowFloat(x3)) //如果v持有值的类型不能无溢出的表示x3,会返回真,如果v的kind不是float32 float64会panic
	fmt.Println(v.FieldByName("Btest").IsNil())          //如果v持有值是否为nil,如果v的值不是通道 函数 接口 映射 指针 切片之一会panic

	fmt.Println(v.NumField())             //返回v持有的结构体类型值的字段数,如果v的kind不是struct会panic
	fmt.Println(v.Field(0))               //返回结构体的第i个字段,如果v的kind不是struct或i出界会panic
	fmt.Println(v.FieldByIndex([]int{0})) //和上面一样,没明白有啥用

}

3. Type和value方法汇总

3.1 Type和Value拥有的同名方法

TypeValue备注
KindKind表示 特定类型,go中定义的类型 ,如自己定义的 type Test struct {}, Kind 就是 struct
MethodByNameMethodByName根据方法名找方法
MethodMethod返回第i个方法
NumMethodNumMethod返回拥有的方法总数,包括unexported方法
FieldField取struct结构的第n个field
FieldByIndexFieldByIndex嵌套的方式取struct的field,比如v.FieldByIndex(1,2,3)等价于 v.field(1).field(2).field(3)
FieldByNameFuncFieldByNameFunc返回名称匹配match函数的field
NumFieldNumField返回struct所包含的field数量

3.2 Type独有的方法

方法名备注
Align分配内存时的内存对齐字节数
FieldAlign作为struct的field时内存对齐字节数
Nametype名 string类型
PkgPath包路径, "encoding/base64", 内置类型返回empty string
Size该类型变量占用字节数
Stringtype的string表示方式
Implements判断该类型是否实现了某个接口
AssignableTo判断该类型能否赋值给某个类型
ConvertibleTo判断该类型能否转换为另外一种类型
Comparable判断该类型变量是否可以比较
ChanDir返回channel的方向 recv/send/double
IsVariadic判断函数是否接受可变参数
Elem取该类型的元素
In函数第n个入参
Out函数第n个出参
NumIn函数的入参数个数
NumOut函数的出参个数
Key返回map结构的key类型Type
Len返回array的长度

3.3 Value独有的方法

方法名备注
Addrv的指针,前提时CanAddr()返回true
Boolbool类型变量的值
Bytes[]bytes类型的值
Call调用函数
CallSlice调用具有可变参的函数
CanAddr判断能否取址
CanInterface判断Interface方法能否使用
CanSet判断v的值能否改变
Cap判断容量 Array/Chan/Slice
Close关闭Chan
Complex 
Convert返回将v转换位type t的结果
Elem返回interface包含的实际值
Float 
Index索引操作 Array/Slice/String
Int 
Interface将当前value以interface{}形式返回
IsNil判断是否为nil,chan, func, interface, map, pointer, or slice value
IsValid是否是可操作的Value,返回false表示为zero Value
Len适用于Array, Chan, Map, Slice, or String
MapIndex对map类型按key取值
MapKeysmap类型的所有key的列表
OverflowComplex 
OverflowFloat溢出判断
OverflowInt 
OverflowUint 
Pointer返回uintptr 适用于slice
Recvchan接收
Sendchan发送
Set将x赋值给v,类型要匹配
SetBool 
SetBytes 
SetCapslice调整切片
SetMapIndexmap赋值
SetUint 
SetPointerunsafe.Pointer赋值
SetString 
Slicereturn v[i:j] 适用于Array/Slict/String
Stringreturn value的string表示方法
TryRecvchan非阻塞接收
Try Sendchan非阻塞发送
Type返回value的Type
UnsafeAddr返回指向value的data的指针
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值