在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用。
Golang的gRPC也是通过反射实现的。
Golang关于类型设计的一些原则
变量包括(type, value)两部分
type 包括 static type
和concrete type
,简单来说 static type
是你在编码是看见的类型(如int、string),concrete type
是runtime系统看见的类型
类型断言能否成功,取决于变量的concrete type
,而不是static type
. 因此,一个 reader变量如果它的concrete type
也实现了write方法的话,它也可以被类型断言为writer.
反射就是找到当前interface
变量的concrete type
具体类型,也可以得到当前变量的值
反射,就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type
),在创建变量的时候就已经确定,反射主要与Golang的interface
类型相关(它的type是concrete type
),只有interface
类型才有反射一说。
在Golang的实现中,每个interface
变量都有一个对应pair,pair中记录了实际变量的值和类型:(value, type)
interface{}
类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type
】,另外一个指针指向实际的值【对应value】。实例1:
package pair_go
import "fmt"
func Pair() {
var str string
// pair
str = "lisi"
var allType interface{}
// str的pair传递给allType了
// pair
allType = str
value, ok := allType.(string)
if ok {
fmt.Println(value)
}
}
pair
,将str赋值给interface{}
类型的变量allType,在赋值的同时,pair也会传递,因此allType的pair为pair
实例2:
package pair_go
import (
"fmt"
"io"
"os"
)
func Pair() {
//以读写状态打开当前Linux终端
//tty pair
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
fmt.Println("open file error", err)
return
}
// r pair
var r io.Reader
// r pair
r = tty
// w pair
var w io.Writer
// w pair
w = r.(io.Writer)
w.Write([]byte("hello, this is a test!!\n"))
}
tty pair
io.Reader
类型是interface
类型的,包含一个接口,将一个文件读到p指向的内存中,给r赋值tty后,此时r变量的pair是pair
io.Writer
类型与io.Reader
类似,也是interface
类型,将r强转为io.Writer
类型并赋值给w,此时w的pair为pair
实例3:
package pair_go
import (
"fmt"
)
type Reader interface {
ReadBook()
}
type Writer interface {
WriteBook()
}
// 具体的类型
type Book struct {
}
func (b *Book) ReadBook() {
fmt.Println("Read a book")
}
func (b *Book) WriteBook() {
fmt.Println("Write a book")
}
func Pair() {
// b pair
b := &Book{}
// r pair
var r Reader
// r pair
r = b
r.ReadBook()
// r pair
var w Writer
// r pair
w = r.(Writer) // 此处的断言为什么会成功,因为w r具体的type是一致的
//因为Book重写了Reader和Writer,因此Reader和Writer都能指向Book对象,因此他们两个之间是能够互相断言的
w.WriteBook()
}
r.(Writer)
断言,是因为w和r具体的type是一致的,Book重写了Reader和Writer,因此Reader和Writer都能指向Book对象,因此他们两个之间是能够互相断言的既然反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。那么在Golang的reflect反射包中有什么样的方式可以让我们直接获取到变量内部的信息呢? 它提供了两种类型(或者说两个方法)让我们可以很容易的访问接口变量内容,分别是reflect.ValueOf()
和 reflect.TypeOf()
,看看官方的解释
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero
func ValueOf(i interface{}) Value {...}
//ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {...}
//TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil
reflect.TypeOf()
是获取pair中的type,reflect.ValueOf()
获取pair中的value实例1:
package reflect_go
import (
"fmt"
"reflect"
)
func reflectNum(arg interface{}) {
fmt.Println("type: ", reflect.TypeOf(arg))
fmt.Println("value: ", reflect.ValueOf(arg))
}
func Reflect() {
var num1 float64 = 1.2345
reflectNum(num1)
}
reflect.ValueOf(interface)
之后,就得到了一个类型为relfect.Value
变量,可以通过它本身的Interface()
方法获得接口变量的真实内容,然后可以通过类型判断进行转换,转换为原有真实类型。不过,我们可能是已知原有类型,也有可能是未知原有类型,因此,下面分两种情况进行说明。realValue := value.Interface().(已知的类型)
实例1
package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 1.2345
pointer := reflect.ValueOf(&num)
value := reflect.ValueOf(num)
// 可以理解为“强制转换”,但是需要注意的时候,转换的时候,如果转换的类型不完全符合,则直接panic
// Golang 对类型要求非常严格,类型一定要完全符合
// 如下两个,一个是*float64,一个是float64,如果弄混,则会panic
convertPointer := pointer.Interface().(*float64) //返回的是*float64类型的变量
convertValue := value.Interface().(float64) // 返回的是float64类型的变量
convertInterface := value.Interface() // 返回的是Interface()类型的变量
fmt.Printf("%T\n", convertPointer)
fmt.Println(convertPointer)
fmt.Printf("%T\n", convertValue)
fmt.Println(convertValue)
val, ok := convertInterface.(float64) //interface类型的变量可以断言
if ok {
fmt.Printf("convertInterface is %T type\n", val)
}
}
relfect.Value
变量,可以通过value.Interface().(已知的类型)
方法进行强制类型转换value.Interface().(已知的类型)
返回的值类型是具体的类型,value.Interface()
返回的是interface类型value.Interface().(已知的类型)
转换的时候,如果转换的类型不完全符合,则直接panic,类型要求非常严格!type.NumField()
可以获取结构体字段的数量,type.Field(i)
可以获取第i个字段,进而获取字段的类型和字段名value.Field(i).Interface()
可以获取第i个字段的值type.NumMethod()
可以获取成员方法的数量,type.Method(i)
获取第i个成员方法,进而获取方法的类型和方法名实例2:
package reflect_go
import (
"fmt"
"reflect"
)
type User struct {
Name string
Sex string
Age int
}
func (usr User) Call() {
fmt.Println("user os called..")
fmt.Println(usr)
}
func Reflect() {
User1 := User{"zhangsan", "male", 16}
DoFiledAndMethod(User1)
}
func DoFiledAndMethod(input interface{}) {
//获取type
inputType := reflect.TypeOf(input)
fmt.Println("input type: ", inputType)
//获取value
inputValue := reflect.ValueOf(input)
fmt.Println("input value: ", inputValue)
//通过type获取里面的字段
//1.获取interface的reflect.Type,通过Type得到NumField,进行遍历
//2.得到每个field,数据类型
//3.通过field中有一个Interface()方法,得到对应的value
for i := 0; i < inputType.NumField(); i++ {
field := inputType.Field(i) //取出第i个字段
value := inputValue.Field(i).Interface() //取出第i个字段的值
fmt.Println(field.Name, field.Type, value)
}
//通过type获取里面的方法
for i := 0; i < inputType.NumMethod(); i++ {
m := inputType.Method(i)
fmt.Println(m.Name, m.Type)
}
}
reflect.Value
是通过reflect.ValueOf(X)
获得的,只有当X是指针的时候,才可以通过reflec.Value
修改实际变量X的值,即:要修改反射类型的对象就一定要保证其值是“addressable”的。实例:
package reflect_go
import (
"fmt"
"reflect"
)
type User struct {
Name string
Sex string
Age int
}
func (usr User) Call() {
fmt.Println("user os called..")
fmt.Println(usr)
}
func Reflect() {
User1 := User{"zhangsan", "male", 16}
ChangeValue(User1)
}
func ChangeValue(input interface{}) {
// 通过reflect.ValueOf获取num中的reflect.Value,注意,参数必须是指针才能修改其值
pointer := reflect.ValueOf(&input)
//Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。
//如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。
newValue := pointer.Elem()
fmt.Println("pointer: type of pointer: ", pointer.Type())
fmt.Println("pointer: settability of pointer: ", pointer.CanSet())
fmt.Println("newValue: type of pointer: ", newValue.Type())
fmt.Println("newValue: settability of pointer: ", newValue.CanSet())
//重新赋值
u := User{"lisi", "male", 25}
val := reflect.ValueOf(u)
newValue.Set(val)
fmt.Println("new value: ", input)
}
interface{}
通用接口接收User
类型的数据,pointer被赋值为reflect.ValueOf(&input)
,也就是input的指针的Value,只有通过reflect拿到其指针,才能够修改数据reflect.Value.Elem()
表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的,会返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装,如果v的类型不是interface
或者指针,则pointer.Elem()
会panic,我们通过Elem()
拿到了pointer指针指向的Value,可以通过该Value更改原数据的值newValue.CantSet()
表示是否可以重新设置其值,如果输出的是true则可修改,否则不能修改Type()
和CanSet()
,可以看出:pointer的类型是*interface {}
,其值是不可以更改的;newValue的类型是interface {}
,其值是可以更改的;Set()
方法更改实际变量的值,注意Set()
参数的类型需要是reflect.Value
,因此还需要将新的值通过reflect.ValueOf()
提取Value,再传入Set()
;如果实际变量是一般类型,可以通过SetInt()
、SetFloat()
方法直接更改这算是一个高级用法了,前面我们只说到对类型、变量的几种反射的用法,包括如何获取其值、其类型、如果重新设置新值。但是在工程应用中,另外一个常用并且属于高级的用法,就是通过reflect来进行方法【函数】的调用。比如我们要做框架工程的时候,需要可以随意扩展方法,或者说用户可以自定义方法,那么我们通过什么手段来扩展让用户能够自定义呢?关键点在于用户的自定义方法是未可知的,因此我们可以通过reflect来搞定
reflect.ValueOf(interface)
来获取到reflect.Value
,得到“反射类型对象”后才能做下一步处理reflect.Value.MethodByName
:需要指定准确真实的方法名字,如果错误将直接panic,MethodByName返回一个函数值对应的reflect.Value
方法的名字。[]reflect.Value
,这个是最终需要调用的方法的参数,可以没有或者一个或者多个,根据实际参数来定。reflect.Value.Call()
这个方法,将最终调用真实的方法,参数务必保持一致,如果reflect.Value
的类型不是一个方法,那么将直接panic。实例1:
package reflect_go
import (
"fmt"
"reflect"
)
type User struct {
Name string
Sex string
Age int
}
func (usr User) ReflectCallFuncHasArgs(name string, age int) {
fmt.Println("ReflectCallFuncHasArgs name:", name, "age", age)
}
func (usr User) ReflectCallFuncNoArgs() {
fmt.Println("ReflectCallFuncNoArgs")
}
func Reflect() {
User1 := User{"zhangsan", "male", 16}
callFunc(User1)
}
func callFunc(input interface{}) {
//获取Value对象
getValue := reflect.ValueOf(input)
//有参调用
//获取方法,一定要指定参数为正确的方法名
method1 := getValue.MethodByName("ReflectCallFuncHasArgs")
//参数列表
args := []reflect.Value{reflect.ValueOf("xiaozhang"), reflect.ValueOf(18)}
//调用
method1.Call(args)
//无参调用
method2 := getValue.MethodByName("ReflectCallFuncNoArgs")
//无参,也要传入空的args
args = make([]reflect.Value, 0)
//或者 args2 = []reflect.Value{}
method2.Call(args)
}
[]reflect.Value{}
类型的,需要传入该函数所需参数的reflect.Value
值,若函数无参,则args为空即可;通过reflect.Type可以获取方法
package reflect_go
import (
"fmt"
"reflect"
)
type User struct {
Name string
Sex string
Age int
}
func (usr User) ReflectCallFuncHasArgs(name string, age int) {
fmt.Println("ReflectCallFuncHasArgs name:", name, "age", age)
}
func (usr User) ReflectCallFuncNoArgs() {
fmt.Println("ReflectCallFuncNoArgs")
}
func Reflect() {
User1 := User{"zhangsan", "male", 16}
callFunc(User1)
}
func callFunc(input interface{}) {
//获取Value对象
getType := reflect.TypeOf(input)
//获取方法
for i := 0; i < getType.NumMethod(); i++ {
method := getType.Method(i)
fmt.Println("name:", method.Name, "type:", method.Type)
}
}
Golang的反射很慢,这个和它的API设计有关。在 java 里面,我们一般使用反射都是这样来弄的。
Field field = clazz.getField("hello");
field.get(obj1);
field.get(obj2);
这个取得的反射对象类型是 java.lang.reflect.Field。它是可以复用的。只要传入不同的obj,就可以取得这个obj上对应的 field。
但是Golang的反射不是这样设计的:
type_ := reflect.TypeOf(obj)
field, _ := type_.FieldByName("hello")
这里取出来的 field 对象是 reflect.StructField 类型,但是它没有办法用来取得对应对象上的值。如果要取值,得用另外一套对object,而不是type的反射
type_ := reflect.ValueOf(obj)
fieldValue := type_.FieldByName("hello")
这里取出来的 fieldValue 类型是 reflect.Value,它是一个具体的值,而不是一个可复用的反射对象了,每次反射都需要malloc这个reflect.Value结构体,并且还涉及到GC。
Golang reflect慢主要有两个原因
上述详细说明了Golang的反射reflect的各种功能和用法,都附带有相应的示例,相信能够在工程应用中进行相应实践,总结一下就是:
TypeOf
和 ValueOf
函数从接口中获取目标对象信息reflect.value.Interface().(已知的类型)
reflect.Type
的Field获取其Fieldinterface.data
是 settable,即 pointer-interface结构体标签需要通过反射来实现;
type Resume struct {
Name string `info:"name" doc:"我的名字"`
Sex string `info:"sex"`
}
reflect.TypeOf(input).Elem()
来得到结构体的所有元素(注意:input的类型需要是interface{}
或者指针)type.Field(i).Tag.Get(key)
来获取标签的key对应的valuepackage struct_tag
import (
"fmt"
"reflect"
)
// Golang允许向结构体字段后面绑定标签,使用kv格式
// 主要作用是其他的包再倒入当前结构体属性的时候,来判断该属性对其他包的作用,起到说明的作用
type Resume struct {
Name string `info:"name" doc:"我的名字"`
Sex string `info:"sex"`
}
func findTag(input interface{}) {
//通过reflect来得到标签
t := reflect.TypeOf(input).Elem() // 得到当前结构体的全部元素
for i := 0; i < t.NumField(); i++ {
tagInfo := t.Field(i).Tag.Get("info") // 通过标签的key找到标签的value
tagDoc := t.Field(i).Tag.Get("doc")
fmt.Println("info:", tagInfo, "doc", tagDoc)
}
}
func StructTag() {
var re Resume
findTag(&re)
}
encoding/json
库中的方法将结构体对象转换为json文件格式json.Marshal(movie1)
方法会将带json标签的结构体对象转换为json格式的字符串,格式为标签value:字段的值
json.Unmarshal(jsonStr, &my_movie)
方法会将json格式的字符串转换为相应的结构体类型,注意结构体需要传入的是对象的地址package struct_tag
import (
"encoding/json"
"fmt"
"reflect"
)
// 可以使用encoding/json将数据转换为json文件格式
// 如果需要转换为json文件,则需要给结构体字段打上json标签
type Movie struct {
Title string `json:"title"`
Year int `json:"year"`
Price int `json:"price"`
Actors []string `json:"actors"`
}
func ConvertToJson() {
movie1 := Movie{"让子弹飞", 2009, 50, []string{"姜文,葛优,周润发"}}
//编码的过程 struct --> json
jsonStr, err := json.Marshal(movie1)
if err != nil {
fmt.Println("json marshal error", err)
return
}
fmt.Printf("jsonStr = %s\n", jsonStr)
//解码的过程 jsonStr --> struct
my_movie := Movie{}
err = json.Unmarshal(jsonStr, &my_movie)
if err != nil {
fmt.Println("json unmarshal error", err)
return
}
fmt.Println(my_movie)
}