GoLang补充提升(五)
1.反射
TypeOf
(1)基本反射
func main() {
var a int64 = 100
var b float64 = 3.1415926
reflectType(a)
reflectType(b)
}
func reflectType(a any) {
t := reflect.TypeOf(a)
fmt.Printf("type:%v\n", t)
}
(2)type name和type kind
在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)
- Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空。
type myInt int64
func main() {
var a *float64
var b myInt
var c rune
reflectType(a) //Name:,Kind:ptr
reflectType(b) //Name:myInt,Kind:int64
reflectType(c) //Name:int32,Kind:int32
type person struct {
name string
age int
}
p := person{
name: "zzs",
age: 18,
}
reflectType(p) //Name:person,Kind:struct
}
func reflectType(a any) {
t := reflect.TypeOf(a)
fmt.Printf("Name:%v,Kind:%v\n", t.Name(), t.Kind())
}
ValueOf
(1)通过反射获取值
func main() {
var a int64 = 100
var b float64 = 3.14
reflectValue(a)
reflectValue(b)
}
func reflectValue(a any) {
v := reflect.ValueOf(a)
k := v.Kind()
switch k {
case reflect.Int64:
fmt.Println("Int64:", v.Int())
case reflect.Float64:
fmt.Println("Float64:", v.Float())
}
}
(2)通过反射设置值
- 想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中使用专有的Elem()方法来获取指针对应的值。
func main() {
var a float64 = 3.14
reflectSetValue2(&a)
fmt.Println(a)
}
func reflectSetValue1(a any) {
v := reflect.ValueOf(a)
if v.Kind() == reflect.Float64 {
v.SetFloat(9.9999) //不能修改值,因为函数调用是值的拷贝,只有传递指针时才能修改对应的值
}
}
func reflectSetValue2(a any) {
v := reflect.ValueOf(a)
if v.Elem().Kind() == reflect.Float64 {
v.Elem().SetFloat(9.9999)
}
}
(3)通过反射获取结构体标签和字段
type student struct {
name string `json:"Name" zzs:"emmm"`
age int `json:"Age" zzs:"hahaha"`
}
func main() {
s := student{
name: "zzs",
age: 18,
}
t := reflect.TypeOf(s)
//遍历所有字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Println(field.Name, field.Type, field.Index, field.Tag.Get("zzs"))
}
//通过名称获取字段
if ageField, ok := t.FieldByName("age"); ok {
fmt.Println(ageField.Name, ageField.Type, ageField.Index, ageField.Tag.Get("zzs"))
}
}
(4)通过反射获取调用结构体方法
type myStudent struct {
name string
age int
}
func (s *myStudent) Study(str string) {
fmt.Println(str, "学习")
}
func (s *myStudent) Sleep(str string) {
fmt.Println(str, "睡觉")
}
func main() {
s := myStudent{
name: "zzs",
age: 18,
}
t := reflect.TypeOf(&s) //要是值接收者就传递值,要是指针接收者就传递指针
v := reflect.ValueOf(&s) //要是值接收者就传递值,要是指针接收者就传递指针
fmt.Println(t.NumMethod())
for i := 0; i < t.NumMethod(); i++ {
fmt.Println(t.Method(i).Name)
fmt.Println(t.Method(i).Type)
a := []reflect.Value{reflect.ValueOf("zzs")}
v.Method(i).Call(a)
}
}
2.反射实现ini配置文件解析
- ini配置文件
# mysqlConfig
[mysql]
address=10.20.30.40
port=3306
username=root
password=root1
; redisConfig
[redis]
host=11.22.33.44
port=4367
password=root123
database=666
解析实现
- 先定义相关的结构体对象,添加标签方便反射进行使用
- 读取ini文件信息,对文件进行解析
- 通过反射对值进行设置
type mySqlConfig struct {
Address string `ini:"address"`
Port int `ini:"port"`
Password string `ini:"password"`
UserName string `ini:"username"`
}
type redisConfig struct {
Host string `ini:"host"`
Port int `ini:"port"`
DataBase int `ini:"database"`
Password string `ini:"password"`
}
type myConfig struct {
MysqlCfg mySqlConfig `ini:"mysql"`
RedisCfg redisConfig `ini:"redis"`
}
func main() {
var config myConfig
err := loadIni("34/conf.ini", &config)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(config)
}
func loadIni(fileName string, data any) error {
t := reflect.TypeOf(data)
if t.Kind() != reflect.Ptr {
return errors.New("error Data Must Prt")
}
if t.Elem().Kind() != reflect.Struct {
return errors.New("error Data Must Struct")
}
buffer, err := ioutil.ReadFile(fileName)
if err != nil {
fmt.Println(err)
return err
}
str := string(buffer)
arr := strings.Split(str, "\r\n")
setInfo(arr, data)
return nil
}
func setInfo(arr []string, data any) error {
t := reflect.TypeOf(data)
v := reflect.ValueOf(data)
var val reflect.Value
for _, lineStr := range arr {
if lineStr == "" || strings.HasPrefix(lineStr, "#") || strings.HasPrefix(lineStr, ";") {
continue
}
if strings.HasPrefix(lineStr, "[") {
//获取config名称
configName := lineStr[1:strings.LastIndex(lineStr, "]")]
for i := 0; i < t.Elem().NumField(); i++ {
field := t.Elem().Field(i)
if field.Tag.Get("ini") == configName {
val = v.Elem().Field(i)
break
}
}
continue
}
item := strings.Split(lineStr, "=")
if len(item) != 2 {
return errors.New("error SetInfo Item Len Error")
}
for i := 0; i < val.NumField(); i++ {
if val.Type().Field(i).Tag.Get("ini") == item[0] {
if val.Field(i).Kind() == reflect.Int {
num, err := strconv.Atoi(item[1])
if err != nil {
fmt.Println(err)
break
}
val.Field(i).SetInt(int64(num))
} else {
val.Field(i).SetString(item[1])
}
break
}
}
}
return nil
}
3.反射说明
反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。
- 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。
- 大量使用反射的代码通常难以理解。
- 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。