[笔记] Go 反射

Go 反射

类型和接口

GO 反射建立在类型系统,Go 是静态类型语言,变量在编译时即可确定其所属的静态类型。

对底层类型相同但静态类型不同的变量,在未做类型转换时,无法相互赋值。举例:

type MyInt int

var i int = 8
var j MyInt = 8

j = i // cannot use i (type int) as type MyInt in assignment

i、j 分别对应 int 和 MyInt 静态类型,它们底层数据类型都是 int,但无法相互赋值。

接口类型,是一种重要的类型,表示一个确定的方法集。只要某个具体值(非接口)实现了某个接口中的方法,那么该接口类型的变量就可以存储这个具体值。

一个众所周知的例子就是 io.Reader 和 io.Writer,即 io 包中的 Reader 和 Writer 类型:

// Reader is the interface that wraps the basic Read method.
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Writer is the interface that wraps the basic Write method.
type Writer interface {
    Write(p []byte) (n int, err error)
}

任何实现了 Read(或 Write)方法及其签名的类型,也就实现了 io.Reader(或 io.Writer)接口,换句话说,某个值的类型拥有 Read 方法,io.Reader 类型的变量就能够存储这个值:

var r io.Reader

r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// 等等

以上,r 无论存储何值,r 的类型总是 io.Reader,即 r 的静态类型为 io.Reader

空接口 interface{}

由于任何值都有 0 至多个方法,因此 空接口 能存储 任何值

接口的表示

接口类型的变量存储了一对内容:

  • 值:赋予该变量的具体值(接口的值是实现了该接口的底层具体数据条目)
  • 类型:该值的类型描述符(类型则描述了该条目的完整类型)

例如,在执行完

var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty

之后,r 包含的 (值, 类型) 对可以形式化地描述为(tty*os.File

注意,类型 *os.File 还实现了除 Read 以外的其它方法:尽管该接口值只提供了访问 Read 方法的能力,但其内部却携带了有关该值的所有类型信息。 这就是我们可以写出这种代码的原因:

var w io.Writer
w = r.(io.Writer)

通过类型断言:断言 r 内的条目是否实现了 io.Writer,因此我们可以将它赋予 w 。赋值后,w 将会包含一对 ( tty , *os.File )。这与保存在 r 中的一致。 接口的静态类型决定了哪些方法可通过接口变量调用, 即便其内部的具体值可能有更大的方法集。

接着,我们可以这样做:

var empty interface{}
empty = w

空接口值 e 也将再次包含同样的一对 ( tty, *os.File )。 这很方便:空接口可保存任何值,同时包含关于该值的所有信息。

(在这里我们无需类型断言,因为 w 肯定是满足空接口的。在本例中, 我们将一个值从 Reader 变成了 Writer ,由于 Writer 的方法集并非 Reader 方法集的子集,因此我们必须显式地使用类型断言。)

一个很重要的细节,就是接口内部的对总是 (值, 具体类型) 的形式,而不会是 (值, 接口类型) 的形式。接口不能保存接口值。

L1: 从接口值可反射出反射对象

反射只是一种检查存储在接口变量中的 (类型-值) 对的机制。

通过,reflect 包下述两个类型,可以访问接口值的类型、值的具体内容:

  • Type 类型,函数 reflect.TypeOf 获取接口值的 reflect.Type 类型
  • Value 类型,函数 reflect.ValueOf 获取接口值的 reflect.Value 类型
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))// type: float64

此处 x 为 float64 类型,并非 接口值,是如何从接口值反射出 x 类型信息的?这是由于包 reflect.TypeOf 函数签名包含空接口

// TypeOf 返回 interface{} 中的值的反射类型 Type。
func TypeOf(i interface{}) Type

在传入 x 时,x 作为实参存储到形参的空接口,reflect.TypeOf 通过解包该空接口还原其类型信息。同理 reflect.ValueOf 也能还原 x 的值:

var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x).String())// value: <float64 Value>

调用 String() 方法的原因是 fmt 包默认会调用 reflect.ValueOf 还原其具体值。

L2: 从反射对象可反射出接口值

通过 reflect.Interface 方法可还原 reflect.Value 类型的接口值。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.4
    p := reflect.ValueOf(x)

    fmt.Println("p:", p)// p: 3.4
    fmt.Println("p[interface]:", p.Interface())//p[interface]: 3.4
}

虽然打印结果上看:fmt.Println("p:", p)fmt.Println("p[interface]:", p.Interface()) 都是打印出 3.4。

但是 p 是 Value 类型数据,会被 fmt 进行内部解包,输出值 3.4,而 p.Interface() 则是获取 p (Value) 的接口值。

fmt.Println("p'type:", reflect.TypeOf(p))                        // p'type: reflect.Value
fmt.Println("p[interface]'type:", reflect.TypeOf(p.Interface())) // p[interface]'type: float64

以上,简单来说 Interface 方法是 ValueOf 的你操作。

L3:通过反射对象的可设置性修改实际值

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // panic: reflect: reflect.Value.SetFloat using unaddressable value
fmt.Println("settability of v:", v.CanSet())// settability of v: false

可设置性 是反射值 Value 的一种属性,并非所有的反射值都拥有它,可以通过 CanSet 方法检测反射值是否有可设置性。

上述实例中通过 v.SetFloat 报错的原因是:v 是 x 的反射对象 Value 而非 x 本身;另外,x 作为实参传入 ValueOf 会出会创建空接口参数作为 x 的副本,因而即使能够设置新值,也不会更新 x 值本身。

如何使反射值 v 可设置呢?再来看看下面的示例:

var x float64 = 3.4
p := reflect.ValueOf(&x) // 注意:获取 x 的地址
fmt.Println("type of p:", p.Type())// type of p: *float64
fmt.Println("settability of p:", p.CanSet())//settability of p: false

v := p.Elem()
fmt.Println("settability of v:", v.CanSet())// settability of v: true

v.SetFloat(7.1)
fmt.Println(v.Interface())// 7.1
fmt.Println(x)// 7.1
  • 以引用传值 &x 的方式获取 x 的反射值对象 p,这里得到 *float64 指针类型
  • 方法 Elme 获取指针 p 所指向的实际值,实际值 v.CanSet 才是可设置的
  • 最后设置实际值 v.SetFloat(7.1),实现 v 和 x 的数据修改

总结

  • 从接口值可反射出反射对象
  • 从反射对象可反射出接口值
  • 要修改反射对象,其值必须可设置

参考

发帖时间: go

发表评论

电子邮件地址不会被公开。 必填项已用*标注