golang reflect知识集锦

目录

  • 反射之结构体tag
  • Types vs Kinds
  • reflect.Type vs reflect.Value
  • 2019/4/20 补充
  • reflect.Value转原始类型
  • 获取类型底层类型
  • 遍历字段和方法
  • 获取值
  • 修改字段的值
  • 动态调用方法

反射之结构体tag

链接

  1. 通过v.Field(i).Tag 获取结构体字段的field
  2. 通过v.Field(i).Tag.Get("id") 获取结构体字段中的特定信息
  3. 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

Golang学习 - reflect 包
Golang的反射reflect深入理解和示例
系列文章

  • 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.ValueOf(x)或者reflect.TypeOf(x)获取对象后,对其执行v.Kind(), 结果与reflect.TypeOf(x)相同 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 补充

《Go语言实战》笔记(二十四) | Go 反射

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函数进行转换。

原文地址:https://www.cnblogs.com/linyihai/p/11602484.html

时间: 2024-10-05 23:25:16

golang reflect知识集锦的相关文章

Tomcat发布网站知识集锦

修改端口.修改默认发布目录.多域名绑定 一.修改发布端口号为80(Tomcat默认为8080) 打开配置文件(我的如下:E:\J2EEServer\Tomcat 6.0\conf\server.xml),找到: <Connector port="8080" protocol="HTTP/1.1" maxThreads="150" connectionTimeout="20000" redirectPort="8

开发零碎知识集锦

开发零碎知识集锦 idea超好用的快捷键 默认的idea快捷键,以及常用的快捷键就不再数流水,这里只介绍你不常用,但是又十分好用的快捷键. 上次改了什么,忘记了?alt + shift + c显示最近的更改 想找类里面的某个方法?ctrl + F12,当然还有个更强大的alt + shift + ctrl + n,不仅可以搜索方法,还可以搜类名 想定位到某一行代码?ctrl + G输入行号即可 Chrome开发者工具快捷键 想找某个文件?ctrl + P搜索某个文件 想定位某一行? ctrl +

前端冷知识集锦[转载]

作者:伯乐在线专栏作者 - 刘哇勇 前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前端技能,一些属于技巧,一些则是闻所未闻的冷知识,一时间还消化不过来.现分类整理出来分享给大家,也补充了一些平时的积累和扩展了一些内容. HTML篇 浏览器地址栏运行JavaScript代码 这个很多人应该还是知道的,在浏览器地址栏可以直接运行JavaScript代

前端不为人知的一面--前端冷知识集锦

前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前端技能,一些属于技巧,一些则是闻所未闻的冷知识,一时间还消化不过来.现分类整理出来分享给大家,也补充了一些平时的积累和扩展了一些内容. HTML篇 浏览器地址栏运行JavaScript代码 这个很多人应该还是知道的,在浏览器地址栏可以直接运行JavaScript代码,做法是以javascript:开

前端不为人知的一面–前端冷知识集锦

前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前端技能,一些属于技巧,一些则是闻所未闻的冷知识,一时间还消化不过来.现分类整理出来分享给大家,也补充了一些平时的积累和扩展了一些内容. HTML篇 浏览器地址栏运行JavaScript代码 这个很多人应该还是知道的,在浏览器地址栏可以直接运行JavaScript代码,做法是以 javascript:

golang reflect

reflect包实现了运行时反射,允许程序操作任意类型的对象.典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值.调用ValueOf函数返回一个Value类型值,该值代表运行时的数据.Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值 type package main import ( "reflect" "fmt" ) type lx interface { SayH

TCP/IP网络协议基础知识集锦[转]

引言 本篇属于TCP/IP协议的基础知识,重点介绍了TCP/IP协议簇的内容.作用以及TCP.UDP.IP三种常见网络协议相关的基础知识. 内容 TCP/IP协议簇是由OSI七层模型发展而来的,之所以存在OSI互联参考模型主要有以下几点好处: 1.用于解决不同厂商的设备互联问题:即兼容性考虑; 2.层次化结构独立性强;可扩展性强; 3.统一标准可以方便学习; TCP/IP模型 TCP/IP模型与OSI模型的相互关系如下图所示: 注释 1.应用层:与OSI的应用层.表示层.会话层相对应,主要作用是

前端不为人知的一面——前端冷知识集锦

前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前端技能,一些属于技巧,一些则是闻所未闻的冷知识,一时间还消化不过来.现分类整理出来分享给大家,也补充了一些平时的积累和扩展了一些内容. HTML篇 浏览器地址栏运行JavaScript代码 这个很多人应该还是知道的,在浏览器地址栏可以直接运行JavaScript代码,做法是以javascript:开

(转)前端不为人知的一面--前端冷知识集锦

转自:http://www.cnblogs.com/Wayou/p/things_you_dont_know_about_frontend.html 前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前端技能,一些属于技巧,一些则是闻所未闻的冷知识,一时间还消化不过来.现分类整理出来分享给大家,也补充了一些平时的积累和扩展了一些内容. HTML篇 浏