Go反射

  1. reflect.Type & reflect.Value
    1. reflect.Value 和 interface{} 区别
  2. reflect.Value 的相关方法
  3. reflect.Type 的相关方法
  4. 通过反射获取Tag

reflect.Type & reflect.Value

反射功能由 reflect 包提供,它定义了两个重要的类型:Type 和 Value。

  1. 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"
    
  2. 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
*/
  1. 我们由 reflect.Type 的 Field 相关方法 ( Field(index), FieldByName(name)等等 )可以或得到结构体的某一字段,类型为 reflect.StructField

  2. 设变量 var field reflect.StructField,我们可以直接访问它的可见字段Tag:field.Tag,其类型为 StructTag,类型具体定义为:

    type StructTag string
    
  3. 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/
目录
×

喜欢就点赞,疼爱就打赏