go语言圣经第十二章(读书笔记)

第十二章 反射

为何需要反射

  • switch类型分支虽然可以识别部分类型,但对于array,chan,map,pointer,slice,struct等就没办法了
func Sprint(x interface{}) string {
    type stringer interface {
        String() string
    }
    switch x := x.(type) {
        case stringer:
            return x.String()
        case string:
            return x
        case int:
            return strconv.Itoa(x)
            // ...similar cases for int16, uint32, and so on...
        case bool:
            if x {
                return "true"
            }
            return "false"
        default:
            // array, chan, func, map, pointer, slice, struct
            return "???"
    }
}

reflect.Type和reflect.Value

  • 反射由reflect包支持,它定义了两个重要的类型:Type和Value
  • reflect.Type表示一个Go类型,他是一个接口
  • reflect.Value可以持有一个任意类型的值
  • 两者都满足fmt.Stringer接口
  • 反映reflect.Type实现的是接口的类型描述信息

  • reflect.TypeOf接受任意的interface{}类型,并返回对应动态类型的reflect.Type
  • reflect.ValueOf接受任意的interface{}类型,并返回对应动态值reflect.Value
t := reflect.TypeOf(3) // a reflect.Type
fmt.Println(t.String()) // "int"
fmt.Println(t) // "int"

v := reflect.ValueOf(3) // a reflect.Value
fmt.Println(v) // "3"
fmt.Printf("%v\n", v) // "3"
fmt.Println(v.String()) // NOTE: "<int Value>"
  • 调用 Value 的 Type 方法将返回具体类型所对应的 reflect.Type:
t := v.Type() // a reflect.Type
fmt.Println(t.String()) // "int"
  • 逆操作是调用 reflect.ValueOf 对应的reflect.Value.Interface 方法. 它返回一个 interface{} 类型表示 reflect.Value 对应类型的具体值:
v := reflect.ValueOf(3) // a reflect.Value
x := v.Interface() // an interface{}
i := x.(int) // an int
fmt.Printf("%d\n", i) // "3"

通过reflect.Value修改值

  • 有一些relfect.Values是可取地址的,有一些则不能:
x := 2                   // value type variable?
a := reflect.ValueOf(2)  // 2  int no
b := reflect.ValueOf(x)  // 2  int no
c := reflect.ValueOf(&x) // &x *int no
d := c.Elem()            // 2  int  yes (x)
  • 实际上,所有通过reflect.ValueOf(x)返回的reflect.Value都是不可取地址的
  • 我们可以通过调用reflect.ValueOf(&x).Elem(),来获取任意变量x对应的可取地址的Value。
  • 可以通过调用reflect.Value的CanAddr方法来判断其是否可以被取地址
fmt.Println(a.CanAddr()) // "false"
fmt.Println(b.CanAddr()) // "false"
fmt.Println(c.CanAddr()) // "false"
fmt.Println(d.CanAddr()) // "true"
  • 要从变量对应的可取地址的reflect.Value来访问变量需要三个步骤。第一步是调用Addr()方法,它返回一个Value,里面保存了指向变量的指针。然后是在Value上调用Interface()方法,也就是返回一个interface{},里面通用包含指向变量的指针。最后,如果我们知道变量的类型,我们可以使用类型的断言机制将得到的interface{}类型的接口强制环为普通的类型指针。这样我们就可以通过这个普通指针来更新变量了
x := 2
d := reflect.ValueOf(&x).Elem()
// d refers to the variable x
px := d.Addr().Interface().(*int) // px := &x
*px = 3 // x = 3
fmt.Println(x) // "3"
  • 或者,不使用指针,而是通过调用可取地址reflect.Value的reflect.Value.Set方法来更新对于的值
d.Set(reflect.ValueOf(4))
fmt.Println(x) // "4"

获取结构体字段标识

  • reflect.Type的Field方法将返回一个reflect.StructField,里面含有每个成员的名字、类型和可选的成员标签等信息。其中成员标签信息对应reflect.StructTag类型的字符串,并且提供了Get方法用于解析和根据特定key提取的子串,例如这里的http:"..."形式的子串。

显示一个类型的方法集

  • 使用reflect.Type来打印任意值的类型和枚举它的方法
  • reflect.Type和reflect.Value都提供了一个Method方法。每次t.Method(i)调用将一个reflect.Method的实例,对应一个用于描述一个方法的名称和类型的结构体
  • 使用reflect.Value.Call方法(我们之类没有演示),将可以调用一个Func类型的Value,但是这个例子中只用到了它的类型

猜你喜欢

转载自www.cnblogs.com/laiyuanjing/p/11253607.html