反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。

支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

 

一、通过反射获取变量的值、变量的类型、变量的类别与将变量转换为interface类型

Go程序在运行期使用reflect包访问程序的反射信息,我们需要导入reflect包。

package main

import (
    "reflect"
    "fmt"
)

func main() {
	var x float64 = 3.4      // 定义float类型的变量

        // 1.通过 reflect.TypeOf() 反射获取变量的类型
	fmt.Println("type:", reflect.TypeOf(x))      // 结果:type: float64
 
        // 2.通过 reflect.ValueOf() 反射获取变量的值
	fmt.Println("value:", reflect.ValueOf(x))    // 结果:value: 3.4

        // 3.通过 reflect.ValueOf(x).Type() 反射已知变量x的值获取变量x的类型
	fmt.Println("type:", reflect.ValueOf(x).Type())     // 结果:type: float64

        // 4.通过 reflect.ValueOf(x).Kind() 反射已知变量x的值获取变量x的类别
	fmt.Println("kind:", reflect.ValueOf(x).Kind())     // 结果:kind: float64

        // 5.通过 reflect.ValueOf(x).Float() 反射已知变量x的值获取变量x的值(float64用Float64()方法获取值、string类型的变量用String()方法,如果int类型的变量用String()方法获取值会提示:<int Value>)
	fmt.Println("value:", reflect.ValueOf(x).Float())   // 结果:value: 3.4

        // 6.通过 reflect.ValueOf(x).Interface() 将已知变量x的类型转换为interface类型
	fmt.Println(reflect.ValueOf(x).Interface())      // 结果:3.4(interface类型)

        // 7.通过 reflect.ValueOf(x).Interface().(float64) 将interface类型的变量x转换为float64类型
	y := reflect.ValueOf(x).Interface().(float64)     // 转成float64类型
        fmt.Println(y)      // 结果:3.4

	
}

  

二、通过反射修改变量的值

package main

import (
    "reflect"
    "fmt"
)

func testInt(b interface{}) {
	val := reflect.ValueOf(b)     // 获取变量b的值(是个内存地址),结果为:0xc000010090
	val.Elem().SetInt(20)       //当传内存地址时,反射没有*val方法,反射提供了Elem()方法(效果和*val一样),通过.SetInt()设置值。
	// 反射传进来的是地址时都要用到.Elem()方法(int类型需要用到.Int()方法来获取变量的值,float64类型用.Float64()方法来获取变量的值)
	// 如果int类型用.Float64()来获取值时会出现提示:<int Value>
	c := val.Elem().Int()
	fmt.Printf("set data: %d\n", c)      // 结果:set data: 20
}

func main() {
	var b int = 12
	testInt(&b)    // 要传地址,传值会报错(传值反射只会修改变量的副本所以会报错)
	fmt.Println(b)   // 结果为:20
}

  

三、反射操作结构体

1、反射可以修改结构体的字段

2、反射可以执行结构体的方法

3、反射可以获取结构体的tag

package main

import (
    "reflect"
    "fmt"
)

// 定义结构体
type Student struct {
	Name  string `json:"name"`
	Age   int
	Score float32
	Sex   string
}

// 给Student结构体定义两个方法
func (self Student) Print() {
	fmt.Println("in Struct Print:", self)
}

func (self Student) Set(name string, age int, score float32, sex string) {
	self.Name = name
	self.Age = age
	self.Score = score
	self.Sex = sex
}

func testStruct(data *Student) {
	tp := reflect.TypeOf(data)               // 获取变量data的类型,结果为:*main.Student
	val := reflect.ValueOf(data)             // 获取变量data的值,结果为:&{zhangsan 18 88.3 男}    
	kd := val.Kind()      // 获取变量data类别,指针类型的类别为:ptr
	// 判断类别不是指针类型,并且指针的类别是struct,就抛出错误提示信息
	if kd != reflect.Ptr && val.Elem().Kind() == reflect.Struct { 
		fmt.Println("expect struct")
		return
	}
	num := val.Elem().NumField() // NumField() 获取结构体字段的数量
	// 循环打印结构体字段
	for i := 0; i < num; i++ {
		fmt.Printf("bumfield - %d, %v, %v\n", i, val.Elem().Field(i), val.Elem().Field(i).Kind())       // i是下标,val.Field(i)是下标对应字段的值,val.Field(i).Kind()是字段的类型
		/*
			结果:
				bumfield - 0, zhangsan, string
				bumfield - 1, 18, int
				bumfield - 2, 88.3, float32
				bumfield - 3, 男, string
		*/
	}
	// 修改结构体字段下标为0的值(指针类型都需要使用Elem()方法)
	val.Elem().Field(0).SetString("Dream")
	fmt.Println("修改后的name值:", val.Elem().Field(0))      // 结果为:修改后的name值: Dream

	// 反射获取结构体字段下表为0的tag(指针类型都需要使用Elem()方法)
	tagVal := tp.Elem().Field(0).Tag.Get("json")
	fmt.Println("Struct Name tag is:", tagVal)      // 结果为:Struct Name tag is: name

	method := val.Elem().NumMethod()             // NumMethod()  获取结构体方法的数量(指针类型都需要使用Elem()方法)
	fmt.Printf("struct has %d method\n", method)      // 结果:struct has 2 method

	val.Elem().Method(0).Call(nil) // Method(0).Call(nil):调用struct下标为0的方法(即print方法,需要传一个空值)
}

func main() {
        // 示例化一个Student结构体类型的变量a
	var a Student = Student{
		Name:  "zhangsan",
		Age:   18,
		Score: 88.3,
		Sex:   "男",
	}

	// 利用反射操作结构体(要传地址,传值反射无法操作到变量本身)
	testStruct(&a)
}