golang reflect知识集锦
反射之结构体tag
- 通过v.Field(i).Tag 获取结构体字段的field
- 通过v.Field(i).Tag.Get("id") 获取结构体字段中的特定信息
- func(tag StructTag)Lookup(key string)(value string,ok bool)
根据 Tag 中的键,查询值是否存在。
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明一个空结构体
type cat struct {
Name string
// 带有结构体tag的字段
Type int `json:"type" id:"100"`
}
// 创建cat的实例
ins := cat{Name: "mimi", Type: 1}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(ins)
// 遍历结构体所有成员
for i := 0; i < typeOfCat.NumField(); i++ {
// 获取每个成员的结构体字段类型
fieldType := typeOfCat.Field(i)
// 输出成员名和tag
fmt.Printf("name: %v tag: '%v'\n", fieldType.Name, fieldType.Tag)
}
// 通过字段名, 找到字段类型信息
if catType, ok := typeOfCat.FieldByName("Type"); ok {
// 从tag中取出需要的tag
fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
}
}
Types vs Kinds
- kind 指的是golang 内置的类型,如int, int32等,在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。例如,需要统一判断类型中的指针时,使用种类(Kind)信息就较为方便。内置类型如下
内置类型如下
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
reflect.Kind例子
package main
import (
"fmt"
"reflect"
)
func main() {
for _, v := range []interface{}{"hi", 42, func() {}} {
switch v := reflect.ValueOf(v); v.Kind() {
case reflect.String:
fmt.Println(v.String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fmt.Println(v.Int())
default:
fmt.Printf("unhandled kind %s", v.Kind())
}
}
}
hi
42
unhandled kind func
- type 指的是使用type定义的类型
type mystruct struct {
name string
age int
}
func main() {
ms := mystruct{"test", 100}
fmt.Println(ms)
}
ms的type为自定义类型mystruct,而kind则为struct
fmt.Println(reflect.TypeOf(ms))
fmt.Println(reflect.ValueOf(ms))
// 输出
// main.mystruct
// {test 100}
总之: kind是go runtime和compiler为变量分配内存或为函数分配堆栈时是使用的概念,如Int8等。而Type则是Go编程时使用的概念。
Type is the user defined metadata about data or function in a Go program. Kind is the compiler and runtime defined metadata about data or function in a Go program.
Kind is used by the runtime and compiler to allocate the memory layout for a variable or to allocate the stack layout for a function.
reflect.Type vs reflect.Value
golang为类型的存储方式为(type, value), type有static type,对应上面的Type,有concrete type,对应上面的Kind。注意只有interface类型才有反射一说。value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。
- reflect.Type
直接给到了我们想要的type类型,如float64、int、各种pointer、struct 等等真实的类型 - reflect.Value
直接给到了我们想要的具体的值,如1.2345这个具体数值,或者类似&{1 "Allen.Wu" 25} 这样的结构体struct的值
通过v=reflect.ValueOf(inst)获取到reflect.Value, 然后通过v.Kind == reflect.Int等判断是否是go基础类型
func formatAtom(v reflect.Value) string {
switch v.Kind() {
case reflect.Invalid:
return "invalid"
case reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(v.Uint(), 10)
// ...floating-point and complex cases omitted for brevity...
case reflect.Bool:
return strconv.FormatBool(v.Bool())
case reflect.String:
return strconv.Quote(v.String())
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
return v.Type().String() + " 0x" +
strconv.FormatUint(uint64(v.Pointer()), 16)
default: // reflect.Array, reflect.Struct, reflect.Interface
return v.Type().String() + " value"
}
}
一个运用反射调用方法的例子
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直接调用的,但是如果要通过反射,那么首先要将方法注册,也就是MethodByName,然后通过反射调动mv.Call
func main() {
user := User{1, "Allen.Wu", 25}
// 1. 要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,得到“反射类型对象”后才能做下一步处理
getValue := reflect.ValueOf(user)
// 一定要指定参数为正确的方法名
// 2. 先看看带有参数的调用方法
methodValue := getValue.MethodByName("ReflectCallFuncHasArgs")
args := []reflect.Value{reflect.ValueOf("wudebao"), reflect.ValueOf(30)}
methodValue.Call(args)
// 一定要指定参数为正确的方法名
// 3. 再看看无参数的调用方法
methodValue = getValue.MethodByName("ReflectCallFuncNoArgs")
args = make([]reflect.Value, 0)
methodValue.Call(args)
}
运行结果:
ReflectCallFuncHasArgs name: wudebao , age: 30 and origal User.Name: Allen.Wu
ReflectCallFuncNoArgs
2019/4/20 补充
reflect.Value转原始类型
上面的例子我们可以通过reflect.ValueOf函数把任意类型的对象转为一个reflect.Value,那我们如果我们想逆向转过回来呢,其实也是可以的,reflect.Value为我们提供了Inteface方法来帮我们做这个事情。继续接上面的例子:
u1:=v.Interface().(User)
fmt.Println(u1)
这样我们就又还原为原来的User对象了,通过打印的输出就可以验证。这里可以还原的原因是因为在Go的反射中,把任意一个对象分为reflect.Value和reflect.Type,而reflect.Value又同时持有一个对象的reflect.Value和reflect.Type,所以我们可以通过reflect.Value的Interface方法实现还原。现在我们看看如何从一个reflect.Value获取对应的reflect.Type。
t1:=v.Type()
fmt.Println(t1)
如上例中,通过reflect.Value的Type方法就可以获得对应的reflect.Type。
获取类型底层类型
底层的类型是什么意思呢?其实对应的主要是基础类型,接口、结构体、指针这些,因为我们可以通过type关键字声明很多新的类型,比如上面的例子,对象u的实际类型是User,但是对应的底层类型是struct这个结构体类型,我们来验证下。
fmt.Println(t.Kind())
遍历字段和方法
通过反射,我们可以获取一个结构体类型的字段,也可以获取一个类型的导出方法,这样我们就可以在运行时了解一个类型的结构,这是一个非常强大的功能。
for i:=0;i<t.NumField();i++ {
fmt.Println(t.Field(i).Name)
}
for i:=0;i<t.NumMethod() ;i++ {
fmt.Println(t.Method(i).Name)
}
这个例子打印出结构体的所有字段名以及该结构体的方法。NumField方法获取结构体有多少个字段,然后通过Field方法传递索引的方式,循环获取每一个字段,然后打印出他们的名字。
同样的对于方法也类似,这里不再赘述。
获取值
当我们将一个接口值传递给一个 reflect.ValueOf 函数调用时,此调用返回的是代表着此接口值的动态值的一个 reflect.Value 值。我们必须通过间接的途径获得一个代表一个接口值的 reflect.Value 值
// 声明整型变量a并赋初值
var a int = 1024
// 获取变量a的反射值对象
valueOfA := reflect.ValueOf(a)
// 获取interface{}类型的值, 通过类型断言转换
var getA int = valueOfA.Interface().(int)
// 获取64位的值, 强制类型转换为int类型
var getA2 int = int(valueOfA.Int())
修改字段的值
假如我们想在运行中动态的修改某个字段的值有什么办法呢?一种就是我们常规的有提供的方法或者导出的字段可以供我们修改,还有一种是使用反射,这里主要介绍反射。
func main() {
x:=2
v:=reflect.ValueOf(&x)
v.Elem().SetInt(100)
fmt.Println(x)
}
以上就是通过反射修改一个变量的例子。
因为reflect.ValueOf函数返回的是一份值的拷贝,所以前提是我们是传入要修改变量的地址。
其次需要我们调用Elem方法找到这个指针指向的值。
最后我们就可以使用SetInt方法修改值了。
以上有几个重点,才可以保证值可以被修改,Value为我们提供了CanSet方法可以帮助我们判断是否可以修改该对象。
动态调用方法
结构体的方法我们不光可以正常的调用,还可以使用反射进行调用。要想反射调用,我们先要获取到需要调用的方法,然后进行传参调用,如下示例:
func main() {
u:=User{"张三",20}
v:=reflect.ValueOf(u)
mPrint:=v.MethodByName("Print")
args:=[]reflect.Value{reflect.ValueOf("前缀")}
fmt.Println(mPrint.Call(args))
}
type User struct{
Name string
Age int
}
func (u User) Print(prfix string){
fmt.Printf("%s:Name is %s,Age is %d",prfix,u.Name,u.Age)
}
MethodByName方法可以让我们根据一个方法名获取一个方法对象,然后我们构建好该方法需要的参数,最后调用Call就达到了动态调用方法的目的。
获取到的方法我们可以使用IsValid 来判断是否可用(存在)。
这里的参数是一个Value类型的数组,所以需要的参数,我们必须要通过ValueOf函数进行转换。
golang reflect知识集锦的更多相关文章
- golang reflect
golang reflect go语言中reflect反射机制.详细原文:地址 接口值到反射对象 package main import ( "fmt" "reflect ...
- golang基础知识之encoding/json package
golang基础知识之json 简介 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式.可以去json.org 查看json标准的清晰定义.json pack ...
- Java知识集锦
Java知识集锦 一.Java程序基础 1.1 开发和运行环境 1.2 Java语言概述 二.Java语法基础 2.1 基础类型和语法 2.2 对象和类型 2.3 包和访问控制 三.数据类型及类型转换 ...
- jQuery知识集锦
CreateTime--2017年2月16日14:00:22Author:MarydonjQuery知识集锦1.empty()与remove()的区别 <select id="ty ...
- golang reflect包使用解析
golang reflect包使用解析 参考 Go反射编码 2个重要的类型 Type Value 其中Type是interface类型,Value是struct类型,意识到这一点很重要 Type和Va ...
- php常用知识集锦
php常用知识集锦 很多位置都有写好的代码,自己做项目的时候可以直接拿来用,而不用自己写,比如现在看到的菜鸟教程. 1.判断是否为空 empty($_POST["name"]) 2 ...
- golang reflect 简单使用举例
golang中的多态,主要由接口interface体现. 接口interface在实现上,包括两部分:动态类型和动态值. golang提供的reflect包可以用来查看这两部分. 动态类型 func ...
- 前端不为人知的一面--前端冷知识集锦 前端已经被玩儿坏了!像console.log()可以向控制台输出图片
前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前 ...
- 前端不为人知的一面–前端冷知识集锦 原文地址(http://web.jobbole.com/83473/);
前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前 ...
随机推荐
- 浅谈BST(二叉查找树)
目录 BST的性质 BST的建立 BST的检索 BST的插入 BST求前驱/后继 BST的节点删除 复杂度 平衡树 BST的性质 树上每个节点上有个值,这个值叫关键码 每个节点的关键码大于其任意左侧子 ...
- css 水平垂直居中两种常用方式
- 关于qt creator各种假死的问题
来自CSDN网友( qq191329827)内容,亲自尝试,且经历一致: 我有两个笔记本,1个台式机,都装了qt, 然后,我的thinkpad x1c,装上之后creator各种假死,网上看了好多解决 ...
- 深入解密来自未来的缓存-Caffeine
1.前言 读这篇文章之前希望你能好好的阅读: 你应该知道的缓存进化史 和 如何优雅的设计和使用缓存? .这两篇文章主要从一些实战上面去介绍如何去使用缓存.在这两篇文章中我都比较推荐Caffeine这款 ...
- Python 3.X 练习集100题 01
有以下几个数字:1.2.3.4.5,能组成多少个互不相同且无重复数字的三位数?都是多少? 方法1: import itertools from functools import reduce lyst ...
- 30分钟用 Laravel 实现一个博客
介绍 Laravel 是一款 MVC架构. 目前最流行的 PHP框架. Laravel的优点在于: 丰富的composer类库支持, 优雅的代码, 未来的主流框架(目前市场占有率最高的框架) Lara ...
- win10 .net framework 3.5 离线安装 不需要外网
win 10如果安装系统时没有安装.net 3.5 那么在以后安装时就必须联网. win10 .net framework 3.5 离线安装工具: 链接: https://pan.baidu.com/ ...
- influx db
1.查看数据库中的tag keys: 如果需要查看field的直接改 > show tag keys on test; name: garage_pc_overviewtagKey--- ...
- java自带的xml解析工具类
public class JaxbUtil { /** * java对象转换为xml文件 * * @param xmlPath xml文件路径 * @param load java对象.Class * ...
- Shell获取指定区间随机未占用的端口号
说明 最近在写Jenkins自动运维的脚本,由于是用的docker,部署的时候启动容器端口号冲突会导致部署失败,用的微服务也不在乎端口什么的,只求部署成功,所以想了很久,参考了一些文章,还有运维大哥的 ...