Go语言 8 反射

文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/

Go学习群:415660935

8.1概念和作用

Reflection(反射)在计算机中表示程序能够检查自身结构的能力,它是元编程的一种形式。通过反射,可以获取丰富的类型信息,并可以利用这些类型信息做非常灵活的工作。

通过反射机制在运行时能够完成如下功能:

1.确认对象的类

2.确认对象的类型的所有成员变量和方法

3.动态调用对象的方法

8.2 基本用法

本质上来说,反射就是一种检查接口变量的类型和值的机制。在Go语言中使用反射,首先要了解两个基本类型—reflect.Typereflect.Value。对任何变量进行反射,都可以得到一个包含TypeValue的信息结构。顾名思义,Type主要表达的是被反射的这个变量本身的类型信息,而Value则为该变量的值。

1. 反射第一定律:反射可以将“接口类型对象”转换为“反射类型对象”

 

以上两个函数的参数都是接口类型,可以接收任何类型的变量。所以,称为接口类型变量。返回值是TypeValue,这两个类型称为反射类型,所以通过这两个函数可以将“接口类型变量”即参数转换为反射类型对象即返回值。

2 反射第二定律:反射可以将“反射类型对象”转换为“接口类型对象”

 

根据一个 reflect.Value 类型的变量,我们可以使用 Interface( )方法恢复其接口类型的值。事实上,这个方法会把 type value 信息打包并填充到一个接口变量中,然后返回。

这两个定律互为逆操作。案例如下:

// reflect.go

package main

 

import (

    "fmt"

    "reflect"

)

 

func main() {

    // 1 根据接口类型的变量获取反射类型对象

    var pi float64 = 3.14

    t := reflect.TypeOf(pi)

    v := reflect.ValueOf(pi)

    fmt.Println(t, v)

    // 2 根据反射类型对象获取接口类型变量

    pi = v.Interface().(float64)

    fmt.Println(pi)

    

}

3 反射第三定律:如果要修改“反射类型对象”,其值必须是“可写的”

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

 

package main

 

import (

    "fmt"

    "reflect"

)

func main() {

    var num int = 10

    fmt.Println(num)

    /*

        // 3.1

        v := reflect.ValueOf(num)

        // 将num作为参数传递给ValueOf后,执行SetInt函数,num的值没有改变

        v.SetInt(100)

    */

    /*

        // 3.2 将num作为参数传递给ValueOf后,执行SetInt函数,修改的是num的地址,不是num的值

        v := reflect.ValueOf(&num)

        v.SetInt(100)

    */

    // 3.3 调用Elem(),相等于解引用 *(&num)

    v := reflect.ValueOf(&num).Elem()

    v.SetInt(100)

    fmt.Println(num)

 

}

4 对结构体的反射操作

4.1 遍历结构体的成员并修改成员的值

package main

 

import (

    "fmt"

    "reflect"

)

 

type Person struct {

    Name string

    Age  int

}

 

func main() {

 

    // 1 p1是对象

p1 := Person{"李四", 20}

v := reflect.ValueOf(&p1).Elem()

 

        // 1 p1是对象的地址

    //p1 := &Person{"李四", 20}

    //v := reflect.ValueOf(p1).Elem()

 

    num := v.NumField()

    for i := 0; i < num; i++ {

        fmt.Println(v.Type().Field(i).Name,

            v.Field(i).Type(), v.Field(i).Interface())

    }

    //修改结构体中字段的值

    v.Field(0).SetString("王五")

    v.Field(1).SetInt(30)

    fmt.Println(p1)

}

4.2 遍历结构体的方法并调用方法

// reflect.go

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) ToString() string {

    return "Name:" + p.Name + "Age:" + strconv.Itoa(p.Age)

}

 

func main() {

 

    // 5 打印和调用结构体的方法

p2 := &Person{"李四", 20}

// 方法的接收器是指针,如果方法的接收器是对象不需要取地址

    pv := reflect.ValueOf(&p2).Elem()

    num := pv.NumMethod()

    fmt.Println(num)

    for i := 0; i < num; i++ {

        fmt.Println(pv.Type().Method(i).Type) //方法类型

        fmt.Println(pv.Type().Method(i).Name) //方法名称

    }

    params := make([]reflect.Value, 1)

    params[0] = reflect.ValueOf(35)

    pv.Method(0).Call(params)

    params[0] = reflect.ValueOf("张飞")

    pv.Method(1).Call(params)

    info := pv.MethodByName("ToString").Call(nil)

    fmt.Println(info[0])

 

}

疑问:反射结构体的方法时,采用结构体指针变量的地址作为ValueOf参数可以遍历到接收器为变量的指针的方法和为变量本身的方法。但是使用采用结构体指针变量作为ValueOf参数只能遍历到接收器为变量本身的方法。

5反射的综合例子:

// reflect.go

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) ToString() string {

    return "Name:" + p.Name + "Age:" + strconv.Itoa(p.Age)

}

 

// 1 根据接口类型的对象获取反射类型对象

func PrintInfo(object interface{}) (v reflect.Value) {

    t := reflect.TypeOf(object)

    v = reflect.ValueOf(object)

    fmt.Println(t, v)

    return v

}

 

// 2 根据反射类型对象获取接口类型对象

func ValueToObject(v reflect.Value) interface{} {

    return v.Interface()

 

}

func main() {

 

    // 1 根据接口类型对象获取反射类型对象

    var pi float64 = 3.14

    v1 := PrintInfo(pi)

    var p1 = Person{"张三", 18}

    v2 := PrintInfo(p1)

    fmt.Printf("%p ", &p1)

 

    // 2 根据反射类型对象Value获取接口类型对象

    pi = ValueToObject(v1).(float64)

    fmt.Println(pi)

    p1 = ValueToObject(v2).(Person)

    fmt.Println(p1)

    p1.ToString()

    fmt.Printf("%p ", &p1)

 

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

    var num int = 10

    fmt.Println(num)

    v := reflect.ValueOf(&num).Elem()

    v.SetInt(100)

    fmt.Println(num)

 

    // 4 打印和修改结构体中字段的信息

    p2 := &Person{"李四", 20}

    v = reflect.ValueOf(p2).Elem()

    num = v.NumField()

    for i := 0; i < num; i++ {

        fmt.Println(v.Type().Field(i).Name,

            v.Field(i).Type(), v.Field(i).Interface())

    }

    //修改结构体中字段的值

    v.Field(0).SetString("王五")

    v.Field(1).SetInt(30)

    fmt.Println(*p2)

 

    // 5 打印和调用结构体的方法

    pv := reflect.ValueOf(&p2).Elem()

    num = pv.NumMethod()

    fmt.Println(num)

    for i := 0; i < num; i++ {

        fmt.Println(pv.Type().Method(i).Type) //方法类型

        fmt.Println(pv.Type().Method(i).Name) //方法名称

    }

    params := make([]reflect.Value, 1)

    params[0] = reflect.ValueOf(35)

    pv.Method(0).Call(params)

    params[0] = reflect.ValueOf("张飞")

    pv.Method(1).Call(params)

    info := pv.MethodByName("ToString").Call(nil)

    fmt.Println(info[0])

 

}

原文地址:https://www.cnblogs.com/mazg/p/8275204.html