Go反射
reflect.Type & reflect.Value
反射功能由 reflect 包提供,它定义了两个重要的类型:Type 和 Value。
-
reflect.Type
reflect.TypeOf 函数接收任何 interface{} 参数,并把接口中的动态类型以 reflect.Type 形式返回。它所返回的是具体类型(而不是接口类型)。
var w io.Writer = os.Stdout fmt.Println(reflect.TypeOf(w)) // "*os.File"
reflect.Type 满足 fmt.Stringer。
fmt.Printf 提供了一个简写方式 %T,内部实现就是用的 reflect.TypeOf
fmt.Printf("%T", 3) // "int"
-
reflect.Value
reflect.Value 可以包含一个任意类型的值
reflect.ValueOf 函数接收任何 interface{} 参数,并把接口中的动态值以 reflect.Type 形式返回。它所返回的是具体值(reflect.Value 也可以包含一个接口值)
v := reflect.ValueOf(3) fmt.Println(v) // "3" fmt.Println(v.String()) // "<int Value>"
reflect.Value 满足 fmt.Stringer。
fmt.Printf 提供了一个简写方式 %v
¶reflect.Value 和 interface{} 区别
二者都可以包含任何值。
-
interface{} 隐藏了值的布局信息、内置操作、相关方法,除非我们知道它的动态类型并且用一个类型断言渗透进去,否则我们对所包含值能做的事情很少
-
value 有很多方法可以用来分析所包含的值,而不用知道它的类型。
我们可以使用 reflect.Value 的 Kind 方法来区分不同的类型,类型的分类只有少数几种:
-
基础类型:
Bool, String, 各种数字类型(int, uint, float, complex…)
-
聚合类型:
Array, Struct
-
引用类型:
Chan, Func, Ptr, Slice, Map, 接口类型 interface
-
Invalid 类型,表示它们还没有任何值。(reflect.Value 的零值就是 Invalid)
-
reflect.Value 的相关方法
实现一个 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))
}
}
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(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
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:
// Array, Struct, Interface
return v.Type().String() + " value"
}
}
设 var v reflect.Value,由上述示例代码知,
对于 slice 和数组:
- v.Len() 方法会返回它们的元素个数,类似于内置的 len(arr) 置的 a[i]
对于 struct :
- v.NumField() 可以报告结构体中字段的个数
- v.Field(i) 会返回第 i 个字段,返回字段类型为 reflect.Value
对于 map:
- v.MapKeys() 会返回一个元素类型为 reflect.Value 的 slice,其中每一个元素都是 map 的键。
- v.MapIndex(key) 会返回 key 对应的值,类型为 reflect.Value。
对于指针:
-
v.Elem() 方法返回指针指向的变量,返回值的类型同样是 reflect.Value。当指针类型为 nil 时返回值为 Invalid 类型
-
v.IsNil() 方法判断该指针是否为空值
-
**reflect包也提供了 reflect.Indirect(v) 方法,获取 v 所表示的实际对象。**当 v 为指针时等同于返回 v.Elem()
其代码如下:
func Indirect(v Value) Value { if v.Kind() != Ptr { return v } return v.Elem() }
对于接口:
- v.Elem() 方法返回动态值
- v.IsNil() 方法判断该指针是否为空值
reflect.Type 的相关方法
设 var t reflect.Type
-
t.Field(i) 方法返回结构体 t 的第 i 个字段,返回值类型为 StructField
-
t.FieldByName(str) 方法返回结构体 t 的名为 str 的字段,返回值类型为 StructField
-
t.NumField() 方法返回结构体 t 的字段个数
-
t.Elem() 方法返回该类型(必须为Array, Chan, Map, Ptr, Slice)的成员(element)的类型(reflect.Type), 举例:
package main func main(){ strs := make([]string, 0) addr := &strs elem := reflect.Indirect(reflect.ValueOf(addr)).Type().Elem() fmt.Println(elem) // string }
通过反射获取Tag
Tag 类似于 Java 中的注解,可在结构体定义中对字段进行标注。具体的语法为:
type 自定义结构体 struct{
字段1 类型1 `自定义的TagKey1:"TagValue1"`
字段2 类型2 `自定义的TagKey2:"TagValue2"`
}
下面为演示程序:
type User struct {
Name string `mytag:"name"`
Age int8 `mytag:"custom rule"`
}
func main() {
u := User{"X.FLY", 24}
v := reflect.ValueOf(u)
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i).Tag.Get("mytag")
fmt.Println(field)
lookup, _ := v.Type().Field(i).Tag.Lookup("mytag")
fmt.Println(lookup)
}
}
/**
输出结果为:
name
name
custom rule
custom rule
*/
-
我们由 reflect.Type 的 Field 相关方法 ( Field(index), FieldByName(name)等等 )可以或得到结构体的某一字段,类型为 reflect.StructField
-
设变量 var field reflect.StructField,我们可以直接访问它的可见字段Tag:field.Tag,其类型为 StructTag,类型具体定义为:
type StructTag string
-
StructTag 类型根据以下两种方法获取 key 为指定字符串的 tag 值:
func (tag StructTag) Get(key string) string { v, _ := tag.Lookup(key) return v } func (tag StructTag) Lookup(key string) (value string, ok bool) {...}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,邮件至 708801794@qq.com
文章标题:Go反射
文章字数:1.5k
本文作者:梅罢葛
发布时间:2020-11-21, 16:35:26
最后更新:2020-12-01, 21:42:27
原始链接:https://qiurungeng.github.io/2020/11/21/Go%E5%8F%8D%E5%B0%84/