引用自 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. Qt 设置应用程序图标(windows)

    Step 1: 创建  xxx.rc 文件. 将ico图标文件复制到项目根目录下.然后在该目录中新建xxx.rc文件,并输入一行代码: IDI_ICON1 ICON DISCARDABLE " ...

  2. SQL优化单表案例

    数据准备: -- 创建数据库 mysql> create database db_index_case; Query OK, row affected (0.00 sec) -- 查看数据库 m ...

  3. oracle 数据库图形化工具 sqldeveloper

    1. 安装完成Oracle数据库,点击左下角[开始]菜单,在所有程序中打开[Oracle] 2. 在开始菜单,展开Oracle数据库,安装文件,然后打开[应用程序开发].可以看到[sqldevelop ...

  4. es6+最佳入门实践(5)

    5.对象扩展 5.1.对象简写 在es5中,有这样一种写法 var name = "xiaoqiang"; var age = 12; var obj = { name : nam ...

  5. sublime JSX Html 标签补全

    Preferences -> Package Settings -> Emmet ->key bindings – user { "keys": ["t ...

  6. HDU 1395

    2^x mod n = 1 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Tot ...

  7. Idea切换svn分支,类似Eclipse的Switch功能

    vcs --> subversion --> update directory --> 勾选中 Update/Switch to specific url 重新设置新的URL即可

  8. 之江学院第0届校赛 qwb去面试 (找规律)

    Description 某一天,qwb去WCfun面试,面试官问了他一个问题:把一个正整数n拆分成若干个正整数的和,请求出这些数乘积的最大值. qwb比较猥琐,借故上厕所偷偷上网求助,聪明的你能帮助他 ...

  9. Intellij IDEA创建spring MVC项目

    相信各位未来的Java工程师已经接触到了spring MVC这个框架的强大之处,看了很多的教程,都是eclipse的,在intellij IDEA这个强大的工具面前居然不能很顺畅的,今天我就带领大家用 ...

  10. vue this.$router.push 页面不刷新

    解决办法: 使用 watch,观察路由,发生变化重新获取数据 <script> export default { data() { return { data: {} } }, metho ...