Go 学习笔记之 反射

184 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金

反射,Go 语言提供的一种机制,能够在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,而不需要在编译时就知道这些变量的具体类型。

为什么需要反射?

有时候我们需要编写一个函数能够处理一类并不满足普通公共接口的类型的值,也可能是因为它们并没有确定的表示方式,或者是在我们设计该函数的时候这些类型可能还不存在。

一个大家熟悉的例子是 fmt.Fprintf 函数提供的字符串格式化处理逻辑,它可以用来对任意类型的值格式化并打印,甚至支持用户自定义的类型。让我们也来尝试实现一个类似功能的函数。

为了简单起见,我们的函数只接收一个参数,然后返回和 fmt.Sprint 类似的格式化后的字符串。

我们实现的函数名也叫 Sprint。

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 "???"
    }
}

首先用 switch 类型分支来测试输入参数是否实现了 String 方法,如果是的话就调用该方法。然后继续增加类型测试分支,检查这个值的动态类型是否是 string、int、bool 等基础类型,并在每种情况下执行相应的格式化操作。

问题:

但是我们如何处理其它类似[]float64、map[string][]string 等类型呢?我们当然可以添加更多的测试分支,但是这些组合类型的数目基本是无穷的。

没有办法来检查未知类型的表示方式,这时我们就被卡住了。

反射的实现

反射是由 reflect 包提供的。它定义了两个重要的类型,Type 和 Value。一个 Type 表示一个 Go 类型。它是一个接口,有许多方法来区分类型以及检查它们的组成部分,例如一个结构体的成员或一个函数的参数等。唯一能反映 reflect.Type 实现的是接口的类型描述信息(§7.5),也正是这个实体标识了接口值的动态类型。

函数 reflect.TypeOf 接受任意的 interface{} 类型,并以 reflect.Type 形式返回其动态类型:

t := reflect.TypeOf(3)  // a reflect.Type
fmt.Println(t.String()) // "int"
fmt.Println(t)          // "int"

- END -

作者:架构精进之路,十年研发风雨路,大厂架构师,CSDN 博客专家,专注架构技术沉淀学习及分享,职业与认知升级,坚持分享接地气儿的干货文章,期待与你一起成长。
关注并私信我回复“01”,送你一份程序员成长进阶大礼包,欢迎勾搭。

Thanks for reading!