Golang Reflect反射的使用详解1

90design · · 2193 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

What you are wasting today is tomorrow for those who died yesterday; what you hate now is the future you can not go back.

你所浪费的今天是昨天死去的人奢望的明天; 你所厌恶的现在是未来的你回不去的曾经。

    Go是静态类型语言。每个变量都拥有一个静态类型,这意味着每个变量的类型在编译时都是确定的:int,float32, *AutoType, []byte,  chan []int 诸如此类。

动静类型

     编译时就知道变量类型的是静态类型;运行时才知道一个变量类型的叫做动态类型。

    1. 静态类型

            静态类型就是变量声明时的赋予的类型。比如:


type MyInt int // int 就是静态类型

type A struct{
    Name string  // string就是静态
}
var i *int  // *int就是静态类型

    2. 动态类型

    动态类型:运行时给这个变量复制时,这个值的类型(如果值为nil的时候没有动态类型)。一个变量的动态类型在运行时可能改变,这主要依赖于它的赋值(前提是这个变量时接口类型)。

    var A interface{} // 静态类型interface{}
	A = 10            // 静态类型为interface{}  动态为int
	A = "String"      // 静态类型为interface{}  动态为string
	var M *int
	A = M             // 猜猜这个呢?

    来看看这个例子:


//定义一个接口
type Abc interface{
	String() string
}
// 类型
type Efg struct{
	data string
}
// 类型Efg实现Abc接口
func (e *Efg)String()string{
	return e.data
}
// 获取一个*Efg实例
func GetEfg() *Efg{
	return nil
}
// 比较
func CheckAE(a Abc) bool{
	return a == nil
}
func main() {
	efg := GetEfg()
	b := CheckAE(efg)
	fmt.Println(b)
	os.Exit(1)
}

关于动静态类型就到这里,详细请自行Google,百度吧。

反射

      那么什么时候下使用反射呢?

      有时候你想在运行时使用变量来处理变量,这些变量使用编写程序时不存在的信息。也许你正试图将来自文件或网络请求的数据映射到变量中。也许创建一个适用于不同类型的tool。在这些情况下,你需要使用反射。反射使您能够在运行时检查类型。它还允许您在运行时检查,修改和创建变量,函数和结构。

    类型

      你可以使用反射来获取变量的类型: var t := reflect.Typeof(v)。返回值是一个reflect.Type类型。该值有很多定义好的方法可以使用。

            Name()

      返回类型的名称。 但是像切片或指针是没有类型名称的,只能返回空字符串。

            Kind()

      Kind有slice, map , pointer指针,struct, interface, string , Array, Function, int或其他基本类型组成。Kind和Type之前要做好区分。如果你定义一个 type Foo struct {}, 那么Kind就是struct,  Type就是Foo。

        *小知识点:反射变量对应的Kind方法的返回值是基类型,并不是静态类型。下面的例子中:

type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)

变量v的Kind依旧是reflect.Int,而不是MyInt这个静态类型。Type可以表示静态类型,而Kind不可以。

       *注意点: 在使用refelct包时, reflect包会假定你已经知道所做的是什么,否则引发panic。 例如你调用了与当前reflect.Type 不同的类型上的方法,那么就会引发panic。

            Elem()

        如果你的变量是一个指针、map、slice、channel、Array。那么你可以使用reflect.Typeof(v).Elem()来确定包含的类型。

           案例代码    

type Foo struct {
	A int `tag1:"Tag1" tag2:"Second Tag"`
	B string
}
func main(){
	// Struct
	f := Foo{A: 10, B: "Salutations"}
	// Struct类型的指针
	fPtr := &f
	// Map
	m := map[string]int{"A": 1 , "B":2}
	// channel
	ch := make(chan int)
	// slice
	sl:= []int{1,32,34}
	//string
	str := "string var"
	// string 指针
	strPtr := &str

	tMap := examiner(reflect.TypeOf(f), 0)
	tMapPtr := examiner(reflect.TypeOf(fPtr), 0)
	tMapM := examiner(reflect.TypeOf(m), 0)
	tMapCh := examiner(reflect.TypeOf(ch), 0)
	tMapSl := examiner(reflect.TypeOf(sl), 0)
	tMapStr := examiner(reflect.TypeOf(str), 0)
	tMapStrPtr := examiner(reflect.TypeOf(strPtr), 0)

	fmt.Println("tMap :", tMap)
	fmt.Println("tMapPtr: ",tMapPtr)
	fmt.Println("tMapM: ",tMapM)
	fmt.Println("tMapCh: ",tMapCh)
	fmt.Println("tMapSl: ",tMapSl)
	fmt.Println("tMapStr: ",tMapStr)
	fmt.Println("tMapStrPtr: ",tMapStrPtr)
}

// 类型以及元素的类型判断
func examiner(t reflect.Type, depth int) map[int]map[string]string{
	outType := make(map[int]map[string]string)

	// 如果是一下类型,重新验证
	switch t.Kind() {
	case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
		fmt.Println("这几种类型Name是空字符串:",t.Name(), ", Kind是:", t.Kind())
		// 递归查询元素类型
		tMap := examiner(t.Elem(), depth)
		for k, v := range tMap{
			outType[k] = v
		}

	case reflect.Struct:
		for i := 0; i < t.NumField(); i++ {
			f := t.Field(i) // reflect字段
			outType[i] = map[string]string{
				"Name":f.Name,
				"Kind":f.Type.String(),
			}
		}
	default:
		// 直接验证类型
		outType = map[int] map[string]string{depth:{"Name":t.Name(), "Kind":t.Kind().String()}}
	}

	return outType
}

运行结果:

其中t.Field(index) 必须使用在Struct上 ,  所以,细读文档才行

 

 利用反射读取,设置,创建

        看完了上面关于reflect检测变量类型外,我们使用反射读取、设置和创建。

    要想读取一个变量的值,首先需要一个reflect.Valueof( var ) 实例(reflectVal := reflect.Valueof(var)), 同时也可以获取变量的类型了。

       要想修改一个变量的值,那么必须通过该变量的指针地址 , 取消指针的引用  。通过refPtrVal := reflect.Valueof( &var )的方式获取指针类型,你使用refPtrVal.elem( ).set(一个新的reflect.Value)来进行更改,传递给set()的值也必须是一个reflect.value。

        要想创建一个值,那么使用NewPtrVal := reflect.New( vartype ) 传递一个reflect.Type类型。 返回的指针类型就可以使用以上修改的方式写入值。

        最后,你可以通过调用interface()方法返回一个正常的变量。因为Golang没有泛型,变量的原始类型丢失;该方法返回一个类型为interface{} 的值。如果创建了一个指针以便可以修改该值,则需要使用elem().interface()来反引用reflect的指针。在这两种情况下,您都需要将空接口转换为实际类型才能使用它。

实例代码:

type Foo struct {
	A int `tag1:"Tag1" tag2:"Second Tag"`
	B string
}
func main(){
	// 反射的使用
	s := "String字符串"
	fo := Foo{A: 10, B: "字段String字符串"}

	sVal := reflect.ValueOf(s)
	// 在没有获取指针的前提下,我们只能读取变量的值。
	fmt.Println(sVal.Interface())

	sPtr := reflect.ValueOf(&s)
	sPtr.Elem().Set(reflect.ValueOf("修改值1"))
	sPtr.Elem().SetString("修改值2")
	// 修改指针指向的值,原变量改变
	fmt.Println(s)
	fmt.Println(sPtr) // 要注意这是一个指针变量,其值是一个指针地址

	foType := reflect.TypeOf(fo)
	foVal := reflect.New(foType)
	// foVal.Elem().Field(0).SetString("A") // 引发panic
	foVal.Elem().Field(0).SetInt(1)
	foVal.Elem().Field(1).SetString("B")
	f2 := foVal.Elem().Interface().(Foo)
	fmt.Printf("%+v, %d, %s\n", f2, f2.A, f2.B)
}

运行结果:

    

记忆10秒。

 

创建slice, map, chan

除了创建内置和用户定义类型的实例之外,还可以使用反射来创建通常需要make功能的实例。使用reflect.Makeslice,reflect.Makemap和reflect.Makechan函数来制作slice,map或channel。

    // 反射创建map slice channel
	intSlice := make([]int, 0)
	mapStringInt := make(map[string]int)
	sliceType := reflect.TypeOf(intSlice)
	mapType := reflect.TypeOf(mapStringInt)

	// 创建新值
	intSliceReflect := reflect.MakeSlice(sliceType, 0, 0)
	mapReflect := reflect.MakeMap(mapType)

	// 使用新创建的变量
	v := 10
	rv := reflect.ValueOf(v)
	intSliceReflect = reflect.Append(intSliceReflect, rv)
	intSlice2 := intSliceReflect.Interface().([]int)
	fmt.Println(intSlice2)

	k := "hello"
	rk := reflect.ValueOf(k)
	mapReflect.SetMapIndex(rk, rv)
	mapStringInt2 := mapReflect.Interface().(map[string]int)
	fmt.Println(mapStringInt2)

运行结果:

            

   

创建函数

使用reflect.Makefunc()创建,这个函数需要我们想要做的函数的reflect.type和一个输入参数是[] reflect.value类型的slice,其输出参数也是类型[] reflect.value的闭包。下面是一个简单的例子,检测任意给定函数的执行时长:

package main

import (
	"reflect"
	"time"
	"fmt"
	"runtime"
)
/*
将创建Func封装, 非reflect.Func类型会panic
当然makeFunc的闭包函数表达式类型是固定的,可以查阅一下文档。
细读文档的reflect.Value.Call()方法。
 */
func MakeTimedFunction(f interface{}) interface{} {
	rf := reflect.TypeOf(f)
	if rf.Kind() != reflect.Func {
		panic("非Reflect.Func")
	}
	vf := reflect.ValueOf(f)
	wrapperF := reflect.MakeFunc(rf, func(in []reflect.Value) []reflect.Value {
		start := time.Now()
		out := vf.Call(in)
		end := time.Now()
		fmt.Printf("calling %s took %v\n", runtime.FuncForPC(vf.Pointer()).Name(), end.Sub(start))
		return out
	})
	return wrapperF.Interface()
}

func time1() {
	fmt.Println("time1Func===starting")
	time.Sleep(1 * time.Second)
	fmt.Println("time1Func===ending")
}

func time2(a int) int {
	fmt.Println("time2Func===starting")
	time.Sleep(time.Duration(a) * time.Second)
	result := a * 2
	fmt.Println("time2Func===ending")
	return result
}

func main() {
	timed := MakeTimedFunction(time1).(func())
	timed()
	timedToo := MakeTimedFunction(time2).(func(int) int)
	time2Val := timedToo(5)
	fmt.Println(time2Val)
}

运行结果:

分析:

reflect.Value.Call(var)  文档如下:

扩展Call()

    首先我们可以确认一点就是,函数像普通变量一样, 假如Foo()是一个函数, 那么,f := Foo 也是成立的。

在反射中 函数 和 方法 的类型(Type)都是 reflect.Func,如果要调用函数的话,可以通过 Value 的 Call() 方法,例如:

func Halou(){
	fmt.Println("This is Halou函数! 6666")
}

func main(){
	// Call()扩展
	h := Halou
	hVal := reflect.ValueOf(h)
	fmt.Println("hVal is reflect.Func ?", hVal.Kind() == reflect.Func)
	hVal.Call(nil)
}

运行结果:

reflect.Value 的 Call() 方法的参数是一个 reflect.Value 的 slice,对应所反射函数类型的参数,返回值也是一个 reflect.Value 的 slice,同样对应所反射函数类型的返回值。所以:

func Halou2(s string)string{
	return "接受到参数:"+s+", 但这是返回值!"
}

func main(){
	h2 := reflect.ValueOf(Halou2)
	params := []reflect.Value{
		reflect.ValueOf("参数1"),
	}
	h2ReturnVal := h2.Call(params)
	fmt.Println(h2ReturnVal)
}

留下个问题吧!

函数本事独立与任何个体之外存活的,方法却要依托对象的存在。方法是“对象”的一种行为。那么如何通过反射调用方法呢?

 

 


有疑问加站长微信联系(非本文作者)

本文来自:开源中国博客

感谢作者:90design

查看原文:Golang Reflect反射的使用详解1

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2193 次点击  ∙  2 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传