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持有值的分类,如果v是value零值,返回值为invalid
	fmt.Println(v.Type())    //返回v持有值的类型Type表示

	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拥有的同名方法

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

3.2 Type独有的方法

方法名 备注
Align 分配内存时的内存对齐字节数
FieldAlign 作为struct的field时内存对齐字节数
Name type名 string类型
PkgPath 包路径, "encoding/base64", 内置类型返回empty string
Size 该类型变量占用字节数
String type的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.3Value独有的方法

方法名 备注
Addr v的指针,前提时CanAddr()返回true
Bool bool类型变量的值
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取值
MapKeys map类型的所有key的列表
OverflowComplex  
OverflowFloat 溢出判断
OverflowInt  
OverflowUint  
Pointer 返回uintptr 适用于slice
Recv chan接收
Send chan发送
Set 将x赋值给v,类型要匹配
SetBool  
SetBytes  
SetCap slice调整切片
SetMapIndex map赋值
SetUint  
SetPointer unsafe.Pointer赋值
SetString  
Slice return v[i:j] 适用于Array/Slict/String
String return value的string表示方法
TryRecv chan非阻塞接收
Try Send chan非阻塞发送
Type 返回value的Type
UnsafeAddr 返回指向value的data的指针

你可能感兴趣的:(golang)