在计算机语言中,编译时就知道变量类型的是静态类型;运行时才知道变量类型的叫做动态类型。比如C++中的多态,其实就是一种运行时类型识别。通过学习Golang,我知道,Go是一种静态类型的编程语言,每个变量都内置一个静态类型,这意味着每个变量的类型在编译时都是确定的。golang,也提供了动态类型,interface。下面我们对golang的静态和动态类型进行简单分析。
静态类型就是我们在对变量进行声明时赋予的类型:
//int64则是静态类型
var number int64
//string则是声明的静态类型
var name string
//*string则是静态类型
var stu *string
在程序运行时,我们根据具体业务场景对变量(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
说明:
通过运行结果可以得知获取未知类型的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
说明
通过Elem获取原始值对应的对象则直接panic;
通过CanSet方法查询是否可以设置返回false
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
struct成员变量标签(TAG)说明:
在golang中,命名都是推荐都是用驼峰方式,并且在首字母大小写有特殊的语法含义:包外无法引用。但是由经常需要和其它的系统进行数据交互,例如转成json格式,存储到mongodb啊等等。这个时候如果用属性名来作为键值可能不一定会符合项目要求。struct是golang中最常使用的变量类型之一,几乎每个地方都有使用,从处理配置选项到使用encoding/json或encoding/xml包编排JSON或XML文档。字段标签是struct字段定义部分,允许你使用优雅简单的方式存储许多用例字段的元数据(如字段映射,数据校验,对象关系映射等等)。