医院网站建设步骤,四川省城乡住房建设部网站,顺德建网站的公司,wordpress子站点反射
反射的存在意义
在开发中#xff0c;你或许会碰到在有些情况下#xff0c;你需要获取一个对象的类型#xff0c;属性及方法#xff0c;而这个过程其实就是反射。 golang中变量包括#xff08;type, value#xff09;两部分
静态类型 所谓的静态类型#xff08;…反射
反射的存在意义
在开发中你或许会碰到在有些情况下你需要获取一个对象的类型属性及方法而这个过程其实就是反射。 golang中变量包括type, value两部分
静态类型 所谓的静态类型即 static type就是变量声明的时候的类型。
var age int // int 是静态类型
var name string // string 也是静态类型它是你在编码时肉眼可见的类型。
动态类型 所谓的动态类型即 concrete type也叫具体类型是程序运行时系统才能看见的类型。
var i interface{}
i 18
i Golang第一行我们在给 i 声明了 interface{} 类型所以 i 的静态类型就是 interface{} 第二行当我们给变量 i 赋一个 int 类型的值时它的静态类型还是 interface{}这是不会变的但是它的动态类型此时变成了 int 类型。 第三行当我们给变量 i 赋一个 string 类型的值时它的静态类型还是 interface{}它还是不会变但是它的动态类型此时又变成了 string 类型。 从以上可以知道不管是 i18 还是 i“Golang”都是当程序运行到这里时变量的类型才发生了改变这就是我们最开始所说的动态类型是程序运行时系统才能看见的类型。
接口组成 每个接口变量实际上都是由一 pair 对type 和 data组合而成pair 对中记录着实际变量的值和类型。 比如下面这条语句
var age int 25我们声明了一个 int 类型变量变量名叫 age 其值为 25。
接口细分 一个interface{}类型的变量包含了2个指针一个指针指向值的类型【对应concrete type】另外一个指针指向实际的值【对应value】。 根据接口是否包含方法可以将接口分为 iface 和 eface。 iface 第一种iface表示带有一组方法的接口。 比如
type Phone interface {call()
}eface 第二种eface表示不带有方法的接口 比如
var i interface{}先来看看 iface有如下一段代码
var reader io.Readertty, err : os.OpenFile(/dev/tty, os.O_RDWR, 0)
if err ! nil {return nil, err
}reader tty第一行代码var reader io.Reader 由于 io.Reader 接口包含 Read 方法所以 io.Reader 是 iface此时 reader 对象的静态类型是 io.Reader暂无动态类型。 最后一行代码reader ttytty 是一个 *os.File 类型的实例此时reader 对象的静态类型还是 io.Reader而动态类型变成了 *os.File。 再来看看 eface有如下一段代码
//不带函数的interface
var empty interface{}tty, err : os.OpenFile(/dev/tty, os.O_RDWR, 0)
if err ! nil {return nil, err
}empty tty第一行代码var empty interface{}由于 interface{} 是一个 eface其只有一个 _type 可以存放变量类型此时 empty 对象的静态类型是 nil。 最后一行代码empty ttytty 是一个 *os.File 类型的实例此时 _type 变成了 *os.File。 由于动态类型的存在在一个函数中接收的参数的类型有可能无法预先知晓此时我们就要对参数进行反射然后根据不同的类型做不同的处理。反射就是用来检测存储在接口变量内部(值value类型concrete type) pair对的一种机制。
反射中的Type 和 Value 在真实世界里type 和 value 是合并在一起组成接口变量(pair对)的。 而在反射的世界里type 和 data 却是分开的他们分别由 reflect.Type 和 reflect.Value 来表现。
Golang里有个反射三定律 Reflection goes from interface value to reflection object. Reflection goes from reflection object to interface value. To modify a reflection object, the value must be settable.
翻译一下就是
反射可以将接口类型变量 转换为“反射类型对象”
反射可以将 “反射类型对象”转换为 接口类型变量
如果要修改 “反射类型对象” 其类型必须是 可写的
第一定律 为了实现从接口变量到反射对象的转换需要提到 reflect 包里很重要的两个方法 reflect.TypeOf(i) 获得接口值的类型 reflect.ValueOf(i)获得接口值的值 这两个方法返回的对象我们称之为反射对象Type object 和 Value object。
import (fmtreflect
)func main() {var age interface{} 25//%T输出值的类型 %v: 使用默认格式输出值fmt.Printf(原始接口变量的类型为 %T值为 %v\n,age,age)t : reflect.TypeOf(age)v : reflect.ValueOf(age)//从接口变量到反射对象fmt.Printf(从接口变量到反射对象type对象的类型为 %T\n,t)fmt.Printf(从接口变量到反射对象value对象的类型为 %T\n,v)
}第二定律 完成从反射对象到接口变量的转换 注意只有 Value 才能逆向转换而 Type 则不行
func main() {var age interface{} 25//%T输出值的类型 %v: 使用默认格式输出值fmt.Printf(原始接口变量的类型为 %T值为 %v\n,age,age)t : reflect.TypeOf(age)v : reflect.ValueOf(age)//从接口变量到反射对象fmt.Printf(从接口变量到反射对象type对象的类型为 %T\n,t)fmt.Printf(从接口变量到反射对象value对象的类型为 %T\n,v)//从反射对象到接口变量//通过Interface函数实现i : v.Interface()fmt.Printf(从接口变量到反射对象value对象的类型为 %T 值为%v\n,i,i)
}最后转换后的对象静态类型为 interface{} 如果要转成最初的原始类型需要再类型断言转换一下
第三定律 反射世界是真实世界的一个『映射』是我的一个描述但这并不严格因为并不是你在反射世界里所做的事情都会还原到真实世界里。 第三定律引出了一个 settable 可设置性或可写性的概念。 Golang 语言里的函数都是值传递只要你传递的不是变量的指针你在函数内部对变量的修改是不会影响到原始的变量的。 回到反射上来当你使用 reflect.Typeof 和 reflect.Valueof 的时候如果传递的不是接口变量的指针反射世界里的变量值始终将只是真实世界里的一个拷贝你对该反射对象进行修改并不能反映到真实世界里。
因此在反射的规则里还要注意 不是接收变量指针创建的反射对象是不具备『可写性』的 是否具备『可写性』可使用 CanSet() 来获取得知 对不具备『可写性』的对象进行修改是没有意义的也认为是不合法的因此会报错。 var name string hello worldv : reflect.ValueOf(name)fmt.Println(可写性为:,v.CanSet())要让反射对象具备可写性需要注意两点 创建反射对象时传入变量的指针 使用 Elem()函数返回指针指向的数据
func main() {var name string hello worldv : reflect.ValueOf(name)fmt.Println(可写性为:,v.CanSet())v2 : v.Elem()fmt.Println(v2可写性为:,v2.CanSet())
}反射中的函数
获取类别Kind() Type 对象 和 Value 对象都可以通过 Kind() 方法返回对应的接口变量的基础类型。
reflect.TypeOf(m).Kind()
reflect.ValueOf(m).Kind()在这里要注意的是Kind 和 Type 是有区别的Kind 表示更基础范围更广的分类。 有一个例子来表示 iPhone 接口变量的 Type 是手机Kind 是电子产品。 Kind 表示的基本都是 Go 原生的基本类型共有 25 种的合法类型
type Kind uintconst (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 // 底层指针
)/* kink函数的用法 */
func main() {m : Profile{}t : reflect.TypeOf(m)/* Type: main.ProfileKind struct */fmt.Println(Type:,t)//1.传入值fmt.Println(Kind ,t.Kind())//2.传入指针v : reflect.ValueOf(m)/* m Type : *main.Profilem Kind : ptrm Type : main.Profilem Kind : struct */fmt.Println(m Type :,v.Type())fmt.Println(m Kind :,v.Kind())//Elem会返回 Type 对象所表示的指针指向的数据fmt.Println(m Type :,v.Elem().Type())fmt.Println(m Kind :,v.Elem().Kind())}进行类型的转换
eg Int() 转成 int 示例代码如下
package mainimport (fmtreflect
)func main() {var age int 25v1 : reflect.ValueOf(age)fmt.Printf(转换前 type: %T, value: %v \n, v1, v1)v2 : v1.Int()fmt.Printf(转换后 type: %T, value: %v \n, v2, v2)
}对属性的操作
func main() {p : Pearson{xy,21,male}v:reflect.ValueOf(p)//NumField()fmt.Println(字段数,v.NumField())//Fieldfmt.Println(第一个字段,v.Field(0))fmt.Println(第一个字段,v.Field(1))fmt.Println(第一个字段,v.Field(2))//通过遍历的方式进行打印for i:0;iv.NumField();i{fmt.Printf(第 %d个字段%v\n,i1,v.Field(i))}
}对方法的操作
type Pearson struct{name stringage intgender string
}func(p Pearson)SayBye(){fmt.Println(Bye)
}func(p Pearson)SayHello(){fmt.Println(Hello)
}func main() {p : Pearson{xy,21,male}
//注意使用 TypeOfv:reflect.TypeOf(p)fmt.Println(方法数,v.NumMethod())fmt.Println(第一个方法,v.Method(0).Name)fmt.Println(第一个方法,v.Method(1).Name)//通过遍历的方式进行打印for i:0;iv.NumMethod();i{fmt.Printf(第 %d个方法%v\n,i1,v.Method(i).Name)}
}反射说明 Golang 的反射很慢这个和它的 API 设计有关 不到不得不用的地步能避免使用反射就不用。