反射的三大laws,laws博主实在找不到什么好的翻译,定律?法律?规则?好像都不适用。就直接使用law吧。本篇是读go官方博客后,加入了自己的部分梳理。
0.前言
在讲清楚go语言中的反射是如何工作之前,需要先回顾一下go语言的类型,因为反射是建立在类型系统之上的,所以复习类型系统是很有必要的,让我们温故而知新。
1.静态类型
在go语言中,每个变量都有一个静态类型,这句话等价于,在编译时,只有一种类型是已知的,且固定。
type MyInt int //类型定义
var i int //int类型
var j MyInt //MyInt类型
尽管上面i与j有相同的底层类型,但是i, j是不同的静态类型,就不能相互赋值,除非转换。
2.接口类型
duck-type programming
3.空接口
代表方法集为空。任何值都满足空接口,因为任何值都有0-n个方法。有些人说,go的接口就是动态类型,这是误导。他们还是静态类型。接口类型的变量总是具有相同的静态类型,即使在运行的过程中,存储在接口变量中的值可能改变类型,但是值也总是满足接口。
以上知识,我们需要精确的了解,在脑海中形成惯性思维,因为反射和接口是密切相关的。
4.接口表示
接口类型的变量存储一对,着重理解这个对,后面我依然会提到对:
- 赋给该接口变量的具体值
- 以及该值的类型描述符
更准确地说,这个值是实现接口的底层具体数据项,而类型描述了底层数据项的完整类型。
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.Fileos.Fileio.Reader
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
var w io.Writer
w = r.(io.Writer) // 类型断言
w=r.(io.Writer)io.Writer
wr(tty, *os.File)
接口的静态类型决定了可以用接口变量调用哪些方法,即使里面的具体值可能有一组更大的方法也调不了。除非断言为其他接口。
继续看:
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
var w io.Writer
w = r.(io.Writer)
var empty interface{} //空接口
empty = w
empty(tty, *os.File)
这里我们不需要类型断言,因为静态地知道w满足空接口。在将一个值从Reader移到Writer的例子中,我们需要显式地使用类型断言,因为Writer的方法不是Reader的方法的子集。
- 换言之,如果某个方法集是另外一个方法集的子集,就不需要类型断言,空接口代表的方法集就是任何接口代表的方法集的子集
另外一个重要的细节是,接口内的对总是具有形式(值,具体类型),而不能是具有形式(值,接口类型)。接口不保存接口值。
5.反射的law
5.1 从接口值到反射对象
反射只是一种检查存储在接口变量中的类型与值对的机制。所以,一开始这里有两个reflect 包的概念需要了解:
- Type
- Value
这两种类型允许访问接口变量的内容:
reflect.TypeOf() // reflect.Type
reflect.ValueOf() // reflect.Value
看如下代码:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x)) //type: float64
}
这里的接口在哪里?因为程序看起来像是在传递float64变量x,而不是接口值。不妨跟踪到源码:
// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type
reflect.TypeOf(x)reflect.TypeOf
- 存储空接口
- 解压空接口
var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x).String()) //value: <float64 Value>
显式地调用String方法,因为默认情况下fmt包挖掘reflect. Value以显示其中的具体值。String方法不这样做。
reflect.Typereflect.Value
ValueTypereflect.ValueTypeTypeValue
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
type: float64
kind is float64: true
value: 3.4
反射库有几个值得注意的属性。首先,为了保持API的简单性,Value的“getter”和“setter”方法操作可以保存值的最大类型,这是什么意思?例如:所有有符号整数的int64。也就是说,Value的Int方法返回一个int64值,SetInt值接受一个int64值; 可能需要将其转换为所涉及的实际类型:
var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type()) // uint8.
fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
x = uint8(v.Uint()) // v.Uint returns a uint64.
第二个属性是反射对象的Kind描述基础类型,而不是静态类型。也就是说,kind是读不了定义类型的:
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
reflect.IntMyInt
5.2 从反射对象到接口值
就像物理现象中的反射一样,go的反射可以生成相反面。
reflect.ValueInterface
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}
y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)
fmt.Println, fmt.Printf
fmt.Println(v.Interface())
而言之,' Interface '方法是' ValueOf '函数的逆函数,只是它的结果总是静态类型' Interface {} ' .
5.3 如果要修改反射对象,该值必须是可设置的
看标题,这条law是最令人困惑的,但如果我们从最基本的原理开始,这很容易理解。
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
panic: reflect.Value.SetFloat using unaddressable value
7.1vValueValueValue
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())
settability of v: false
不能设置,而设置,肯定要报错,那么问题来了。什么是可设置性???
可设置性有点像可寻址,但是更严格。它是反射对象可以被修改并创建反射对象实际存储的属性。可设置性是由反射对象的原始项。
来继续看上面的代码:
var x float64 = 3.4
v := reflect.ValueOf(x)
reflect.ValueOfv.SetFloat(7.1)
f(x)fxxxfxxf(&x)
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())
type of p: *float64
settability of p: false
pp*pValuep
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
v := p.Elem() // save the result in a reflection `Value`
fmt.Println("settability of v:", v.CanSet())
v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)
这样v就是一个可设置的反射对象
settability of v: true
7.1
7.1
反射可能很难理解,但它的功能与语言完全一样,尽管它通过反射类型和值来掩盖所发生的事情。只要记住,反射值需要某个东西的地址,以便修改它们所表示的内容。
6.反射之结构体
结构体又有有一些特别的地方。
reflect. Value
type T struct {
A int
B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
typeOfT.Field(i).Name, f.Type(), f.Interface())
}
0: A int = 23
1: B string = skidoo
- 关于 可设置性 需要补充一点,就是结构体的字段名称是大写字母开头(导出),因为只有导出的结构体字段才是settable
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)
如果我们修改程序,使' s '是从' t '创建的,而不是' &t ',调用' SetInt '和' SetString '将失败,因为' t '的字段将不可设置。
7.结论
- 每个变量都有一个静态类型,也就是说,变量的类型是在编译时是已知固定的。
- 要区分底层类型与静态类型
- 无论接口变量的具体值(实现)是什么,接口变量的类型总是接口,因为go是静态类型,所以接口类型的变量总是具有相同的静态类型。
- 接口类型的变量:具体值+这个值的类型描述
- 反射三板斧:
- 从接口值到反射对象(解压空接口以获得接口值的信息)
- 从反射对象到接口值(从解压空接口获得的接口值信息再恢复为接口值)
- 修改反射对象的值,则该值必须为可设置的(settable)
参考链接
https://go.dev/blog/laws-of-reflection