详解概述
在计算机语言中,编译时就知道变量类型的是静态类型;运行时才知道变量类型的叫做动态类型。比如C++中的多态,其实就是一种运行时类型识别。通过学习Golang,我知道,Go是一种静态类型的编程语言,每个变量都内置一个静态类型,这意味着每个变量的类型在编译时都是确定的。golang,也提供了动态类型,interface。下面我们对golang的静态和动态类型进行简单分析。
变量类型
1.静态类型
静态类型就是我们在对变量进行声明时赋予的类型:
//int64则是静态类型
var number int64
//string则是声明的静态类型
var name string
//*string则是静态类型
var stu *string
2.动态类型
在程序运行时,我们根据具体业务场景对变量(interface{})进行具体类型赋值,这个值类型就是动态类型。一个变量的类型在运行时可以动态改变,则为动态类型(interface{}):
//声明接口类型变量
var Test interface{}
//Test静态类型为interface{},动态类型为int
Test=10
//Test静态类型为interface{},动态类型为string
Test="string"
举个更详细的例子:
func findType(i interface{}){
switch i.(type) {
case string:
fmt.Printf("I am a string and my value is %s\n",i.(string))
case int:
fmt.Printf("I am an int and my value is %d\n",i.(int))
default:
fmt.Printf("Unknown type\n")
}
}
反射的常见应用场景
1)不知道接口调用函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法反射。桥接模式:
func bridge(funcPtr interface{},args ...interface{})
第一个参数funcPtr以接口的形式传入函数指针,函数参数args以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr。
2)对结构体序列化时,如果结构体有指定Tag,也会使用到反射生成对应的字符串。
3)变量、interface{}和reflect.Value是可以相互转换的,这点在实际开发中,会经常使用到。
case1:
package main
import (
"fmt"
"reflect"
)
func main(){
var num float64=888.888
fmt.Println(reflect.TypeOf(num))
fmt.Println(reflect.ValueOf(num))
}
运行结果:
float64
888.888
说明:
1)reflect.TypeOf: 直接给了我们想要的type类型,如float64、int、各种pointer、struct等等真实的类型。
2)reflect.ValueOf:直接给了我们想要的具体的值,如888.888这个具体的值,或者类似&{1 "Allen.Wu" 25} 这样的结构体struct的值。
3)反射可以将接口类型变量转换为反射类型对象,反射类型指的是reflect.Type和reflect.Value这两种。从reflect.Value中获取接口interface的信息。当执行reflect.ValueOf(interface)之后,就得到了一个类型为“reflect.Value”变量,可以通过它本身interface{}方法获得接口变量的真实内容,然后可以通过类型判断进行转换,转换为原有真实类型。
不过,在实际应用中,我们可能是已知原有类型,也可能是未知原有类型。
case2:已知类型转换
package main
import (
"fmt"
"reflect"
)
func main(){
//已知原有类型,进行强制类型转换
var num float64=888.888
pointer:=reflect.ValueOf(&num)
value:=reflect.ValueOf(num)
//进行具体的类型获取,这里需要注意,golang对类型要求比较严格,
// 类型一定要完全符合,如果不符合则panic
convertPointer:=pointer.Interface().(*float64)
convertValue:=value.Interface().(float64)
fmt.Println(convertPointer)
fmt.Println(convertValue)
}
运行结果:
0xc000066080
888.888
说明:
1)转换的时候,如果转换的类型不完全符合,则直接panic,类型要求非常严格!
2)转换的时候,要区分是指针还是值
3)也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”
case3:未知类型
package main
import (
"fmt"
"reflect"
)
type User struct{
Id int
Name string
Age int
}
func(u User) ReflectCallFunc(){
fmt.Println("ReflectCallFunc")
}
func AccquireFieldAndMethod(input interface{}){
getType:=reflect.TypeOf(input)
fmt.Println("Get Type is",getType.Name())
getValue:=reflect.ValueOf(input)
fmt.Println("Get value is",getValue)
//获取方法字段
//1.首先获取interface的reflect.Type,然后通过NumFiled进行遍历
//2.再通过reflect.Type的Field获取其Field
//3.最后通过Field的Interface()得到对应的Value
for i:=0;i<getType.NumField();i++{
field:=getType.Field(i)
value:=getValue.Field(i).Interface()
fmt.Printf("%s: %v = %v\n",field.Name,field.Type,value)
}
//获取方法:
//1.先获取interface的reflect.Type,然后通过.NumMethod进行遍历
for i:=0;i<getType.NumMethod();i++{
m:=getType.Method(i)
fmt.Printf("%s: %v\n",m.Name,m.Type)
}
}
func main(){
user:=User{100,"curl",144}
AccquireFieldAndMethod(user)
}
结果:
Get Type is User
Get value is {100 curl 144}
Id: int = 100
Name: string = curl
Age: int = 144
ReflectCallFunc: func(main.User)
说明:
通过运行结果可以得知获取未知类型的interface的具体变量及其类型的步骤为:
1.先获取interface的reflect.Type,然后通过NumField进行遍历
2.再通过reflect.Type的Field获取其Field
3.最后通过Field的Interface()得到对应的value
通过运行结果可以得知获取未知类型的interface的所属方法的步骤为:
1.先获取interface的reflect.Type,然后通过NumMethod进行遍历
2.再分别通过reflect.Type的Method获取对应的真实的方法(函数)
3.最后对结果取其Name和Type得知具体的方法名
4.也就说反射可以将“反射类型对象”再重新转换为“接口类型遍历”
5.struct或者struct的嵌套都是一样的判断处理方式
case4:通过reflect.Value设置实际变量的值
package main
import (
"fmt"
"reflect"
)
func main(){
var num float64=1.2345
fmt.Println("old value of pointer:",num)
//通过reflect.ValueOf获取num中的reflect.Value,注意,参数必须是指针才能修改其值
pointer:=reflect.ValueOf(&num)
//Elem获取引用
newValue:=pointer.Elem()
fmt.Println("type of pointer: ",newValue.Type())
fmt.Println("settability of pointer:",newValue.CanSet())
//判断是否可以赋值
if newValue.CanSet(){
//重新赋值
newValue.SetFloat(777)
}else{
fmt.Println("not revalue")
}
fmt.Println("new value is",num)
}
结果:
old value of pointer: 1.2345
type of pointer: float64
settability of pointer: true
new value is 777
说明
- 需要传入的参数是* float64这个指针,然后可以通过pointer.Elem()去获取所指向的Value,注意一定要是指针。
- 如果传入的参数不是指针,而是变量,那么:
通过Elem获取原始值对应的对象则直接panic;
通过CanSet方法查询是否可以设置返回false
- newValue.CantSet()表示是否可以重新设置其值,如果输出的是true则可修改,否则不能修改,修改完之后再进行打印发现真的已经修改了。
- reflect.Value.Elem() 表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的
- 也就是说如果要修改反射类型对象,其值必须是“addressable”【对应的要传入的是指针,同时要通过Elem方法获取原始值对应的反射对象】
case5:通过reflect.ValueOf来进行方法的调用
package main
import (
"fmt"
"reflect"
)
type User struct{
Id int
Name string
Age int
}
func (u User) ReflectCallFuncHasArgs(name string, age int) {
fmt.Println("ReflectCallFuncHasArgs name: ", name, ", age:", age, "and origal User.Name:", u.Name)
}
func (u User) ReflectCallFuncNoArgs() {
fmt.Println("ReflectCallFuncNoArgs")
}
/*
如何通过反射来进行方法的调用
本来可以用u.ReflectCallFuncXXX来调用
本来可以用u.ReflectCallFuncXXX直接调用的,但是如果要通过反射,那么首先
要将方法注册,也就是MethodByName,然后通过反射调动mv.Call
*/
func main(){
user:=User{1,"lllll",23}
//1.要通过反射来调用对应的方法,必须要先通过reflect.ValueOf(interface)来获取到
//reflect.Value,得到“反射类型对象”后才能做下一步处理
getValue:=reflect.ValueOf(user)
//一定要指定参数为正确的方法名
//2.先看看带有参数的调用方法
methodValue:=getValue.MethodByName("ReflectCallFuncHasArgs")
args:=[]reflect.Value{reflect.ValueOf("curl"),reflect.ValueOf(10)}
methodValue.Call(args)
//一定要指定参数为正确的方法名
//3. 再看看无参数的调用方法
methodValue=getValue.MethodByName("ReflectCallFuncNoArgs")
args=make([]reflect.Value,0)
methodValue.Call(args)
}
说明:
1.要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,得到“反射类型对象”后才能做下一步处理。
2.reflect.Value.MethodByName这.MethodByName,需要指定准确真实的方法名字,如果错误将直接panic,MethodByName返回一个函数值对应的reflect.Value方法的名字;
3.[]reflect.Value,这个是最终需要调用的方法的参数,可以没有或者一个或者多个,根据实际参数来定
4.reflect.Value的Call这个方法,这个方法将最终调用真实的方法,参数务必一致,如果reflect.Value.Kind不是一个方法,则直接panic
5.本来可以用u.ReflectCallFuncXXX直接调用的,但是如果要通过反射,那么首先要将方法注册,也就是MethodByName,然后通过反射调用methodValue.Call
case6: 通过反射获取结构体标签
package main
import (
"reflect"
"fmt"
)
type Stu struct{
Name string `json:"name" doc:"我的名字"`
}
func findDoc(stu interface{}) map[string]string{
t:=reflect.TypeOf(stu).Elem()
doc:=make(map[string]string)
for i:=0;i<t.NumField();i++{
doc[t.Field(i).Tag.Get("json")]=t.Field(i).Tag.Get("doc")
}
return doc
}
func main(){
var stu Stu
doc:=findDoc(&stu)
fmt.Printf("name字段为:%s\n",doc["name"])
}
结果:
name字段为:我的名字
struct成员变量标签(TAG)说明:
在golang中,命名都是推荐都是用驼峰方式,并且在首字母大小写有特殊的语法含义:包外无法引用。但是由经常需要和其它的系统进行数据交互,例如转成json格式,存储到mongodb啊等等。这个时候如果用属性名来作为键值可能不一定会符合项目要求。struct是golang中最常使用的变量类型之一,几乎每个地方都有使用,从处理配置选项到使用encoding/json或encoding/xml包编排JSON或XML文档。字段标签是struct字段定义部分,允许你使用优雅简单的方式存储许多用例字段的元数据(如字段映射,数据校验,对象关系映射等等)。