Go 反射

     文章转载地址:https://www.flysnow.org/2017/06/13/go-in-action-go-reflect.html

1. TypeOf 和 ValueOf 

     在 Go 的反射定义中,任何接口都由两部分组成,一个是接口的具体类型,一个是具体类型对应的值。比如:

var i int = 3,因为 interface{} 可以表示任何类型,所以变量 i 可以转换成 interface{} ,所以可以把变量 i 当成一个

接口,那么这个变量在 Go 反射中的表示就是 <type,value> ,其中 value 为变量的值 3,type 为类型 int

     在 Go 反射中,标准库为我们提供两种类型来分别表示他们 reflect.Value 和 reflect.Type ,并提供两个函数来获

取任意对象的 Value 和 Type,看如下示例:

package main

import (
	"fmt"
	"reflect"
)

// 定义一个 User 结构体
type User struct {
	Name string
	Age int
}

func main() {
	u := User{"张三",25}
	t := reflect.TypeOf(u)
	fmt.Println(t)
}

-------------------------------

输出结果:

main.User

  reflect.TypeOf 可以获取任意对象的具体类型,通过上面的例子可以看到打印输出的结果是 main.User 这个结构体类型

       那么如何反射获取一个对象的 Value:

v := reflect.ValueOf(u)
fmt.Println(v)

--------------------------

输出结果:

{张三 25}

  对于以上两种输出,Go 语言还通过 fmt.Printf 函数提供了更为简便的方法

fmt.Printf("%T\n",u)
fmt.Printf("%v\n",u)

2.reflect.Value 转原始类型

   上面的例子中我们通过 reflect.ValueOf 函数把任意类型对象转换成 reflect.Value 类型,如果我们想逆向转换回来呢?其实也

是可以的,reflect.Value 为我们提供了 interface 方法,如下示例:

package main

import (
	"fmt"
	"reflect"
)

// 定义一个 User 结构体
type User struct {
	Name string
	Age int
}

func main() {
	u := User{"张三",25}
	// 使用 refelct.ValueOf 转换成 refelct.Value
	v := reflect.ValueOf(u)
	fmt.Println(reflect.TypeOf(v))

	// 使用 interface 方法转换成 main.User
	u1 := v.Interface().(User)
	fmt.Println(reflect.TypeOf(u1))
}

-----------------------------------------------------

输出结果:

reflect.Value
main.User

3.获取类型底层类型

  底层类型是什么意思?其实对应的主要是基础类型、接口、结构体、指针这些,因为我们可以通过 Type 关键字声明

很多新的类型,比如上面的例子,对象 u 的实际类型是 User,但对应的底层类型是 struct 这个结构体类型,如下示例:

package main

import (
	"fmt"
	"reflect"
)

// 定义一个 User 结构体
type User struct {
	Name string
	Age int
}

func main() {
	u := User{"张三",25}
	t := reflect.TypeOf(u)
	fmt.Println(t.Kind())
}

--------------------------------

输出结果:

struct

4.遍历字段和方法

   通过反射,我们可以获取一个结构体类型的字段,也可以获取一个类型的导出方法,这样我们就可以在运行时了解一个

类型的结构,如下示例:

package main

import (
	"fmt"
	"reflect"
)

// 定义一个 User 结构体
type User struct {
	Name string
	Age int
}

// 给结构体 User 绑定一个方法
func (u User) Print() {
	fmt.Println("I am User")
}

func main() {
	u := User{"张三",26}
	t := reflect.TypeOf(u)

	// 获取结构体类型的字段
	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)
	}
}

---------------------------------------------------

输出结果:

Name
Age
Print

5.修改字段的值

  我们还可以通过反射修改某个字段的值,如下示例(使用反射修改变量的值):

package main

import (
	"fmt"
	"reflect"
)

func main() {
	x := 2
	v := reflect.ValueOf(&x)
	v.Elem().SetInt(100)
	fmt.Println(x)
}

---------------------------------

输出结果:

100

   因为 reflect.ValueOf 返回的是一份值的拷贝,所以要想修改一个变量的值前提是要传入修改变量的地址。

其次需要我们调用 Elem 方法找到指针指向的值。最后我们就可以使用 SetInt 方法修改值了 

      如何修改结构体字段的值呢?如下示例:

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type User struct {
	Name string
	Age int
}

func main() {
	u := User{"tom",11}
	v := reflect.ValueOf(&u.Name)
	v.Elem().SetString("paul")
	fmt.Println(u)
}

----------------------------------------

输出结果:

{paul 11}

6.动态调用方法

  结构体的方法不仅可以正常调用,还可以通过反射调用。要想通过反射调用,我们先要获取到需要调用的方法,

然后进行传参调用,如下示例:

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type User struct {
	Name string
	Age int
}

// 给 User 绑定一个方法
func (u User) Print(prfix  string) {
	fmt.Printf("%s:Name is %s,Age is %d",prfix,u.Name,u.Age)
}

func main() {
	u := User{"lotus",26}
	v := reflect.ValueOf(u)

	// MethodByName 根据方法名获取方法对象
	mPrint := v.MethodByName("Print")

	// 构建参数
	args := []reflect.Value{reflect.ValueOf("前缀")}
	fmt.Println(mPrint.Call(args))
}

--------------------------------------------------------------

输出结果:

前缀:Name is lotus,Age is 26[]

  MethodByName 可以根据一个方法名获取一个方法对象,然后我们构建好该方法需要的参数,最后调用

Call 就达到了动态调用方法的目的

7 通过反射获取 struct 的 Tag 

   字段的 Tag 是标记到字段上的,所以我们可以通过先获取字段,然后再获取标记在字段上的 Tag,如下示例:

package main

import (
	"fmt"
	"reflect"
)

// 定义一个 User 结构体
type User struct {
	Name string `name`
	Age int `age`
}

func main() {
	var u User
	t := reflect.TypeOf(u)

	for i := 0; i < t.NumField(); i++ {
		sf := t.Field(i)
		fmt.Println(sf.Tag)
	}
}

---------------------------------------------

输出结果:

name
age

  通过 .Tag 就可以获取到对应的 Tag 

8 字段 Tag 的键值对

   一个 struct 的字段可能对应多个不同的 Tag,以便满足不同的功能场景,在 Go 中,struct 为我们提供了

键值对的 Tag ,来满足我们针对不同场景的需求,如下示例:

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type User struct {
	Name string `json:"name"`
	Age int `json:"age"`
}

func main() {
	var u User
	t := reflect.TypeOf(u)

	for i := 0; i < t.NumField(); i++ {
		sf := t.Field(i)
		fmt.Println(sf.Tag.Get("json"))
	}
}

--------------------------------------------------

输出结果:

name
age

  上面例子,使用了键值对的方式配置 struct Tag,Key-Value 以冒号分开,这里的 Key 就是 json,所以,我们

可以通过这个 Key 获取对应的值,也就是通过 .Tag.Get("json") 方法。 Get 方法就是通过一个 Key 获取对应的 tag 设置

       除此之外,我们还可以通过设置多个 key,如下示例:

 

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type User struct {
	Name string `json:"name" bson:"b_name"`
	Age int `json:"age" bson:"b_age"`
}

func main() {
	var u User
	t := reflect.TypeOf(u)

	for i := 0; i < t.NumField(); i++ {
		sf := t.Field(i)
		fmt.Println(sf.Tag.Get("json"),",",sf.Tag.Get("bson"))
	}
}

--------------------------------------------------------------------------

输出结果:

name , b_name
age , b_age

  多个 Key 之间使用空格分开,然后通过 Get 方法获取不同的 Key 值

posted @ 2019-02-15 11:03  流光瞬息  阅读(263)  评论(0编辑  收藏  举报