各个编程语言的反射是相通的,理解Java反射也能理解Go反射,不得不说Java的生态太好了,啥都能找到回答。
一、什么是反射?
维基百科:在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
计算机编程语言一般分为动态语言和静态语言,像PHP、JavaScript等为动态语言,像Java、GoLang等为静态语言。
• 动态语言,可以直接运行,在运行时(Run time),对代码边进行逐行翻译为“机器语言”,边送往CPU执行。
• 静态语言,不能直接运行,需要编译,在编译时(Compile time),将代码全部翻译为“机器语言”,在运行时(Run time),直接CPU执行。
这就是为什么动态语言开发简单,静态语言执行效率高。
映射和反射
映射:指存在两种事物,之间存在对应关系。
反射:指一种事物,通过反射获取和改变该事物。
二、为什么需要反射?
再说反射,定义中提到“反射就是程序在运行的时候能够“观察”并且修改自己的行为”,在动态语言本身就可以随时修改代码,随时运行,用不着反射。但静态语言不行,一旦修改代码,就需要重新编译,因此需要“反射”这一功能,可以让静态语言在编译后也可以“动态”的修改自己。
三、GoLang反射
Go语言提供了一种机制,能够在运行时检查其变量和值并找到其类型的能力。
本节思路参考,写的非常好——实战演示Go反射的使用方法和应用场景
1.静态程序
该程序为打印一个插入sql语句,可以发现在执行createQuery()时,参数结构体的类型是已知的,则在函数内部拼接的sql语句也固定死了。若这时有另一个结构体也需要插入sql语句相同的功能,那么就只能重写一个createQuery()。
package main
import "fmt"
func main() {
o := order{
orderId: 21,
customerId: 200,
}
fmt.Println(createQuery(o))
}
type order struct {
orderId int
customerId int
}
func createQuery(o order) string {
i := fmt.Sprintf("INSERT INTO order VALUES(%d, %d)", o.orderId, o.customerId)
return i
}
运行结果:
INSERT INTO order VALUES(21, 200)
2.动态程序-反射
reflect标准库
Go语言的标准库,reflect包实现了在运行时进行反射的功能,可以识别一个interface{}类型其底层具体的类型和值。
该包有两个重要的接口,分别为reflect.Type、reflect.Value。其对应的重要方法为以下:
• func TypeOf(i interface{}) Type :获取类型
package main
import (
"fmt"
"reflect"
)
func main() {
o := order{
ordId: 21,
customerId: 200,
}
fmt.Println("Type:", reflect.TypeOf(o))
}
type order struct {
ordId int
customerId int
}
运行结果:
Type: main.order
• func ValueOf(i interface{}) Value :获取类型值
package main
import (
"fmt"
"reflect"
)
func main() {
o := order{
ordId: 21,
customerId: 200,
}
fmt.Println("Type:", reflect.ValueOf(o))
}
type order struct {
ordId int
customerId int
}
运行结果:
Type: {21 200}
还有一个重要的接口Kind,可以用来获取官方特定的类型。
package main
import (
"fmt"
"reflect"
)
func main() {
o := order{
ordId: 21,
customerId: 200,
}
v := reflect.ValueOf(o)
fmt.Println("kind:", v.Kind())
}
type order struct {
ordId int
customerId int
}
运行结果:
kind: struct
改造静态程序
当createQuery()的参数是未知的,就需要适应任何结构体,则需要一个interface{}类型的参数,这时函数内部则迫切的需要知道传入来的参数是什么类型,以便拼接sql语句,这时就需要反射了。
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
type employee struct {
name string
id int
address string
salary int
country string
}
func createQuery(q interface{}) string {
t := reflect.TypeOf(q)
v := reflect.ValueOf(q)
if v.Kind() != reflect.Struct {
panic("unsupported argument type!")
}
tableName := t.Name() // 通过结构体类型提取出SQL的表名
sql := fmt.Sprintf("INSERT INTO %s ", tableName)
columns := "("
values := "VALUES ("
for i := 0; i < v.NumField(); i++ {
// 注意reflect.Value 也实现了NumField,Kind这些方法
// 这里的v.Field(i).Kind()等价于t.Field(i).Type.Kind()
switch v.Field(i).Kind() {
case reflect.Int:
if i == 0 {
columns += fmt.Sprintf("%s", t.Field(i).Name)
values += fmt.Sprintf("%d", v.Field(i).Int())
} else {
columns += fmt.Sprintf(", %s", t.Field(i).Name)
values += fmt.Sprintf(", %d", v.Field(i).Int())
}
case reflect.String:
if i == 0 {
columns += fmt.Sprintf("%s", t.Field(i).Name)
values += fmt.Sprintf("'%s'", v.Field(i).String())
} else {
columns += fmt.Sprintf(", %s", t.Field(i).Name)
values += fmt.Sprintf(", '%s'", v.Field(i).String())
}
}
}
columns += "); "
values += "); "
sql += columns + values
fmt.Println(sql)
return sql
}
func main() {
o := order{
ordId: 21,
customerId: 200,
}
createQuery(o)
e := employee{
name: "Naveen",
id: 565,
address: "Coimbatore",
salary: 90000,
country: "India",
}
createQuery(e)
}
运行结果:
INSERT INTO order (ordId, customerId); VALUES (21, 200);
INSERT INTO employee (name, id, address, salary, country); VALUES ('Naveen', 565, 'Coimbatore', 90000, 'India');
四、GoLang反射应用的标准包举例
• fmt包-提供的字符串格式化功能
• encoding/json包-json数据协议编解码功能
但并不是所有的静态语言的应用都具备反射,例如像Flutter,虽然语言是Dart静态语言,但是Flutter禁止使用反射,因此没办法像GoLang中json库一样,直接将json反射为对象模型,非常苦逼。
五、反射的忠告
关于反射的几点忠告。——《Go语言圣经》
• 脆弱性:基于反射的代码是比较脆弱的。
• 安全性:反射的操作不能做静态类型检查,而且大量反射的代码通常难以理解。
• 低性能:基于反射的代码通常比正常的代码运行速度慢一到两个数量级。