反射(reflect)机制
什么是反射
官方对此有个非常简明的介绍,两句话耐人寻味:
- 反射提供一种让程序检查自身结构的能力
- 反射是困惑的源泉
反射的三个定律 反射就是检查interface的(value, type)对的,因为任何类型的变量或方法都是实现了空接口。具体一点说就是Go提供一组方法提取interface的value,提供另一组方法提取interface的type.
官方提供了三条定律来说明反射,比较清晰,下面也按照这三定律来总结。
反射包里有两个接口类型要先了解一下.
reflect.Type
提供一组接口处理interface的类型,即(value, type)中的typereflect.Value
提供一组接口处理interface的值,即(value, type)中的value
reflect.Type
类型对象reflect.Value
类型对象
这里的reflect类型指的是reflect.Type和reflect.Value类型的变量
package mainimport (
"fmt"
"reflect"
)func main() {
var x float64 = 8.5
t := reflect.TypeOf(x)// 这里的t类型为:reflect.Type
fmt.Println("type:", t)
v := reflect.ValueOf(x) // 这里的v类型为:reflect.Value
fmt.Println("value:", v)
}
2. 反射第二定律:反射可以将reflect类型对象还原成interface类型对象
package mainimport (
"fmt"
"reflect"
)func main() {
var x float64 = 8.5
v := reflect.ValueOf(x) //这里v的类型为:reflect.Value
var y float64 = v.Interface().(float64) //v通过Interface()函数将反射类型转换为interface类型变量,再通过断言为float64获取值
fmt.Println("value:", y)
}
3. 反射第三定律:如果要修改reflect类型对象,则value必须是可设置的
package mainimport (
"reflect"
"fmt"
)func main() {
var x float64 = 8.5
fmt.Println("x :", x)
v := reflect.ValueOf(&x)
fmt.Println("settability of v:", v.CanSet())
//v.SetFloat(5.8) // 这里会报panic的错误,因为这时候v是不可设置的
fmt.Println("settability of v:", v.Elem().CanSet())
v.Elem().SetFloat(5.8)
fmt.Println("x :", v.Elem().Interface().(float64))
}
上面11行v代表的是指针地址,我们要设置的是指针所指向的内容,也即我们想要修改的是
*v
。 那怎么通过v修改x的值呢?reflect.Value
提供了Elem()
方法,可以获得指针向指向的value
,也就是第14行和第15行的操作。反射的使用 1. 查看结构体类型、字段、方法、匿名字段、字段tag
package mainimport (
"fmt"
"reflect"
)//定义结构体
type User struct {
Id int
Name string
Age int
}type Boy struct {
User
Addr string `db:"addr"`
}//绑定方法
func (u User) Say() {
fmt.Println("Hello")
}func (u User) Eat() {
fmt.Println("Eat price")
}func PrintInfo(i interface{}){
t := reflect.TypeOf(i)
fmt.Println("结构体类型:", t)
v := reflect.ValueOf(i)
fmt.Println("结构体的值:", v)fmt.Println("\n结构体字段名称:字段类型 \t : 值 \t\t : 是否为匿名字段: 字段tag为db的值")
for i := 0;
i < t.NumField();
i++ {
f := t.Field(i)
val := v.Field(i)
fmt.Printf("%s\t\t : %v\t : %v\t : %v\t : %v\n", f.Name, f.Type, val.Interface(), f.Anonymous, f.Tag.Get("db"))
//fieldByName, _ := t.FieldByName(f.Name)
//fmt.Println(fieldByName)
}
fmt.Println("\n结构体绑定的方法:方法类型\t:方法地址 \t\t 方法索引 ")
for i := 0;
i < t.NumMethod();
i++ {
m := t.Method(i)
fmt.Printf("%s\t\t %v\t %v\t %v\n", m.Name, m.Type, &m.Func, m.Index)
//Func, _ := t.MethodByName(m.Name)
//fmt.Println(Func)
}
}func main() {b := Boy{
User: User{
Id:1,
Name: "Yuan",
Age:26,
},
Addr: "chengdu",
}
PrintInfo(b)
}
2. 修改结构体字段的值
package mainimport (
"fmt"
"reflect"
)//定义结构体
type User struct {
Id int
Name string
Age int
}func SetValue(i interface{}){
v := reflect.ValueOf(i)
//获取指针指向的元素
elem:= v.Elem()
//获取要修改的字段
name := elem.FieldByName("Name")
if name.Kind() == reflect.String && name.CanSet(){
name.SetString("老大")
}
}func main() {u := User{
Id:1,
Name: "Yuan",
Age:26,
}
fmt.Println("修改前:", u)
SetValue(&u)
fmt.Println("修改后:", u)
}
3. 调用结构体绑定的方法
package mainimport (
"fmt"
"reflect"
)//定义结构体
type User struct {
Id int
Name string
Age int
}//绑定方法
func (u User) Say(s string) {
fmt.Println("Hello", s)
}func (u User) Eat() {
fmt.Println("Eat price.")
}func CallFunc(i interface{}){
v := reflect.ValueOf(i)
//获取结构体绑定的方法
sayFunc := v.MethodByName("Say")
//构建调用参数
args := []reflect.Value{reflect.ValueOf("world!")}
//调用方法
sayFunc.Call(args)//调用无参方法
eatFunc := v.MethodByName("Eat")
args = []reflect.Value{}
eatFunc.Call(args) //这里必须传值,不能为空
}func main() {u := User{
Id:1,
Name: "Yuan",
Age:26,
}
CallFunc(&u)
}
反射的牛刀小试 在Kubernetes中的资源配置文件的主要形式是yaml格式的,同时Kubernetes还支持json格式和proto格式的配置文件,下面我们自己可以实现一个简单的ini格式配置文件的解析,使用案例如下:
package mainfunc main() {// 制作测试数据
var conf Config
conf.ServerConf.IP = "192.168.0.1"
conf.ServerConf.Port = 8080
conf.ClientConf.Username = "Yuan"
conf.ClientConf.Password = "Abcd123456"//将结构体信息写入配置文件
StructToFile("./config.ini", conf)var config Config
// 从配置文件解析到结构体中
FileToStruct("./config.ini", &config)
logger.Printf("%#v", config)
}
执行上面的代码会生成
config.ini
配置文件并输出从配置文件解析的结构体信息到控制台其中生成的config.in文件内容如下:
[SERVER]
ip = 192.168.0.1
port = 8080[CLIENT]
username = Yuan
password = Abcd123456
控制台输出为:
main.go:18: main.Config{ServerConf:main.ServerConfig{IP:"192.168.0.1", Port:8080}, ClientConf:main.ClientConfig{Username:"Yuan", Password:"Abcd123456"}}
【反射(reflect)机制】项目源码地址
推荐阅读
- 深入理解|深入理解 Android 9.0 Crash 机制(二)
- 316日课总结--反射效应---
- java|java Field类的使用
- 轻量模块注意力机制ECA-Net(注意力模块+一维卷积)
- Java中的反射
- k8s|k8s(六)(配置管理与集群安全机制)
- 反射
- 垃圾回收机制(第十二天)
- Unity中使用反射机制调用函数
- Kotlin反射