引用自 http://www.jb51.net/article/115002.htm

和 C 数据结构一样,Go 对象头部并没有类型指针,通过其自身是无法在运行期获知任何类型相关信息的。反射操作所需要的全部信息都源自接口变量。接口变量除存储自身类型外,还会保存实际对象的类型数据。

reflect包有两个接口:reflect.Type和reflect.Value。这两个反射入口函数,会将任何传入的对象转换为对应的接口类型。

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

一、Type(类型)

源码文件里type的注释:
// Type is the representation of a Go type.
//
// Not all methods apply to all kinds of types. Restrictions,
// if any, are noted in the documentation for each method.
// Use the Kind method to find out the kind of type before
// calling kind-specific methods. Calling a method
// inappropriate to the kind of type causes a run-time panic.
//
// Type values are comparable, such as with the == operator,
// so they can be used as map keys.
// Two Type values are equal if they represent identical types.

在面对类型时,需要区分 Type 和 Kind。前者表示真实类型(静态类型),后者表示其基础结构(底层类型)类别 -- 基类型。

type X int
func main() {
var a X = 100
t := reflect.TypeOf(a)
fmt.Println(t)
fmt.Println(t.Name(), t.Kind())
} 输出:X int

  

除通过实际对象获取类型外,也可直接构造一些基础复合类型。

func main() {
a := reflect.ArrayOf(10, reflect.TypeOf(byte(0)))
m := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
fmt.Println(a, m)
}

输出:[10]uint8 map[string]int
// 还有ArrayOf, ChanOf, MapOf and SliceOf

  

传入对象 应区分 基类型 和 指针类型,因为它们并不属于同一类型。方法 Elem() 返回 指针、数组、切片、字典(值)或 通道的 基类型。

func main() {
   x := 100
tx, tp := reflect.TypeOf(x), reflect.TypeOf(&x)
fmt.Println(tx, tp, tx == tp) fmt.Println(reflect.TypeOf(map[string]int{}).Elem())
fmt.Println(reflect.TypeOf([]int32{}).Elem())
} 输出:
int *int false
int
int32

  

  

只有在获取 结构体指针 的 基类型 后,才能遍历它的字段。对于匿名字段,可用多级索引(按照定义顺序)直接访问。反射能探知当前包或外包的非导出结构成员。

type user struct {
name string
age int
}
type manager struct {
user
title string
}
func main() {
var m manager
t := reflect.TypeOf(&m)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
  // age := t.FieldByIndex([]int{0, 1}) // 按多级索引查找匿名字段
  // fmt.Println(age.Name, age.Type) 输出:age int
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Println(f.Name, f.Type, f.Offset)
if f.Anonymous { // 输出匿名字段结构
for x := 0; x < f.Type.NumField(); x++ {
af := f.Type.Field(x)
fmt.Println(" ", af.Name, af.Type)
}
}
  }
} 输出:
user main.user 0
name string
age int
title string 24

  

  

同样地,输出方法集时,一样区分 基类型 和 指针类型。

type A int
type B struct {
A
}
func (A) av() {}
func (*A) ap() {}
func (B) bv() {}
func (*B) bp() {}
func main() {
var b B
t := reflect.TypeOf(&b)
s := []reflect.Type{t, t.Elem()}
for _, t2 := range s {
fmt.Println(t2, ":")
for i := 0; i < t2.NumMethod(); i++ {
fmt.Println(" ", t2.Method(i))
}
}
} 输出:
*main.B :
{ap main func(*main.B) <func(*main.B) Value> 0}
{av main func(*main.B) <func(*main.B) Value> 1}
{bp main func(*main.B) <func(*main.B) Value> 2}
{bv main func(*main.B) <func(*main.B) Value> 3}
main.B :
{av main func(*main.B) <func(*main.B) Value> 0}
{bv main func(*main.B) <func(*main.B) Value> 1}

  

  

可用反射提取 struct tag,还能自动分解。其常用于 ORM 映射,或数据格式验证。

type user struct {
name string `field:"name" type:"varchar(50)"`
age int `field:"age" type:"int"`
}
func main() {
var u user
t := reflect.TypeOf(u)
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("%s: %s %s\n", f.Name, f.Tag.Get("field"), f.Tag.Get("type"))
}
} 输出:
name: name varchar(50)
age: age int 

  

 

辅助判断方法 Implements()、ConvertibleTo、AssignableTo() 都是运行期进行 动态调用 和 赋值 所必需的。

二、值(Value)

 和 Type 获取类型信息不同,Value 专注于对象实例数据读写。

go源码里面Value的注释:
// Value is the reflection interface to a Go value.
//
// Not all methods apply to all kinds of values. Restrictions,
// if any, are noted in the documentation for each method.
// Use the Kind method to find out the kind of value before
// calling kind-specific methods. Calling a method
// inappropriate to the kind of type causes a run time panic.
//
// The zero Value represents no value.
// Its IsValid method returns false, its Kind method returns Invalid,
// its String method returns "<invalid Value>", and all other methods panic.
// Most functions and methods never return an invalid value.
// If one does, its documentation states the conditions explicitly.
//
// A Value can be used concurrently by multiple goroutines provided that
// the underlying Go value can be used concurrently for the equivalent
// direct operations.
//
// To compare two Values, compare the results of the Interface method.
// Using == on two Values does not compare the underlying values
// they represent.

接口变量会复制对象,且是 unaddressable 的,所以要想修改目标对象,就必须使用指针。

func main()  {
a := 100
  fmt.Println(a)
  va, vp := reflect.ValueOf(a), reflect.ValueOf(&a).Elem()
  fmt.Println(va.CanAddr(), va.CanSet())
  fmt.Println(vp.CanAddr(), vp.CanSet())
  vp.Set(reflect.ValueOf(10))
  fmt.Println(a)
} 输出:
100
false false
true true
10

  

就算传入指针,一样需要通过 Elem() 获取目标对象。因为被接口存储的指针本身是不能寻址和进行设置操作的。

不能对非导出字段直接进行设置操作,无论是当前包还是外包。

复合类型对象设置示例:(chan类型)

func main()  {
c := make(chan int, 4)
v := reflect.ValueOf(c)
if v.TrySend(reflect.ValueOf(100)) {
fmt.Println(v.TryRecv())
}
}

  

接口有两种 nil 状态,这一直是个潜在麻烦。解决方法是用 IsNil() 判断值是否为 nil。

func main()  {
var a interface{} = nil
var b interface{} = (*int)(nil)
fmt.Println(a == nil) // 如果此条件为true,就不能执行reflect.ValueOf(a).IsNil()
fmt.Println(b == nil, reflect.ValueOf(b).IsNil())
} 输出:
true
false true

  

  

三、方法

动态调用方法,谈不上有多麻烦。只须按 In 列表准备好所需参数即可。

type X struct {}
func (X) Test(x, y int) (int, error) {
return x + y, fmt.Errorf("err: %d", x + y)
}
func main() {
var a X
v := reflect.ValueOf(&a)
m := v.MethodByName("Test")
in := []reflect.Value{
reflect.ValueOf(1),
reflect.ValueOf(2),
}
out := m.Call(in) // 对于变参来说,用 CallSlice() 要更方便一些。
for _, v := range out {
fmt.Println(v)
}
} 输出:
3
err: 3

  

  

四、构建

不同结构体字段赋值函数如下。

函数说明:传入参数from和to都是指针变量。只支持不同结构体同名且相同值类型字段赋值(可优化,加一个字段映射,并兼容int与string相互赋值)。

func CopyStruct (from, to interface{}) (bool) {

	typFrom := reflect.TypeOf(from)
valFrom := reflect.Indirect(reflect.ValueOf(from)) typTo := reflect.TypeOf(to)
valTo := reflect.Indirect(reflect.ValueOf(to)) if typFrom.Kind() != reflect.Ptr || typTo.Kind() != reflect.Ptr ||
valFrom.Kind() != reflect.Struct || valTo.Kind() != reflect.Struct {
return false
} typTo = typTo.Elem()
typFrom = typFrom.Elem()
for i := 0; i < typTo.NumField(); i ++ {
toField := typTo.Field(i)
toFieldName := toField.Name
_, exists := typFrom.FieldByName(toFieldName)
if !exists || !valTo.FieldByName(toFieldName).CanSet() {
continue
} if valFrom.FieldByName(toFieldName).Kind() == valTo.FieldByName(toFieldName).Kind() {
valTo.FieldByName(toFieldName).Set(valFrom.FieldByName(toFieldName))
}
}
return true
}

  

golang的reflect的更多相关文章

  1. golang:reflect反射

    因为之前一直以C++为主要开发语言,所以刚接触go语言中的reflect时感觉很懵逼,因此决定找资料彻底学习一下. 到底反射是什么? https://blog.golang.org/laws-of-r ...

  2. golang基础--reflect反射

    反射的知识点比较晦涩,后期会对此知识点展开深入的分析及示例代码展示 反射可达大提高程序的灵活性,使得inferface{}有更大的发挥余地 反射使用TypeOf和ValueOf函数从接口中获取目标对象 ...

  3. golang 使用reflect反射结构体

    "反射结构体"是指在程序执行时,遍历结构体中的字段以及方法. 1.反射结构体 下面使用一个简单的例子说明如何反射结构体. 定义一个结构体,包括3个字段,以及一个方法. 通过refl ...

  4. Golang学习 - reflect 包

    ------------------------------------------------------------ 在 reflect 包中,主要通过两个函数 TypeOf() 和 ValueO ...

  5. 【玩转Golang】reflect.DeepEqual

    如果有两个map,内容都一样,只有顺序不同 m1:=map[,,}; m2:=map[,,}; 我们怎么判断二者是否一致呢? 如果你打算这么写: fmt.Println("m1==m2&qu ...

  6. golang 通过reflect反射修改值

    不是所有的反射值都可以修改.对于一个反射值是否可以修改,可以通过CanSet()进行检查. 要修改值,必须满足: 可以寻址 可寻址的类型: 指针指向的具体元素 slice的元素 可寻址的结构体的字段( ...

  7. golang之reflect

    reflect,反射. 利用reflect,可以得到一个struct的相关信息. package main import ( "fmt" "reflect" ) ...

  8. golang反射reflect机制用法

    package main import ( "fmt" "reflect" ) type User struct { Id int Name string Ag ...

  9. Golang的反射reflect深入理解和示例

    编程语言中反射的概念 在计算机科学领域,反射是指一类应用,它们能够自描述和自控制.也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examin ...

随机推荐

  1. synchronized ---- 作用

    获得同步锁: 1.清空工作内存: 2.从主内存拷贝对象副本到工作内存: 3.执行代码(计算或者输出等): 4.刷新主内存数据: 5.释放同步锁.

  2. 前端面试:js闭包,为什么要使用闭包

    要理解闭包,首先理解javascript特殊的变量作用域,变量的作用于无非就是两种:全局变量,局部变量. javascript语言的特殊处就是函数内部可以读取全局变量. 1.如何从外部读取局部变量? ...

  3. 网络(bzoj 4538)

    Description 一个简单的网络系统可以被描述成一棵无根树.每个节点为一个服务器.连接服务器与服务器的数据线则看做一条树边.两个服务器进行数据的交互时,数据会经过连接这两个服务器的路径上的所有服 ...

  4. tr/td

    在HTML中,tr代表行,td代表列. 说明: 1.tr与td必须一起使用,并且输入的内容必须在td里面: 2.td必须在tr里面,表示在一行中的列: 3.在一个tr里面,有x个td,就表示在这一行里 ...

  5. Laravel - Property [title] does not exist on this collection instance

    When you're using get() you get a collection. In this case you need to iterate over it to get proper ...

  6. 类的 propert,classmethod,ataticmethod 方法 与 多态

    一 .property 将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数 然后计算出来的,这种特性的使用方式遵循了统一访问的原则 egon. ...

  7. 编写类du命令Python脚本

    #!/usr/bin/env python #_*_ coding:utf-8 _*_ #计算整个目录的大小,脚本接受-H参数,来加上适当的单位 #功能像du看齐 import os,sys from ...

  8. lucene api

    设置重新打开索引目录(清空) IndexWriterConfig conf = new IndexWriterConfig(new WhitespaceAnalyzer());conf.setOpen ...

  9. 举例说明如何使用【聚合数据】的API接口

    0 注册[聚合数据]的账号 登陆www.juhe.cn,如图,如果没有账号,注册一个(手机号或者邮箱注册),如果有直接登陆即可. 1 搜索所需的API接口 找到聚合数据主页,在搜索框输入你想搜索的AP ...

  10. java Socket启动服务

    java -cp /Library/WebServer/Documents/Java/test/src com.zhidian.soft.sendOfClick localhost 8888 java ...