reflect.Type 和 reflect.Value

reflect.Type 和 reflect.Value

反射是由reflect包提供支持,它定义了俩个重要的类型,Type和Value。

一个Type表示一个Go类型,它是一个接口。reflect.Type 可以表示任意类型。

一个Value表示一个Go值,reflect.Value 可以持有一个任意类型的值,

reflect.Type

函数reflect.TypeOf 接受任意的interface{} 类型,并返回对应动态类型的reflect.Type:

t := reflect.TypeOf(3) // 返回一个reflect.Type 
fmt.Println(t.String()) 
fmt.Println(t)

reflect.Type 是满足fmt.Stringer 接口的,比如以下打印类型,就是通过将值通过relfect.TypeOf 的结果输出的

fmt.Printf("%T\n", 3) // "int"

reflect.Value

函数reflect.ValueOf 接受任意的interface{} 类型,并返回对应动态类型的reflect.Value 和 reflect.TypeOf类似,reflect.ValueOf 返回的结果也是对于具体的类型,但是reflect.Value 也可以持有一个接口值。

和reflect.Type 类似,reflect.Value 也满足fmt.Stringer接口,但是除非Value 持有的是字符串,否则String 只是返回具体的类型。

v := reflect.ValueOf(3) // 返回一个 reflect.Value
fmt.Println(v) // "3"
fmt.Printf("%v\n", v) // "3"
fmt.Println(v.String())

调用Value 的Type 方法将返回具体类型所对应的 reflect.Type:

t := v.Type() // 返回一个reflect.Type
fmt.Println(t.String()) // "int"

逆操作是调用reflect.ValueOf 对应的 reflect.Value.Interface 方法,它返回一个 interface{} 类型表示reflect.Value 对应的具体值:

v := reflect.ValueOf(3) // 得到一个reflect.Value
x := v.Interface() // 转换为interface{}
i := x.(int)     // 转为int
fmt.Printf("%d\n", i) // 3

一个reflect.Value 和interface 都能够存储任意的值,所不同的是,一个空的接口隐藏了值对应的表示方式和所有的公开的方法,因此只有我们知道具体的动态类型才能使用类型断言来访问内部的值。

但reflect.Value 可以通过Kind 方法获取对应的类型,然后再进行类型选择,且kinds 的类型是有限的

以下的kinds 所有类型:

kind
Bool
String
Int Int8… Uint Uint8…
Chan
Func
Ptr
Slice
Map
Array
Struct
Interface
Invalid

实现打印值:

package format

import (
	"fmt"
	"reflect"
	"strconv"
	"testing"
	"time"
)

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(), 10)
	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 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: // reflect.Array, reflect.Struct, reflect.Interface
		return v.Type().String() + " value"
	}
}

func Test_formatAtom(t *testing.T) {
	var x int64 = 1
	var d time.Duration = 1 * time.Nanosecond
	fmt.Println(Any(x))
	fmt.Println(Any(d))
	fmt.Println(Any([]int64{x}))
	fmt.Println(Any([]time.Duration{d}))
}

实现一个结构体解析为json格式

传入reflect.Value 打印相应的值

  1. 通过v.Kind() 方法,获取当前value 类型 对每个类型进行分析, 根据对应的类型打印值

  2. 对于Slice 和Array 俩者的逻辑是一样的,Len 方法返回Slice 或Array 值中的元素个数, Index(i) 活动索引i对应的元素,返回的也是一个reflect.Value 类型的值;

  3. Struct: NumField 方法报告结构体中成员的数量,Field(i)以reflect.Value 类型返回第i个成员的值。

  4. Map: MapKeys 方法返回一个reflect.Value类型的slice, 每一个都对应map的key。 遍历map 的顺序也是随机的。MapIndex(key) 返回map 中key 对应的value。

import (
	"encoding/json"
	"bytes"
	"fmt"
	"reflect"
	"testing"
)

// Marshal jsonmarshal 将一个类型解析为json格式
func Marshal(x interface{}) ([]byte, error) {
	var buf bytes.Buffer
	if err := encode(&buf, reflect.ValueOf(x)); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func encode(buf *bytes.Buffer, v reflect.Value) error {
	switch v.Kind() {
	case reflect.Invalid:
		buf.WriteString("null")
	case reflect.Int, reflect.Int8, reflect.Int16,
		reflect.Int32, reflect.Int64:
		fmt.Fprintf(buf, "%d", v.Int())
	case reflect.Uint, reflect.Uint8, reflect.Uint16,
		reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		fmt.Fprintf(buf, "%d", v.Uint())
	case reflect.String:
		fmt.Fprintf(buf, "%q", v.String())
	case reflect.Bool:
		if v.Bool() {
			buf.WriteString("true")
		} else {
			buf.WriteString("false")
		}
	case reflect.Ptr:
		return encode(buf, v.Elem())
	case reflect.Slice, reflect.Array:
		buf.WriteByte('[')
		for i := 0; i < v.Len(); i++ {
			if i > 0 {
				buf.WriteByte(' ')
			}
			if err := encode(buf, v.Index(i)); err != nil {
				return err
			}
			if i < v.Len()-1 {
				buf.WriteByte(',')
			}
		}
		buf.WriteByte(']')
	case reflect.Map:
		buf.WriteByte('{')
		for i, key := range v.MapKeys() {
			if i > 0 {
				buf.WriteByte(' ')
			}
			if err := encode(buf, key); err != nil {
				return err
			}
			buf.WriteString(": ")
			if err := encode(buf, v.MapIndex(key)); err != nil {
				return err
			}
			if i < v.Len()-1 {
				buf.WriteByte(',')
			}
		}
		buf.WriteByte('}')
	case reflect.Struct:
		buf.WriteByte('{')
		for i := 0; i < v.NumField(); i++ {
			if i > 0 {
				buf.WriteByte(' ')
			}
			fmt.Fprintf(buf, "%q:", v.Type().Field(i).Name)
			if err := encode(buf, v.Field(i)); err != nil {
				return err
			}
			if i < v.NumField()-1 {
				buf.WriteByte(',')
			}
		}
		buf.WriteByte('}')
	default: // complex, chan, interface, func
		fmt.Fprint(buf, "null")
	}
	return nil
}

func TestJsonMarshal(t *testing.T) {
	type Movie struct {
		Title, Subtitle string
		Year            int
		Color           bool
		Actor           map[string]string
		Oscars          []string
		Sequel          *string
	}
	strangelove := Movie{
		Title:    "Dr. Strangelove",
		Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
		Year:     1964,
		Color:    false,
		Actor: map[string]string{
			"Dr. Strangelove":            "Peter Sellers",
			"Grp. Capt. Lionel Mandrake": "Peter Sellers",
			"Pres. Merkin Muffley":       "Peter Sellers",
			"Gen. Buck Turgidson":        "George C. Scott",
			"Brig. Gen. Jack D. Ripper":  "Sterling Hayden",
			`Maj. T.J. "King" Kong`:      "Slim Pickens",
		},
		Oscars: []string{
			"Best Actor (Nomin.)",
			"Best Adapted Screenplay (Nomin.)",
			"Best Director (Nomin.)",
			"Best Picture (Nomin.)",
		},
	}
	encodeBytes, err := Marshal(strangelove)
	if err != nil {
		t.Fatal(err.Error())
	} else {
		t.Log(string(encodeBytes))
	}
	var got Movie
	if err := json.Unmarshal(encodeBytes, &got); err != nil {
		t.Fatal(err.Error())
	}
	if !reflect.DeepEqual(got, strangelove) {
		t.Error("jsonUnmarshal faild!!!")
	} else {
		t.Logf("%v", got)
	}
}

修改reflect.Value 的值

在go中是可以通过反射来修改当前变量的值的,前提是当前reflect.Value 是可以寻址的。

首先,先确定reflect.Value 中addressable 和变量类型的addressable 是有点差别的。 下面是reflect.Value 中可寻址的变量:

  • slice 的元素
  • 可寻址数组的元素
  • 可寻址struct 的字段
  • 指针引用的结果
x := 2
a := reflect.ValueOf(2)
b := reflect.ValueOf(x)
c := reflect.ValueOf(&x)
d := x.Elem()

查看当前每个接受的Value 是否能够取得地址

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

可以看出每当我们通过指针间接地获取的reflect.Value 都是可取地址的,即使开始的是一个不可取地址的Value。比如,slice 的索引表达式e[i]将隐式地包含一个指针,它是可取地址的,即使开始的e表达式不支持也没有关系。则reflect.ValueOf(e).Index(i)对于的值也是可取地址的,即使原始的reflect.ValueOf(e) 不支持也没有关系。

一般可以使用reflect.Value(&v).Elem() 获取一个可寻址对象。

x := 2
addr := reflect.Value(&x).Elem() // 获取可寻址对象
addr.CanAddr() // true
addr.Set(reflect.ValueOf(3)) // x 3
addr.SetInt(4)