GoLang:反射详解

各个编程语言的反射是相通的,理解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语言圣经》

• 脆弱性:基于反射的代码是比较脆弱的。

• 安全性:反射的操作不能做静态类型检查,而且大量反射的代码通常难以理解。

• 低性能:基于反射的代码通常比正常的代码运行速度慢一到两个数量级。

除非注明,否则均为呆小猴博客原创文章,转载必须以链接形式标明本文链接!付费资源为虚拟物品,一经出售,概不退款!
呆小猴 » GoLang:反射详解

发表回复

呆小猴 · 专注安全学习与分享

关于作者 联系作者