golang笔记-反射reflect-02-详解

详解概述

    在计算机语言中,编译时就知道变量类型的是静态类型;运行时才知道变量类型的叫做动态类型。比如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

说明

  1. 需要传入的参数是* float64这个指针,然后可以通过pointer.Elem()去获取所指向的Value,注意一定要是指针。
  2. 如果传入的参数不是指针,而是变量,那么:

                 通过Elem获取原始值对应的对象则直接panic;

                 通过CanSet方法查询是否可以设置返回false

  1. newValue.CantSet()表示是否可以重新设置其值,如果输出的是true则可修改,否则不能修改,修改完之后再进行打印发现真的已经修改了。
  2. reflect.Value.Elem() 表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的
  3. 也就是说如果要修改反射类型对象,其值必须是“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字段定义部分,允许你使用优雅简单的方式存储许多用例字段的元数据(如字段映射,数据校验,对象关系映射等等)。



 

 

 

 

 

 

          

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值