前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[笔记] Go 反射

[笔记] Go 反射

作者头像
柳公子
发布2021-03-27 16:46:07
3300
发布2021-03-27 16:46:07
举报
文章被收录于专栏:PhpZendo

Go 反射

类型和接口

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

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

代码语言:javascript
复制
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 类型:

代码语言:javascript
复制
// 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 类型的变量就能够存储这个值:

代码语言:javascript
复制
var r io.Reader

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

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

空接口 interface{}

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

接口的表示

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

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

例如,在执行完

代码语言:javascript
复制
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 方法的能力,但其内部却携带了有关该值的所有类型信息。 这就是我们可以写出这种代码的原因:

代码语言:javascript
复制
var w io.Writer
w = r.(io.Writer)

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

接着,我们可以这样做:

代码语言:javascript
复制
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 类型
代码语言:javascript
复制
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))// type: float64

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

代码语言:javascript
复制
// TypeOf 返回 interface{} 中的值的反射类型 Type。
func TypeOf(i interface{}) Type

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

代码语言:javascript
复制
var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x).String())// value: <float64 Value>

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

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

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

代码语言:javascript
复制
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) 的接口值。

代码语言:javascript
复制
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:通过反射对象的可设置性修改实际值

代码语言:javascript
复制
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 可设置呢?再来看看下面的示例:

代码语言:javascript
复制
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 的数据修改

总结

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

参考

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年3月24日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Go 反射
    • 类型和接口
      • 空接口 interface{}
    • 接口的表示
      • L1: 从接口值可反射出反射对象
        • L2: 从反射对象可反射出接口值
          • L3:通过反射对象的可设置性修改实际值
            • 总结
              • 参考
              相关产品与服务
              对象存储
              对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档