Go反射无敌了,彻底懂了

简介: Go反射无敌了,彻底懂了

今天应做的事没有做,明天再早也是耽误了。——裴斯泰洛齐


个人觉得,反射讲得最透彻的还是官方博客。官方博客略显晦涩,多读几遍就慢慢理解了。本文既是学习笔记,也是总结,如果想看官方文档,那么请移步到这里:点击这里


1 反射的概念



  • 反射提供一种让程序检查自身结构的能力
  • 反射是困惑的源泉


第1条,再精确点的描述是“反射是一种检查interface变量的底层类型和值的机制”。第2条,很有喜感的自嘲,不过往后看就笑不出来了,因为你很可能产生困惑.

想深入了解反射,必须深入理解类型和接口概念。下面开始复习一下这些基础概念。


1.1 关于静态类型


你肯定知道Go是静态类型语言,比如”int”、”float32”、”[]byte”等等。每个变量都有一个静态类型,且在编译时就确定了。那么考虑一下如下一种类型声明:


type Myint int
var i int
var j Myint

Q: i 和j 类型相同吗?A:i 和j类型是不同的。二者拥有不同的静态类型,没有类型转换的话是不可以互相赋值的,尽管二者底层类型是一样的。


1.2 特殊的静态类型interface


interface类型是一种特殊的类型,它代表方法集合。它可以存放任何实现了其方法的值。

经常被拿来举例的是io包里的这两个接口类型:


// 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)
}

任何类型,比如某struct,只要实现了其中的Read()方法就被认为是实现了Reader接口,只要实现了Write()方法,就被认为是实现了Writer接口,不过方法参数和返回值要跟接口声明的一致。


接口类型的变量可以存储任何实现该接口的值


1.3 特殊的interface类型


最特殊的interface类型为空interface类型,即interface {},前面说了,interface用来表示一组方法集合,所有实现该方法集合的类型都被认为是实现了该接口。那么空interface类型的方法集合为空,也就是说所有类型都可以认为是实现了该接口。


一个类型实现空interface并不重要,重要的是一个空interface类型变量可以存放所有值,记住是所有值,这才是最最重要的。这也是有些人认为Go是动态类型的原因,这是个错觉。


1.4 interface类型是如何表示的


前面讲了,interface类型的变量可以存放任何实现了该接口的值。还是以上面的io.Reader为例进行说明,io.Reader是一个接口类型,os.OpenFile()方法返回一个File结构体类型变量,该结构体类型实现了io.Reader的方法,那么io.Reader类型变量就可以用来接收该返回值。如下所示:


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


那么问题来了。Q:r的类型是什么?A: r的类型始终是io.Readerinterface类型,无论其存储什么值。


Q:那File类型体现在哪里?A:r保存了一个(value, type)对来表示其所存储值的信息。value即为r所持有元素的值,type即为所持有元素的底层类型

Q:如何将r转换成另一个类型结构体变量?比如转换成io.WriterA:使用类型断言,如w = r.(io.Writer). 意思是如果r所持有的元素如果同样实现了io.Writer接口,那么就把值传递给w。


2 反射三定律



前面之所以讲类型,是为了引出interface,之所以讲interface是想说interface类型有个(value,type)对,而反射就是检查interface的这个(value, type)对的。具体一点说就是Go提供一组方法提取interface的value,提供另一组方法提取interface的type.


官方提供了三条定律来说明反射,比较清晰,下面也按照这三定律来总结。


反射包里有两个接口类型要先了解一下.

  • reflect.Type 提供一组接口处理interface的类型,即(value, type)中的type
  • reflect.Value提供一组接口处理interface的值,即(value, type)中的value


下面会提到反射对象,所谓反射对象即反射包里提供的两种类型的对象。

  • reflect.Type 类型对象
  • reflect.Value类型对象


2.1 反射第一定律:反射可以将interface类型变量转换成反射对象


下面示例,看看是如何通过反射获取一个变量的值和类型的:


package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    t := reflect.TypeOf(x)  //t is reflext.Type
    fmt.Println("type:", t)
    v := reflect.ValueOf(x) //v is reflext.Value
    fmt.Println("value:", v)
}

程序输出如下:



type: float64
value: 3.4


注意:反射是针对interface类型变量的,其中TypeOf()和ValueOf()接受的参数都是interface{}类型的,也即x值是被转成了interface传入的。


除了reflect.TypeOf()和reflect.ValueOf(),还有其他很多方法可以操作,本文先不过多介绍,否则一不小心会会引起困惑。


2.2 反射第二定律:反射可以将反射对象还原成interface对象


之所以叫’反射’,反射对象与interface对象是可以互相转化的。看以下例子:


package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x) //v is reflext.Value
    var y float64 = v.Interface().(float64)
    fmt.Println("value:", y)
}

对象x转换成反射对象v,v又通过Interface()接口转换成interface对象,interface对象通过.(float64)类型断言获取float64类型的值。


2.3 反射第三定律:反射对象可修改,value值必须是可设置的


通过反射可以将interface类型变量转换成反射对象,可以使用该反射对象设置其持有的值。在介绍何谓反射对象可修改前,先看一下失败的例子


package main
import (
    "reflect"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x)
    v.SetFloat(7.1) // Error: will panic.
}

如下代码,通过反射对象v设置新值,会出现panic。报错如下:


panic: reflect: reflect.Value.SetFloat using unaddressable value

反射对象是否可修改取决于其所存储的值,回想一下函数传参时是传值还是传址就不难理解上例中为何失败了。


上例中,传入reflect.ValueOf()函数的其实是x的值,而非x本身。即通过v修改其值是无法影响x的,也即是无效的修改,所以golang会报错。

想到此处,即可明白,如果构建v时使用x的地址就可实现修改了,但此时v代表的是指针地址,我们要设置的是指针所指向的内容,也即我们想要修改的是*v。那怎么通过v修改x的值呢?


reflect.Value提供了Elem()方法,可以获得指针向指向的value。看如下代码:


package main
import (
"reflect"
    "fmt"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(&x)
    v.Elem().SetFloat(7.1)
    fmt.Println("x :", v.Elem().Interface())
}

输出为:


x : 7.1


3 总结



结合官方博客及本文,至少可以对反射理解个大概,还有很多方法没有涉及。对反射的深入理解,个人觉得还需要继续看的内容:

  • 参考业界,尤其是开源框架中是如何使用反射的
  • 研究反射实现原理,探究其性能优化的手段


4 关注公众号



微信公众号:堆栈future

相关文章
|
28天前
|
Go
Go to Learn Go之反射
Go to Learn Go之反射
37 8
|
2月前
|
JSON 人工智能 Go
go 反射的常见用法
go 反射的常见用法
35 4
|
3月前
|
安全 算法 程序员
在go语言中使用泛型和反射
【7月更文挑战第8天】本文介绍go支持泛型后,提升了代码复用,如操作切片、映射、通道的函数,以及自定义数据结构。 泛型适用于通用数据结构和函数,减少接口使用和类型断言。
122 1
在go语言中使用泛型和反射
|
2月前
|
存储 缓存 人工智能
深入理解 go reflect - 反射为什么慢
深入理解 go reflect - 反射为什么慢
23 0
|
2月前
|
存储 人工智能 JSON
深入理解 go reflect - 反射基本原理
深入理解 go reflect - 反射基本原理
49 0
|
2月前
|
JavaScript 前端开发 Go
Go中使用反射的动态方法调用
Go中使用反射的动态方法调用
|
4月前
|
Go
go反射获取变量类型、值、结构体成员、结构体方法
go反射获取变量类型、值、结构体成员、结构体方法
|
5月前
|
JSON 监控 安全
Golang深入浅出之-Go语言中的反射(reflect):原理与实战应用
【5月更文挑战第1天】Go语言的反射允许运行时检查和修改结构,主要通过`reflect`包的`Type`和`Value`实现。然而,滥用反射可能导致代码复杂和性能下降。要安全使用,应注意避免过度使用,始终进行类型检查,并尊重封装。反射的应用包括动态接口实现、JSON序列化和元编程。理解反射原理并谨慎使用是关键,应尽量保持代码静态类型。
83 2
|
5月前
|
Go
go 反射Reflect
go 反射Reflect
|
5月前
|
Go 数据处理
【Go 语言专栏】Go 语言的反射机制及其应用
【4月更文挑战第30天】Go语言的反射机制通过`reflect`包实现,允许运行时检查和操作类型信息。核心概念包括`reflect.Type`(表示类型)和`reflect.Value`(表示值)。主要操作包括获取类型信息、字段信息及动态调用方法。反射适用于通用数据处理、序列化、动态配置和代码生成等场景,但也带来性能开销和维护难度,使用时需谨慎。通过实例展示了如何使用反射处理不同类型数据,强调了在理解和应用反射时需要不断实践。
45 0