Go语言核心知识回顾(反射)

2023-05-18,,

有时要求写一个函数有能力统一处理各种值类型的函数,而这些类型可能无法共享同一个接口,也可能布局未知,也有可能这个类型在设计函数时并不存在,当我们无法透视一个未知类型的布局时,这段代码就无法继续,这是就需要反射

reflect.Type & reflect.Value

反射功能由reflect包提供,其中定义了两个重要类型: Type 和 Value,Type表示Go语言的一个类型,它是一个有很多方法的接口,这些方法可以用来识别类型以及透视类型的组成部分,比如一个结构的各个字段或者一个函数的各个参数

reflect.Type接口只有一个实现,即类型描述符,接口值中的动态类型也是类型描述符号

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

reflect.TypeOf函数接收任何的interface{}参数,并且将接口中的动态类型以reflect.Type形式返回,将一个具体值赋给一个接口类型时会发生一个隐式类型转换,转换会生成一个包含两部分内容的接口值: 动态类型部分是操作数类型(int),动态值部分是操作数值(3),并且TypeOf返回一个接口值对应的动态类型,返回总是具体类型

var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // *os.File

上述代码输出的是*os.File而不是io.Writer

reflect.Value可以包含一个任意类型的值,reflect.ValueOf函数接收任意的interface{}并将接口的动态值以reflect.Value的形式返回,返回值同样是具体值,不过包含一个接口值

v := reflect.ValueOf(3)
fmt.Println(v) // 3
fmt.Printf("%v\n", v) // 3
fmt.Println(v.String()) // <int value>

调用Value的Type方法会将其类型以reflect.Type方式返回

v := reflect.ValueOf(3)
t := v.Type()
fmt.Println(t.String()) // int

reflect.Value的逆操作是reflect.Value.Interface方法,返回一个interface{}接口值,与reflect.Value包含一个具体值

v := reflect.ValueOf(3)
x := v.Interface()
i := x.(int)
fmt.Printf("%d\n", i)

reflect.Value和interface{}都可以任意的值,二者的区别是空接口(interface{}),隐藏了值的布局信息、内置操作和相关方法,所以除非知道其动态类型,并用一个类型断言来渗透进去,否则对所包含值能做的事情很少,Value有很多方法可以用来分析所包含的值,而不用知道它的类型,基于此可以尝试写一个通用的格式化函数:

package format

import (
"reflect"
"strconv"
) func Any(value interface{}) string {
return formatAtom(reflect.ValueOf(value))
} func formatAtom(v reflect.Value) string {
switch v.Kind() {
case reflect.Invalid:
return "invalid"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.Int(), 8)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(v.Uint(), 10)
case reflect.Bool:
return strconv.FormatBool(v.Bool())
case reflect.String:
return strconv.Quote(v.String())
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
return v.Type().String() + "0x" + strconv.FormatUint(uint64(v.Pointer()), 16)
default:
return v.Type().String() + " value"
}
}

调用:

func main() {
var x int64 = 1
var d time.Duration = 1 * time.Nanosecond
fmt.Println(format.Any(x))
fmt.Println(format.Any(d))
fmt.Println(format.Any([]int64{x}))
fmt.Println(format.Any([]time.Duration{d}))
}

输出:

1
1
[]int640xc0000200f8
[]time.Duration0xc000020110

值显示

实现一个函数Display,对于给定的任意一个复杂值x,输出这个复杂值的完整结构,并对找到的每个元素标上这个元素的路径

func Display(name string, x interface{}) {
fmt.Printf("Display %s (%T):\n", name, x)
display(name, reflect.ValueOf(x))
} func display(path string, v reflect.Value) {
switch v.Kind() {
case reflect.Invalid:
fmt.Printf("%s = invalid\n", path)
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
}
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
fieldPath := fmt.Sprintf("%s %s", path, v.Type().Field(i).Name)
display(fieldPath, v.Field(i))
}
case reflect.Map:
for _, key := range v.MapKeys() {
display(fmt.Sprintf("%s[%s]", path, formatAtom(key)), v.MapIndex(key))
}
case reflect.Ptr:
if v.IsNil() {
fmt.Printf("%s = nil\n", path)
} else {
display(fmt.Sprintf("(*%s)", path), v.Elem())
}
case reflect.Interface:
if v.IsNil() {
fmt.Printf("%s = nil\n", path)
} else {
fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
display(path+".value", v.Elem())
}
default:
fmt.Printf("%s = %s\n", path, formatAtom(v))
}
}

在上述display函数中,我们使用之前定义的formatAtom函数来输出基础值(基础类型、函数和通道),使用reflect.Value的一些方法来递归展示复杂类型的每个组成部分,当递归深入时,path字符串会增长,以表示如何找到当前值

其中Len方法会返回slice或者数组中元素个数,NumField可以报告结果中的字段数,Field(i)会返回第i个字段,返回字段类型为reflect.Value,MapKeys方法返回一个元素类型为reflect.Value的slice, 每个元素都是一个map的键,Elem方法返回指针指向的变量,这个方法在指针是nil时也能正确处理,返回的结果属于Invalid类型,所以用IsNil来显式检测

测试:

func main() {
strangelove := Movie{
Title: "Dr.Strangelove",
Subtitle: "How I learned to Stop Worring and Love the Bomb",
Year: 1964,
Color: false,
Actor: map[string]string{
"Dr.Strangelove": "Peter Sellers",
"Grp.Capt. Lionel Mandrake": "Peter Sellers",
"Gen. Buck Turgidson": "George C.Scott",
},
Oscars: []string{
"Best Actor (Nomin.)",
"Best Adapted Screenplay (Nomin.)",
},
}
format.Display("strangelove", strangelove)
}

输出:

Display strangelove (main.Movie):
strangelove Title = "Dr.Strangelove"
strangelove Subtitle = "How I learned to Stop Worring and Love the Bomb"
strangelove Year = 3654
strangelove Color = false
strangelove Actor["Dr.Strangelove"] = "Peter Sellers"
strangelove Actor["Grp.Capt. Lionel Mandrake"] = "Peter Sellers"
strangelove Actor["Gen. Buck Turgidson"] = "George C.Scott"
strangelove Oscars[0] = "Best Actor (Nomin.)"
strangelove Oscars[1] = "Best Adapted Screenplay (Nomin.)"
strangelove Sequel = nil

设置值

一个变量是一个可寻址的存储区域,其中包含了一个值,并且其值可以通过这个地址进行更新

x := 2
a := reflect.ValueOf(2)
b := reflect.ValueOf(x)
c := reflect.ValueOf(&x)
d := c.Elem()

reflect.ValueOf返回的reflect.Value都是不可寻址的,但d是通过c的指针得来的,所以是可寻址的,通过如下方法来询问是否可寻址

fmt.Println(a.CanAddr(), b.CanAddr(), c.CanAddr(), d.CanAddr()) // false false false true

可直接通过reflect.Value来更新变量,无须通过指针,而是reflect.Set方法

x := 2
d := reflect.ValueOf(&x).Elem()
px := d.Addr().Interface().(*int)
*px = 3
fmt.Println(x) // 3
d.Set(reflect.ValueOf(4))
fmt.Println(x) // 4

Go语言核心知识回顾(反射)的相关教程结束。

《Go语言核心知识回顾(反射).doc》

下载本文的Word格式文档,以方便收藏与打印。