2020年12月20日
阅读: 2219
Go 语言基础之反射(十三)
友情提醒:本文最后更新于 1462 天前,文中所描述的信息可能已发生改变,请谨慎使用。
一. 反射介绍
反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
Go程序在运行期使用reflect
包访问程序的反射信息。
在上一篇博客中我们介绍了空接口。 空接口可以存储任意类型的变量,那我们如何知道这个空接口保存的数据是什么呢? 反射就是在运行时动态的获取一个变量的类型信息和值信息。
二. 变量的内在机制
- 变量包含类型信息和值信息
var arr [10]int arr[0] = 10
- 类型信息:是静态的元信息,是预先定义好的
- 值信息:是程序运行过程中动态改变的
三. 反射的使用
- reflect包封装了反射相关的方法
- 获取类型信息:reflect.TypeOf,是静态的
- 获取值信息:reflect.ValueOf,是动态的
四. 空接口与反射
- 反射可以在运行时动态获取程序的各种详细信息
- 反射获取interface类型信息
package main
import (
"fmt"
"reflect"
)
//反射获取interface类型信息
func reflect_type(a interface{}) {
t := reflect.TypeOf(a)
fmt.Println("类型是:", t)
// kind()可以获取具体类型
k := t.Kind()
fmt.Println(k)
switch k {
case reflect.Float64:
fmt.Printf("a is float64\n")
case reflect.String:
fmt.Println("string")
}
}
func main() {
var x float64 = 3.4
reflect_type(x)
}
- 反射获取interface值信息
package main
import (
"fmt"
"reflect"
)
//反射获取interface值信息
func reflect_value(a interface{}) {
v := reflect.ValueOf(a)
fmt.Println(v)
k := v.Kind()
fmt.Println(k)
switch k {
case reflect.Float64:
fmt.Println("a是:", v.Float())
}
}
func main() {
var x float64 = 3.4
reflect_value(x)
}
- 反射修改值信息
package main
import (
"fmt"
"reflect"
)
//反射修改值
func reflect_set_value(a interface{}) {
v := reflect.ValueOf(a)
k := v.Kind()
switch k {
case reflect.Float64:
// 反射修改值
v.SetFloat(6.9)
fmt.Println("a is ", v.Float())
case reflect.Ptr:
// Elem()获取地址指向的值
v.Elem().SetFloat(7.9)
fmt.Println("case:", v.Elem().Float())
// 地址
fmt.Println(v.Pointer())
}
}
func main() {
var x float64 = 3.4
// 反射认为下面是指针类型,不是float类型
reflect_set_value(&x)
fmt.Println("main:", x)
}
五. 结构体与反射
查看类型、字段和方法
package main
import (
"fmt"
"reflect"
)
// 定义结构体
type User struct {
Id int
Name string
Age int
}
// 绑方法
func (u User) Hello() {
fmt.Println("Hello")
}
// 传入interface{}
func Poni(o interface{}) {
t := reflect.TypeOf(o)
fmt.Println("类型:", t)
fmt.Println("字符串类型:", t.Name())
// 获取值
v := reflect.ValueOf(o)
fmt.Println(v)
// 可以获取所有属性
// 获取结构体字段个数:t.NumField()
for i := 0; i < t.NumField(); i++ {
// 取每个字段
f := t.Field(i)
fmt.Printf("%s : %v", f.Name, f.Type)
// 获取字段的值信息
// Interface():获取字段对应的值
val := v.Field(i).Interface()
fmt.Println("val :", val)
}
fmt.Println("=================方法====================")
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Println(m.Name)
fmt.Println(m.Type)
}
}
func main() {
u := User{1, "zs", 20}
Poni(u)
}
查看匿名字段
package main
import (
"fmt"
"reflect"
)
// 定义结构体
type User struct {
Id int
Name string
Age int
}
// 匿名字段
type Boy struct {
User
Addr string
}
func main() {
m := Boy{User{1, "zs", 20}, "bj"}
t := reflect.TypeOf(m)
fmt.Println(t)
// Anonymous:匿名
fmt.Printf("%#v\n", t.Field(0))
// 值信息
fmt.Printf("%#v\n", reflect.ValueOf(m).Field(0))
}
修改结构体的值
package main
import (
"fmt"
"reflect"
)
// 定义结构体
type User struct {
Id int
Name string
Age int
}
// 修改结构体值
func SetValue(o interface{}) {
v := reflect.ValueOf(o)
// 获取指针指向的元素
v = v.Elem()
// 取字段
f := v.FieldByName("Name")
if f.Kind() == reflect.String {
f.SetString("kuteng")
}
}
func main() {
u := User{1, "5lmh.com", 20}
SetValue(&u)
fmt.Println(u)
}
调用方法
package main
import (
"fmt"
"reflect"
)
// 定义结构体
type User struct {
Id int
Name string
Age int
}
func (u User) Hello(name string) {
fmt.Println("Hello:", name)
}
func main() {
u := User{1, "5lmh.com", 20}
v := reflect.ValueOf(u)
// 获取方法
m := v.MethodByName("Hello")
// 构建一些参数
args := []reflect.Value{reflect.ValueOf("6666")}
// 没参数的情况下:var args2 []reflect.Value
// 调用方法,需要传入方法的参数
m.Call(args)
}
获取字段的tag
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string `json:"name1" db:"name2"`
}
func main() {
var s Student
v := reflect.ValueOf(&s)
// 类型
t := v.Type()
// 获取字段
f := t.Elem().Field(0)
fmt.Println(f.Tag.Get("json"))
fmt.Println(f.Tag.Get("db"))
}
六. 反射是把双刃剑
反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。
- 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。
- 大量使用反射的代码通常难以理解。
- 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。