共计 3919 个字符,预计需要花费 10 分钟才能阅读完成。
什么是反射
官网对此有个十分扼要的介绍,两句话回味无穷:
- 反射提供一种让程序查看本身构造的能力
- 反射是困惑的源泉
要深刻理解反射,个人感觉须要花工夫在官网博客上再加以练习,循序渐进,缓缓领会。
反射的三个定律
反射就是查看 interface 的 (value, type) 对的,因为任何类型的变量或办法都是实现了空接口。具体一点说就是 Go 提供一组办法提取 interface 的 value,提供另一组办法提取 interface 的 type.
官网提供了三条定律来阐明反射,比拟清晰,上面也依照这三定律来总结。
反射包里有两个接口类型要先理解一下.
reflect.Type
提供一组接口解决 interface 的类型,即(value, type)中的 typereflect.Value
提供一组接口解决 interface 的值, 即 (value, type) 中的 value
上面会提到反射对象,所谓反射对象即反射包里提供的两种类型的对象。
reflect.Type
类型对象reflect.Value
类型对象
1. 反射的第一定律:反射能够将 interface 类型变量转换成为 reflect 类型变量
这里的 reflect 类型指的是 reflect.Type 和 reflect.Value 类型的变量
package main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"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 main
func 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"}}
我的项目源码地址
正文完