Golang的反射reflect

  通过反射(Reflection),可以在程序运行时,获取对象的类型信息,包括确定对象的类、确定对象的类型的所有成员变量和方法、动态调用对象的方法。

  

接口类型变量转换为反射类型对象

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name   string
	Age    int
	gender string
}

func main() {
	var num int = 100
	t := reflect.TypeOf(num)
	v := reflect.ValueOf(num)
	fmt.Println(t, v) //int  100

	p := Person{"John", 30, "male"}
	t = reflect.TypeOf(p)
	v = reflect.ValueOf(p)
	fmt.Println(t, v) //main.Person {John 30 male}

}

  

反射类型对象转换为接口类型变量

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name   string
	Age    int
	gender string
}

func main() {
	//接口类型对象转为反射类型对象
	p := Person{"John", 30, "male"}
	t = reflect.TypeOf(p)
	v = reflect.ValueOf(p)
	fmt.Println(t, v) //main.Person {John 30 male}

	//反射类型对象转换为接口类型对象
	p = v.Interface().(Person)
	fmt.Println(p) //{John 30 male}
}

  转换前后,p是一样的(内存地址是一样的)

 

通过反射类型对象修改接口类型变量的值

  如果要修改“反射类型对象”,其值必须是可写的。

  反射对象包含了接口变量中存储的值以及类型,如果反射对象中包含的是原始值,那么可以通过反射对象修改原始值;如果反射对象中包含的值不是原始对象,比如反射对象包含的是副本值或者指向原始值的地址,那么反射对象是不可修改的。

  操作普通类型:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var pi float64 = 3.1425926
	/*
		//出错原因:将pi传递给ValueOf,是传值,ValueOf接收到的是pi的副本
		//    所以不能修改pi的值
		v := reflect.ValueOf(pi)
		v.SetFloat(3.14)
	*/

	/*
		//出错原因:虽然传递的是pi的地址,但是进行修改的时候,修改的却是pi的地址,不是pi的地址执行的内容
		v := reflect.ValueOf(&pi)
		v.SetFloat(3.14)
	*/

	//正确   Elem()相当于解引用
	v := reflect.ValueOf(&pi).Elem()
	v.SetFloat(3.14)
	fmt.Println(v)  //3.14
	fmt.Println(pi)	//3.14
}

  

遍历、修改结构体属性信息

//package main

//import (
//	"fmt"
//	"reflect"
//	"strconv"
//)

//type Person struct {
//	Name string
//	Age  int
//}

////注意这里是传的指针类型
////注意方法名开头是小写
//func (p *Person) setName(name string) {
//	p.Name = name
//}

////注意这里是传的指针类型
////注意方法名首字母大写
//func (p *Person) SetAge(age int) {
//	p.Age = age
//}

////传值形式
////注意方法名首字母小写
//func (p Person) saySelf() string {
//	return "My Name:" + p.Name + ", My Age:" + strconv.Itoa(p.Age)
//}

////传值形式
////注意方法名首字母大写
//func (p Person) ToString() string {
//	return "Name:" + p.Name + ", Age:" + strconv.Itoa(p.Age)
//}

//func main() {
//	//注意这里的p是Person对象的值
//	p := &Person{"John", 99}

//	//注意
//	v := reflect.ValueOf(&p).Elem()
//	//方法数量
//	count := v.NumMethod()
//	for i := 0; i < count; i++ {
//		fmt.Println(v.Type().Method(i).Name, v.Method(i).Type())
//	}
//}

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name   string
	Age    int		//注意首字母大写
	gender string  //注意首字母小写
}

func main() {
	p := Person{"John", 99, "male"}

	//传递地址,然后解引用,此时v就是p对象本身
	v := reflect.ValueOf(&p).Elem()

	//p(v)的属性数量
	count := v.NumField()
	for i := 0; i < count; i++ {
		fmt.Println(v.Type().Field(i).Name) //属性名
		fmt.Println(v.Field(i).Type())      //属性类型
		fmt.Println(v.Field(i))             //属性值
	}
	//  打印结果:
	//  Name string John
	//  Age  int    99
	// gender string male

	//修改第一个属性的值
	v.Field(0).SetString("Jane")

	//修改Age属性的值
	v.FieldByName("Age").SetInt(88)
	fmt.Println(v) //{Jane 88 male}
	fmt.Println(p) //{Jane 88 male}
}

  注意:不管结构体的首字母是大写还是小写,通过这种方法都是可以遍历出出来。

 

遍历、调用结构体中的方法

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

type Person struct {
	Name string
	Age  int
}

//注意这里是传的指针类型
//注意方法名开头是小写
func (p *Person) setName(name string) {
	p.Name = name
}

//注意这里是传的指针类型
//注意方法名首字母大写
func (p *Person) SetAge(age int) {
	p.Age = age
}

//传值形式
//注意方法名首字母小写
func (p Person) saySelf() string {
	return "My Name:" + p.Name + ", My Age:" + strconv.Itoa(p.Age)
}

//传值形式
//注意方法名首字母大写
func (p Person) ToString() string {
	return "Name:" + p.Name + ", Age:" + strconv.Itoa(p.Age)
}

func main() {
	//注意这里的p是Person对象的值
	p := Person{"John", 99}

	//注意
	v := reflect.ValueOf(&p).Elem()
	//方法数量
	count := v.NumMethod()
	for i := 0; i < count; i++ {
		fmt.Println(v.Type().Method(i).Name, v.Method(i).Type())
	}
}

  上面程序运行结果是:ToString func() string

  没错,只有这一个方法被输出了,这里有两个注意点:

  1、方法首字母的大小写

  2、接受参数的类型

  3、在主函数中结构体对象是对象,还是对象指针

  对于第1个问题:只有首字母是大写的方法才是可导出的,对于成员属性也是一样的。

  对于第2个问题:这个问题要和第3个问题一起说,

  如果传递给ValueOf一个对象的地址,就可以将定义在结构体上的、接收者是一个传值的方法,并且首字母大写的方法遍历出来;

  如果传递给ValueOf一个对象的地址的地址,即取两次地址,就可以找到定义在结构体上的、接受者是一个对象指针的或者是一个传值的方法、并且首字母大写的方法。

  看下面的代码:

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

type Person struct {
	Name string
	Age  int
}

//注意这里是传的指针类型
//注意方法名开头是小写
func (p *Person) setName(name string) {
	p.Name = name
}

//注意这里是传的指针类型
//注意方法名首字母大写
func (p *Person) SetAge(age int) {
	p.Age = age
}

//传值形式
//注意方法名首字母小写
func (p Person) saySelf() string {
	return "My Name:" + p.Name + ", My Age:" + strconv.Itoa(p.Age)
}

//传值形式
//注意方法名首字母大写
func (p Person) ToString() string {
	return "Name:" + p.Name + ", Age:" + strconv.Itoa(p.Age)
}

func main() {
	//注意这里的p是Person对象的值
	p := &Person{"John", 99}

	//注意
	v := reflect.ValueOf(&p).Elem()
	//方法数量
	count := v.NumMethod()
	for i := 0; i < count; i++ {
		fmt.Println(v.Type().Method(i).Name, v.Method(i).Type())
	}
}

  这段代码和上面一段代码就只有第40行,多了一个取地址。

  运行结果:

SetAge func(int)

ToString func() string

没错,打印出了两个方法。

 

 利用反射调用结构体的方法

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

type Person struct {
	Name string
	Age  int
}

//注意这里是传的指针类型
//注意方法名开头是小写
func (p *Person) setName(name string) {
	p.Name = name
}

//注意这里是传的指针类型
//注意方法名首字母大写
func (p *Person) SetAge(age int) {
	p.Age = age
}

//传值形式
//注意方法名首字母小写
func (p Person) saySelf() string {
	return "My Name:" + p.Name + ", My Age:" + strconv.Itoa(p.Age)
}

//传值形式
//注意方法名首字母大写
func (p Person) ToString() string {
	return "Name:" + p.Name + ", Age:" + strconv.Itoa(p.Age)
}

func main() {
	//注意这里的p是Person对象的值
	p := &Person{"John", 99}

	//注意
	v := reflect.ValueOf(&p).Elem()
	//方法数量
	count := v.NumMethod()
	for i := 0; i < count; i++ {
		fmt.Println(v.Type().Method(i).Name, v.Method(i).Type())
	}
	//输出
	//SetAge func(int)
	//ToString func() string

	//调用结构体的方法
	params := make([]reflect.Value, 1)
	params[0] = reflect.ValueOf(50) 
	v.Method(0).Call(params) //调用SetAge方法
	fmt.Println(v) //&{John 50}
	fmt.Println(p) //&{John 50}

	res := v.MethodByName("ToString").Call(nil)
	fmt.Println(res[0]) //Name:John, Age:50
}

  

posted @ 2018-03-29 11:26  寻觅beyond  阅读(486)  评论(0编辑  收藏  举报
返回顶部