Go语言提供了一种机制在运行时更新和检查变量的值或者调用它们的方法,但是编译时并不知道这些变量的具体类型,这就是反射机制。
什么场景需要反射?
比如要写一个函数,但是不知道传入的参数类型是什么,可能是没有约定好,也可能传入的类型很多,这些类型并不能统一表示。或者需要根据某些条件决定调用哪个函数。
反射的缺点是什么?
Go语言是一门静态语言,编译过程中会提前发现一些错误,但是对反射无能为力,因为反射是在运行时检查,所以可能运行很久才出错,可能造成严重的后果。另外反射对性能的影响比较大,比正常代码运行速度慢上一到两个量级。对于处于运行效率关键的代码,应尽量避免使用反射特性。还有就是包括反射的代码可读性太差。
go语言为反射机制提供了两个方法,可以让我们很容易的访问到接口变量内容。reflect.ValueOf()和reflect.TypeOf()。ValueOf用来获取输入参数接口中中数据的值,如果接口为空返回0,TypeOf用来动态的获取输入参数接口中的值的类型,如果接口为空返回nil。
例1:
package main
import (
"fmt"
"reflect"
_"runtime"
)
func main() {
//反射操作:通过反射,可以获取一个接口类型变量的 类型和数值
var i int = 101
fmt.Println("type:", reflect.TypeOf(i))
fmt.Println("value:", reflect.ValueOf(i))
//通过反射的Kind方法知道变量类型
v := reflect.ValueOf(i)
if v.Kind() == reflect.Int {
fmt.Println("Kind is int")
}
fmt.Println("type:", v.Type())
fmt.Println("vale:", v.Int())
fmt.Println("vale:", v.Interface())
//fmt.Println("version:", runtime.Version())
//通过反射修改变量的值,需传入变量指针
p := reflect.ValueOf(&i)
newVal := p.Elem()
fmt.Println("Elem type:", newVal.Type())
if newVal.CanSet() {
newVal.SetInt(1001)
}
fmt.Println("i value:", i)
}
结果:
例2:
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string //注意如果通过反射改名必须首字母大写
Age int
sex string
addr string
}
//如果想通过反射调用和取得NumMethod 首字母大写
func (p Person) GetPersonMsg() (msg string) {
msg = fmt.Sprintf("Name:%s Age:%d sex:%s addr:%s", p.Name, p.Age, p.sex, p.addr)
return
}
func (p Person) EatFood(food string) {
fmt.Printf("Person %s eat:%s\n", p.Name, food)
}
func main() {
p := Person{Name: "李卫", Age: 18, sex: "男", addr: "北京"}
fmt.Println(p.GetPersonMsg())
GetMsg(p)
//修改数值
ptr := reflect.ValueOf(&p)
if ptr.Kind() == reflect.Ptr {
newVal := ptr.Elem()
if newVal.CanSet() {
v1 := newVal.FieldByName("Name")
v1.SetString("大卫")
v2 := newVal.FieldByName("Age")
v2.SetInt(20)
}
}
fmt.Println(p.GetPersonMsg())
}
//参数是interface{} 表示可以任意类型参数
func GetMsg(in interface{}) {
t := reflect.TypeOf(in)
fmt.Println("Type :", t.Name()) //Person
fmt.Println("Kind :", t.Kind()) //struct
v := reflect.ValueOf(in)
fmt.Println(v) //{李卫 18 男 北京}
fmt.Println("Type :", v.Type()) //main.Person
fmt.Println("Kind :", v.Kind()) //struct
for i := 0; i < t.NumField(); i++ {
tf := t.Field(i)
vf := v.Field(i)
switch vf.Kind() {
case reflect.Int:
fmt.Printf("字段名称:%s 字段类型:%s 值:%d\n", tf.Name, tf.Type, vf.Int())
case reflect.String:
fmt.Printf("字段名称:%s 字段类型:%s 值:%s\n", tf.Name, tf.Type, vf.String())
default:
fmt.Println("Type not support!!!")
}
}
//方法名打印
fmt.Println(t.NumMethod(), v.NumMethod)
for i := 0; i < t.NumMethod(); i++ {
tm := t.Method(i)
fmt.Printf("方法名:%s 方法类型:%v\n", tm.Name, tm.Type)
}
//方法调用 无参
method1 := v.MethodByName("GetPersonMsg")
fmt.Printf("Kind:%s Type:%v\n", method1.Kind(), method1.Type())
msg := method1.Call(nil)
fmt.Println(msg)
//方法调用 有参数
method2 := v.MethodByName("EatFood")
fmt.Printf("Kind:%s Type:%v\n", method2.Kind(), method2.Type())
args := []reflect.Value{reflect.ValueOf("烤鱼")}
method2.Call(args)
}
结果:
回到最初的问题,写一个函数不知道具体的函数参数,比如函数参数是一个结构体,返回插入这条数据的sql语句。
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string //注意如果通过反射改名必须首字母大写
Age int
Sex string
Addr string
}
type Bird struct {
Name string
Color int
Addr string
}
func main() {
p := Person{Name: "李卫", Age: 18, Sex: "男", Addr: "北京"}
fmt.Println(GetSqlMsg(p))
b := Bird{Name: "燕子", Color: 1, Addr: "北京"}
fmt.Println(GetSqlMsg(b))
}
func GetSqlMsg(in interface{}) string {
t := reflect.TypeOf(in)
v := reflect.ValueOf(in)
if v.Kind() != reflect.Struct {
return "Param error!!"
}
sql := fmt.Sprintf("insert into %s values(", t.Name())
for i := 0; i < t.NumField(); i++ {
vf := v.Field(i)
switch vf.Kind() {
case reflect.Int:
if i == 0 {
sql = fmt.Sprintf("%s%d", sql, v.Field(i).Int())
} else {
sql = fmt.Sprintf("%s, %d", sql, v.Field(i).Int())
}
case reflect.String:
if i == 0 {
sql = fmt.Sprintf("%s\"%s\"", sql, v.Field(i).String())
} else {
sql = fmt.Sprintf("%s, \"%s\"", sql, v.Field(i).String())
}
default:
return "Type error!!!"
}
}
sql = fmt.Sprintf("%s)", sql)
return sql
}
结果: