深刻理解Golang之interface和reflect

前言

interface(即接口),是Go语言中一个重要的概念和知识点,而功能强大的reflect正是基于interface。本文便是对Go语言中的interfacereflect相关知识较为全面的梳理,也算是我阶段学习的总结,以期温故而知新。文章较长,请读者作好心理准备。html

interface(接口)

定义

在Go语言中,若是自定义类型(好比struct)实现了某个interface中的全部方法,那么就能够说这个类型实现了这个接口。接口可以下定义:golang

type 接口名称 interface {
    method1(参数列表) 返回值列表
    method1(参数列表) 返回值列表
    ...
}
复制代码

interface是一组方法的集合,但并不须要实现这些方法,而且interface没有变量interface中的方法集合能够表示一个对象的特征和能力,当自定义类型须要使用这些方法时,能够根据须要把这些方法实现出来。举个栗子:web

package main

import (
    "fmt"
)

type Animal interface {
    Eat()
    Run()
}

type Dog struct {
    Name string
}

type Cat struct {
    Name string
}

func (dog *Dog) Eat() {
    fmt.Printf("%s is eating.", dog.Name)
}

func (dog *Dog) Run() {
    fmt.Printf("%s is running.", dog.Name)
}

func (cat *Cat) Eat() {
    fmt.Printf("%s is eating.", cat.Name)
}

func (cat *Cat) Run() {
    fmt.Printf("%s is running.", cat.Name)
}

func main() {
    var animal1 Animal
    animal1 = &Dog{"doggy"}
    animal1.Eat()
    animal1.Run()

    var animal2 Animal
    animal2 = &Cat{"catty"}
    animal2.Eat()
    animal2.Run()
}
复制代码

上面即定义了一个Animal接口,以及Dog类型和Cat类型。Dog类型和Cat类型都实现了Animal接口中的方法,因此Dog和Cat都是Animal类型。
同时接口自己不能建立实例,但从上例能够看出,接口类型的变量能够指向一个实现了该接口的自定义类型的实例。interface类型默认是一个指针(引用类型),若是没有对interface初始化就使用,那么会输出nilsql

空接口

空接口interface{}没有任何方法,因此全部类型都实现了空接口, 即咱们能够把任何一个变量赋值给空接口。修改一下上面的main函数:数据库

func main() {
    var animal interface{}
    dog := &Dog{"doggy"}
    animal = dog
    fmt.Println(animal)
}
复制代码

运行结果:数组

&{doggy}
复制代码

接口继承

一个接口能够继承多个其余接口,若是要实现这个接口,那么必须将所继承的全部接口中的方法都实现。bash

package main

import (
    "fmt"
)

type Eater interface {
    Eat()
}

type Runner interface {
    Run()
}

type Animal interface {
    Eater
    Runner
}

// 这里定义一个Dog的struct,并实现eat方法和run方法,这样就实现了动物的接口
type Dog struct {
    Name string
}

func (dog *Dog) Eat() {
    fmt.Printf("%s is eating.", dog.Name)
}

func (dog *Dog) Run() {
    fmt.Printf("%s is running.", dog.Name)
}

func main() {
    var animal1 Animal
    animal1 = &Dog{"doggy"}
    animal1.Eat()
    animal1.Run()
}
复制代码

类型断言

当咱们不肯定某个接口变量里存储的是什么类型的变量时,咱们能够利用类型断言来判断变量类型。app

var animal1 Animal
animal1 = &Dog{"doggy"}
dog := animal1.(*Dog)
复制代码

在进行类型断言时,若是类型不匹配,就会报panic, 所以须要加上检测机制,若是成功就 ok,不然也不要报 panic函数

var animal1 Animal
animal1 = &Dog{"doggy"}

if dog, ok := animal1.(*Dog); ok {
    fmt.Println("convert success")
    dog.Run()
else {
    fmt.Println("convert fail")
}
复制代码

另外咱们也可使用switch-type语法进行类型断言:工具

package main

import (
    "fmt"
)

type Eater interface {
    Eat()
}

type Runner interface {
    Run()
}

type Animal interface {
    Eater
    Runner
}

type Dog struct {
    Name string
}

type Cat struct {
    Name string
}

func (dog *Dog) Eat() {
    fmt.Printf("%s is eating.", dog.Name)
}

func (dog *Dog) Run() {
    fmt.Printf("%s is running.", dog.Name)
}

func (cat *Cat) Eat() {
    fmt.Printf("%s is eating.", cat.Name)
}

func (cat *Cat) Run() {
    fmt.Printf("%s is running.", cat.Name)
}

func TypeJudge(animals ...interface{}) {
    for index, animal := range animals {
        switch animal.(type) {
        case *Dog:
            fmt.Printf("第%d个参数是Dog类型\n", index)
        case *Cat:
            fmt.Printf("第%d个参数是Cat类型\n", index)
        default:
            fmt.Println("不肯定类型")
        }
    }
}

func main() {
    var animal1 Animal
    animal1 = &Dog{"doggy"}

    var animal2 Animal
    animal2 = &Cat{"catty"}

    TypeJudge(animal1, animal2)
}
复制代码

做用

interface对于Go语言的意义在于其实现了泛型,好比在一个函数中须要能接收不一样类型的参数或者返回不一样类型的值,而不是一开始就指定参数或者返回值的类型,这样就可让函数支持全部类型:

func FuncName(arg1 interface{}, rest ...interface{}) interface{} {
    // ...
}
复制代码

面向对象语言好比C++、Java都有多态的特性,能够说interface是Go语言中实现多态的一种形式。同一个interface,可让不一样的类(自定义类型)实现,从而能够调用同一个函数名的函数但实现彻底不一样的功能。

有时咱们可以利用interface实现很是巧妙的功能:一般咱们定义一个切片(slice)都会指定一个具体的类型,可是咱们有时须要切片中的元素能够任何类型的变量,这个时候interface就派上用场了。下面是在go代码中update数据库表中数据时,利用interface实现的骚操做,读者能够体会一下interface带来的便利:

func generateSQLForUpdatingArticle(article model.ArticleStruct) (string, []interface{}) {
    var columns = make([]string0)
    var arguments = make([]interface{}, 0)

    if len(article.CommentCount) > 0 {
        columns = append(columns, "comment_count = ?")
        arguments = append(arguments, article.CommentCount)
    }

    if len(article.Source) > 0 {
        columns = append(columns, "source = ?")
        arguments = append(arguments, article.Source)
    }

    if len(article.Summary) > 0 {
        columns = append(columns, "summary = ?")
        arguments = append(arguments, article.Summary)
    }

    if len(article.Content) > 0 {
        columns = append(columns, "content = ?")
        arguments = append(arguments, article.Content)
    }

    sql := fmt.Sprintf("UPDATE article_structs SET %s WHERE sid = %s", strings.Join(columns, ","), article.Sid)
    return sql, arguments
}

func UpdateArticle(article model.ArticleStruct) error {
    sql, arguments := generateSQLForUpdatingArticle(article)
    if err := db.Exec(sql, arguments...).Error; err != nil {
        log.Println("Updating article failed with error:", err)
        return err
    }
    return nil
}
复制代码

然而,空接口interface{} 虽然能保存任意的值,但也带来了一个问题:一个空的接口会隐藏值对应的表示方式和全部的公开的方法,所以只有咱们知道具体的动态类型才能使用类型断言来访问内部的值, 对于内部值并无特别可作的事情;若是咱们事先不知道空接口指向的值的具体类型,咱们可能就一筹莫展了。

这个时候咱们想要知道一个接口类型的变量具体是什么(什么类型),有什么能力(有哪些方法),就须要一面“镜子”可以反射(reflect)出这个变量的具体内容。在Go语言中也正好有这样的工具——reflect

reflect(反射)

概念

在计算机科学领域,反射是指一类应用,它们可以自描述和自控制。也就是说,这类应用经过采用某种机制来实现对本身行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

支持反射的语言能够在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就能够在程序运行期获取类型的反射信息,而且有能力修改它们。

在讲反射以前,咱们须要了解一下Golang关于类型设计的一些原则:

变量包含两部分:type(类型)和value(值)。

type 分为 static typeconcrete type。其中static type是咱们在编码阶段用到的数据类型,如int、string、bool等等;而concrete type则是runtime系统看见的类型。

接口类型的变量在类型断言时可否成功,取决于concrete type 而不是 static type

在Go语言中指定类型的变量的类型都是静态的,即static type,其在建立变量的时候就已经肯定;而反射主要是配合interface类型变量来使用的,这些变量的类型都是concrete type

在Go的实现中,每一个interface类型的变量都有一个对应的pair, pair中记录了实际变量的valuetype

(value, type)
复制代码

interface类型变量包含了两个指针,分别指向实际变量的值(value)和类型(对应concrete type)。interface及其pair的存在,是Golang实现反射的前提,而反射也正是用来检测接口类型变量内部存储的值和类型的一种机制。说到这里,天然也就要引出reflect包中的两个数据类TypeValue

reflect.Type和reflect.Value

reflect.Type

reflect包中Type接口定义以下:

type Type interface {
    // Kind返回该接口的具体分类
    Kind() Kind
    // Name返回该类型在自身包内的类型名,若是是未命名类型会返回""
    Name() string
    // PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64"
    // 若是类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""
    PkgPath() string
    // 返回类型的字符串表示。该字符串可能会使用短包名(如用base64代替"encoding/base64")
    // 也不保证每一个类型的字符串表示不一样。若是要比较两个类型是否相等,请直接用Type类型比较。
    String() string
    // 返回要保存一个该类型的值须要多少字节;相似unsafe.Sizeof
    Size() uintptr
    // 返回当从内存中申请一个该类型值时,会对齐的字节数
    Align() int
    // 返回当该类型做为结构体的字段时,会对齐的字节数
    FieldAlign() int
    // 若是该类型实现了u表明的接口,会返回真
    Implements(u Type) bool
    // 若是该类型的值能够直接赋值给u表明的类型,返回真
    AssignableTo(u Type) bool
    // 如该类型的值能够转换为u表明的类型,返回真
    ConvertibleTo(u Type) bool
    // 返回该类型的字位数。若是该类型的Kind不是Int、Uint、Float或Complex,会panic
    Bits() int
    // 返回array类型的长度,如非数组类型将panic
    Len() int
    // 返回该类型的元素类型,若是该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic
    Elem() Type
    // 返回map类型的键的类型。如非映射类型将panic
    Key() Type
    // 返回一个channel类型的方向,如非通道类型将会panic
    ChanDir() ChanDir

    // 返回struct类型的字段数(匿名字段算做一个字段),如非结构体类型将panic
    NumField() int
    // 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
    Field(i int) StructField
    // 返回索引序列指定的嵌套字段的类型,
    // 等价于用索引中每一个值链式调用本方法,如非结构体将会panic
    FieldByIndex(index []int) StructField
    // 返回该类型名为name的字段(会查找匿名字段及其子字段),
    // 布尔值说明是否找到,如非结构体将panic
    FieldByName(name string) (StructField, bool)
    // 返回该类型第一个字段名知足函数match的字段,布尔值说明是否找到,如非结构体将会panic
    FieldByNameFunc(match func(string) bool(StructField, bool)
    // 若是函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真
    // 若是这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片)
    // 如非函数类型将panic
    IsVariadic() bool
    // 返回func类型的参数个数,若是不是函数,将会panic
    NumIn() int
    // 返回func类型的第i个参数的类型,如非函数或者i不在[0, NumIn())内将会panic
    In(i int) Type
    // 返回func类型的返回值个数,若是不是函数,将会panic
    NumOut() int
    // 返回func类型的第i个返回值的类型,如非函数或者i不在[0, NumOut())内将会panic
    Out(i int) Type
    // 返回该类型的方法集中方法的数目
    // 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
    // 匿名字段致使的歧义方法会滤除
    NumMethod() int
    // 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将致使panic
    // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
    // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
    Method(int) Method
    // 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
    // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
    // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
    MethodByName(string) (Method, bool)
    // 内含隐藏或非导出方法
}
复制代码

咱们能够经过reflect.TypeOf接受任意interface{}类型,并返回对应的动态类型reflect.Type

num := reflect.TypeOf(1)
fmt.Println(num.String())
fmt.Println(num)
复制代码

看一下TypeOf()的实现代码:

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}
复制代码

能够发现TypeOf函数的参数类型是一个interface{},而且在函数内部将这里的具体值1进行一个隐式转换,转换为一个空接口类型的变量,这个变量包含两部分信息:1这个变量的动态类型(为int)和动态值(为1);最后TypeOf的返回值是reflect.Type类型(咱们称为反射类型对象),这样就可以调用上面Type接口的方法获取所需的变量信息。

  • 当反射对象的类型是原始数据类型时:
func main() {
    var s string
    rString := reflect.TypeOf(s)
    fmt.Println(rString)         //string
    fmt.Println(rString.Name())  //string,返回表示类型名称的字符串
    fmt.Println(rString.Kind())  //string,返回 reflect.Kind 类型的常量
}
复制代码
  • 当反射对象的类型是指针类型时:
type Dog struct {
    Name string
    Age  int
}

func main() {
    dogPtr := &Dog{"doggy"}
    rDogPtr := reflect.TypeOf(dogPtr)

    fmt.Println(rDogPtr.Name())  // 为空
    fmt.Println(rDogPtr.Kind())  // ptr

    // Elem()能够获取指针指向的实际变量
    rDog := rDogPtr.Elem()
    fmt.Println(rDogPtr.Name())  // Dog
    fmt.Println(rDogPtr.Kind())  // struct
}
复制代码

能够发现从指针获取反射对象时,不能直接使用Name()Kind(),这样只能获得该指针的信息。这时可使用Elem()获取指针指向的实际变量。

  • 当反射对象的类型是结构体类型时:

若是反射对象的类型是结构体,能够经过 NumField()Field() 方法得到结构体成员的详细信息。

type Dog struct {
    Name string
    Age  int
}

func main() {
    dog := Dog{"doggy"2}
    rDog := reflect.TypeOf(dog)

    fmt.Printf("%v ", rDog.Name()) // Dog
    fmt.Println(rDog.Kind())       // struct

    for index := 0; index < rDog.NumField(); index++ {
        fmt.Printf("%v ", rDog.Field(index).Name)
        fmt.Println(rDog.Field(index).Type)
    }
}
复制代码

运行输出:

Dog struct
Name string
Age int
复制代码
reflect.Value

reflect包中Value类型定义以下:

type Value struct {
    // typ holds the type of the value represented by a Value.
    typ *rtype

    // Pointer-valued data or, if flagIndir is set, pointer to data.
    // Valid when either flagIndir is set or typ.pointers() is true.
    ptr unsafe.Pointer

    // flag holds metadata about the value.
    flag
}
复制代码

能够看到Value类型包含一个类型指针、一个值指针以及标志信息。同时Value类型还有不少方法,其中用于获取值方法:

func (v Value) Int() int64 // 获取int类型值,若是 v 值不是有符号整型,则 panic

func (v Value) Uint() uint64 // 获取unit类型的值,若是 v 值不是无符号整型(包括 uintptr),则 panic

func (v Value) Float() float64 // 获取float类型的值,若是 v 值不是浮点型,则 panic

func (v Value) Complex() complex128 // 获取复数类型的值,若是 v 值不是复数型,则 panic

func (v Value) Bool() bool // 获取布尔类型的值,若是 v 值不是布尔型,则 panic

func (v Value) Len() int // 获取 v 值的长度,v 值必须是字符串、数组、切片、映射、通道。

func (v Value) Cap() int  // 获取 v 值的容量,v 值必须是数值、切片、通道。

func (v Value) Index(i int) reflect.Value // 获取 v 值的第 i 个元素,v 值必须是字符串、数组、切片,i 不能超出范围。

func (v Value) Bytes() []byte // 获取字节类型的值,若是 v 值不是字节切片,则 panic

func (v Value) Slice(i, j int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = v.Cap() - i
// v 必须是字符串、数值、切片,若是是数组则必须可寻址。i 不能超出范围。

func (v Value) Slice3(i, j, k int) reflect.Value  // 获取 v 值的切片,切片长度 = j - i,切片容量 = k - i
// ijk 不能超出 v 的容量。i <= j <= k
// v 必须是字符串、数值、切片,若是是数组则必须可寻址。i 不能超出范围。

func (v Value) MapIndex(key Value) reflect.Value // 根据 key 键获取 v 值的内容,v 值必须是映射。
// 若是指定的元素不存在,或 v 值是未初始化的映射,则返回零值(reflect.ValueOf(nil)

func (v Value) MapKeys() []reflect.Value // 获取 v 值的全部键的无序列表,v 值必须是映射。
// 若是 v 值是未初始化的映射,则返回空列表。

func (v Value) OverflowInt(x int64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是有符号整型。

func (v Value) OverflowUint(x uint64) bool  // 判断 x 是否超出 v 值的取值范围,v 值必须是无符号整型。

func (v Value) OverflowFloat(x float64) bool  // 判断 x 是否超出 v 值的取值范围,v 值必须是浮点型。

func (v Value) OverflowComplex(x complex128) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是复数型。
复制代码

用于设置值方法:

func (v Value) SetUint(x uint64)  // 设置无符号整型的值

func (v Value) SetFloat(x float64) // 设置浮点类型的值

func (v Value) SetComplex(x complex128) //设置复数类型的值

func (v Value) SetBool(x bool) //设置布尔类型的值

func (v Value) SetString(x string) //设置字符串类型的值

func (v Value) SetLen(n int)  // 设置切片的长度,n 不能超出范围,不能为负数。

func (v Value) SetCap(n int) //设置切片的容量

func (v Value) SetBytes(x []byte) //设置字节类型的值

func (v Value) SetMapIndex(key, val reflect.Value) //设置mapkeyvalue,前提必须是初始化之后,存在覆盖、不存在添加

func (v Value) Set(x Value) // 将v的持有值修改成x的持有值。若是v.CanSet()返回假,会panicx的持有值必须能直接赋给v持有值的类型。
复制代码

其余方法:

结构体相关:
func (v Value) NumField() int // 获取结构体字段(成员)数量

func (v Value) Field(i int) reflect.Value  //根据索引获取结构体字段

func (v Value) FieldByIndex(index []int) reflect.Value // 根据索引链获取结构体嵌套字段

func (v Value) FieldByName(string) reflect.Value // 根据名称获取结构体的字段,不存在返回reflect.ValueOf(nil)

func (v Value) FieldByNameFunc(match func(string) boolValue // 根据匹配函数 match 获取字段,若是没有匹配的字段,则返回零值(reflect.ValueOf(nil)


通道相关:
func (v Value) Send(x reflect.Value)// 发送数据(会阻塞),v 值必须是可写通道。

func (v Value) Recv() (x reflect.Value, ok bool) // 接收数据(会阻塞),v 值必须是可读通道。

func (v Value) TrySend(x reflect.Value) bool // 尝试发送数据(不会阻塞),v 值必须是可写通道。

func (v Value) TryRecv() (x reflect.Value, ok bool) // 尝试接收数据(不会阻塞),v 值必须是可读通道。

func (v Value) Close() // 关闭通道


函数相关
func (v Value) Call(in []Value) (r []Value) // 经过参数列表 in 调用 v 值所表明的函数(或方法)。函数的返回值存入 r 中返回。
// 要传入多少参数就在 in 中存入多少元素。
// Call 便可以调用定参函数(参数数量固定),也能够调用变参函数(参数数量可变)。

func (v Value) CallSlice(in []Value) []Value // 调用变参函数
复制代码

一样地,咱们能够经过reflect.ValueOf接受任意interface{}类型,并返回对应的动态类型reflect.Value

v := reflect.ValueOf(2)
fmt.Println(v)  // 2
fmt.Println(v.String()) // <int Value>
复制代码

看一下reflect.ValueOf的实现代码:

func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }

    // TODO: Maybe allow contents of a Value to live on the stack.
    // For now we make the contents always escape to the heap. It
    // makes life easier in a few places (see chanrecv/mapassign
    // comment below).
    escapes(i)

    return unpackEface(i)
}

// unpackEface converts the empty interface i to a Value.
func unpackEface(i interface{}) Value {
    e := (*emptyInterface)(unsafe.Pointer(&i))
    // NOTE: don't read e.word until we know whether it is really a pointer or not.
    t := e.typ
    if t == nil {
        return Value{}
    }
    f := flag(t.Kind())
    if ifaceIndir(t) {
        f |= flagIndir
    }
    return Value{t, e.word, f}
}
复制代码

escapes() 涉及栈和堆的对象分配以及逃逸分析,有兴趣的能够看 William Kennedy 写的系列文章: Go 语言机制之逃逸分析

reflect.TypeOf相似,ValueOf函数的参数类型是一个interface{},在函数内部将入参进行一个隐式转换,转换为一个空接口类型的变量,最终返回一个Value对象,而且reflect.ValueOf返回值也是反射类型对象

能够注意到Value对象中也包含了实际值的类型信息,经过ValueType() 方法将返回具体类型所对应的reflect.Type:

v := reflect.ValueOf(2)
t := v.Type()
fmt.Println(t) // int
fmt.Println(t.String()) // int
复制代码

经过`relfect.Value`获取实际变量的信息

如今咱们知道了经过reflect.ValueOf能够将接口类型变量转换成反射类型变量,固然咱们也能够经过reflect.Value.Interface方法逆操做回去,而后经过断言的方式获得实际值:

v := reflect.ValueOf(2)
i := v.Interface()
if num, ok := i.(int); ok { // 类型断言
    fmt.Println(num)
}
复制代码

但一般在实际场景中,咱们其实并不知道原始值的类型,这里就须要利用reflect.Typereflect.Value的方法探索原始值的信息。下面经过一个例子说明:

package main

import (
    "fmt"
    "reflect"
)

type Dog struct {
    Name string
    Age  int
}

func (dog *Dog) Eat() {
    fmt.Printf("%s is eating.", dog.Name)
}

func (dog *Dog) Run() {
    fmt.Printf("%s is running.", dog.Name)
}

func (dog Dog) Sleep() {
    fmt.Printf("%s is sleeping.", dog.Name)
}

func (dog Dog) Jump() {
    fmt.Printf("%s is jumping.", dog.Name)
}

func main() {
    doggy := Dog{"doggy"2}
    checkFieldAndMethod(doggy)

    fmt.Println("")
    tommy := &Dog{"tommy"2}
    checkFieldAndMethod(tommy)
}

func checkFieldAndMethod(input interface{}) {
    inputType := reflect.TypeOf(input)
    fmt.Println("Type of input is :", inputType.Name())
    inputValue := reflect.ValueOf(input)
    fmt.Println("Value of input is :", inputValue)

    // 若是input原始类型时指针,经过Elem()方法或者Indirect()获取指针指向的值
    if inputValue.Kind() == reflect.Ptr {
        inputValue = inputValue.Elem()
        // inputValue = reflect.Indirect(inputValue)
        fmt.Println("Value input points to is :", inputValue)
    }

    //使用NumField()获得结构体中字段的数量,遍历获得字段的值Field(i)和类型Field(i).Type()
    for i := 0; i < inputValue.NumField(); i++ {
        field := inputValue.Type().Field(i)
        value := inputValue.Field(i).Interface()
        fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
    }

    // 获取方法
    for i := 0; i < inputType.NumMethod(); i++ {
        m := inputType.Method(i)
        fmt.Printf("%s: %v\n", m.Name, m.Type)
    }
}
复制代码

运行以后输出:

Type of input is : Dog
Value of input is : {doggy 2}
Namestring = doggy
Age: int = 2
Jump: func(main.Dog)
Sleep: func(main.Dog)

Type of input is : 
Value of input is : &{tommy 2}
Value input points to is : {tommy 2}
Namestring = tommy
Age: int = 2
Eat: func(*main.Dog)
Jump: func(*main.Dog)
Run: func(*main.Dog)
Sleep: func(*main.Dog)
复制代码

利用反射获取原始值得类型和方法的步骤以下:

  • 判断原始值是值变量仍是指针变量,若是是指针变量,则经过Elem()方法或者Indirect()获取指针指向的值;
  • 使用NumField()获得结构体中字段的数量,遍历获得字段的值Field(i)和类型Field(i).Type()
  • 使用NumMethod()获得结构体的方法,遍历获得方法的名称和类型。

另外,在使用reflect.Value过程有时会对Elem()方法和Indirect()有些迷惑,搞不清这两个方法的区别,这里总结一下:

// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.
func (v Value) Elem() Value

// Indirect returns the value that v points to.
// If v is a nil pointerIndirect returns a zero Value.
// If v is not a pointerIndirect returns v.
func Indirect(v Value) Value
复制代码
  • Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。若是v的Kind不是InterfacePtrpanic;若是v持有的值为nil,会返回Value零值。
  • Indirect返回v持有的指针指向的值的Value封装。若是v持有的值为nil,会返回Value零值。若是v持有的变量不是指针,那么将返回原值v。

也就是说,当v持有的变量是指针时,Elem()方法和Indirect()是等价的。

细心的读者可能发现对于值变量和指针变量,经过反射获取到的变量方法有些差别,这个问题就留给读者本身思考吧。

经过`relfect.Value`修改实际变量的信息

当经过relfect.Value修改实际变量的信息是经常使用到如下反射值对象的方法:

func (v Value) Elem() Value  
//Elem()返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装,相似于*操做,此时的Value表示的是Value的元素且能够寻址。

func (v Value) Addr() Value 
//Addr()返回一个持有指向v变量地址的指针的Value封装,相似于&操做。

func (v Value) CanAddr() bool
//CanAddr()返回是否能够获取v持有值的指针。能够获取指针的值被称为可寻址的。

func (v Value) CanSet() bool
//CanSet()返回v持有的值是否能够被修改
复制代码

然而,值得注意的是并非全部reflect.Value类型的反射值均可以修改,考虑下面这个例子:

package main 

import(
    "fmt"
    "reflect"
)

func main() {
    a := 1
    rA := reflect.ValueOf(a)
    fmt.Println(rA.CanSet()) //false

    rAptr := reflect.ValueOf(&a)
    rA2 := rAptr.Elem()
    fmt.Println(rA2.CanSet()) //true
    rA2.SetInt(2)
    fmt.Println(rA2.Int()) //2
}
复制代码

修改反射类型变量的值有两个条件:

  • 反射类型变量的值是addressable的,便可取地址的;
  • 反射类型变量的值来自导出字段。

有一些修改反射类型变量是可寻址的,有一些则不是:

package main

import (
    "reflect"
    "fmt"
)

func main() {
    x := 2
    a := reflect.ValueOf(2)
    b := reflect.ValueOf(x)
    c := reflect.ValueOf(&x)
    d := c.Elem()
    fmt.Println(a.CanAddr()) // false
    fmt.Println(b.CanAddr()) // false
    fmt.Println(c.CanAddr()) // false
    fmt.Println(d.CanAddr()) // true

}
复制代码

对于非指针变量x,经过reflect.ValueOf(x)返回的 reflect.Value是不可取地址的。可是对于d,它是c的解引用方式生成的,指向另外一个变量,所以是可 取地址的。咱们能够经过调用reflect.ValueOf(&x).Elem(),获取到x对应的可取地址的反射值。

对于结构体类型变量,若是成员字段没有导出,那么虽然能够被访问,但不能经过反射修改:

package main

import (
    "fmt"
    "reflect"
)

type Dog struct {
    Name string
    Age  int
    sex  string
}

func main() {
    rDog := reflect.ValueOf(&Dog{}).Elem()
    vAge := rDog.FieldByName("Age")
    vAge.SetInt(1)

    vSex := rDog.FieldByName("sex")
    vSex.SetString("male")
}
复制代码

运行出现报错:SetString使用的值来自于一个未导出的字段。

panic: reflect: reflect.Value.SetString using value obtained using unexported field
复制代码

为了能修改这个值,须要将该字段导出。将Dog类型中的 sex成员首字母大写便可。

修改可取地址的reflect.Value持有的变量值,除了能够经过反射的Set系列方法,还能够经过从反射类型变量获取实际值的指针来修改:

package main

import (
    "reflect"
    "fmt"
)

func main() {
    x := 1
    v := reflect.ValueOf(&x).Elem()
    px := v.Addr().Interface().(*int)
    *px = 2
    fmt.Print(x) //2
}
复制代码

首先调用Addr()方法,返回 一个持有指向变量的指针的Value;而后在Value上调用Interface()方法,返回一个 interface{},里面包含指向变量的指针;最后经过类型断言获得普通指针来修改变量的值。

经过反射调用函数

若是反射值对象(reflect.Value)持有值的类型为函数时,能够经过 reflect.Value 调用该函数。

func (v Value) Call(in []Value) []Value
复制代码

Call方法使用输入的参数in调用v持有的函数。参数in是反射值对象的切片,即[]reflect.Value;调用完成时,函数的返回值经过 []reflect.Value 返回。

package main 

import(
    "fmt"
    "reflect"
)
func add(a, b int) int {

    return a + b
}

func main() {

    // 将函数add包装为反射值对象
    funcValue := reflect.ValueOf(add)

    // 构造函数add的参数, 传入两个整型值
    paramList := []reflect.Value{reflect.ValueOf(5), reflect.ValueOf(10)}

    // 反射调用函数Call()
    retList := funcValue.Call(paramList)

    // 获取第一个返回值, 取整数值
    fmt.Println(retList[0].Int()) //返回 15
}
复制代码

若是须要经过反射调用结构体的方法,能够利用MethodByName方法来完成:

func (v Value) MethodByName(name string) Value
//返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装。
复制代码

举例:

package main 

import(
    "fmt"
    "reflect"
)

type Dog struct {
    Name string
    Age  int
}

func (dog *Dog) SetName(name string){
    dog.Name = name
}

func main() {
    dog := Dog{}
    rDog := reflect.ValueOf(&dog)
    paramList1 := []reflect.Value{reflect.ValueOf("doggy")}
    rDog.MethodByName("SetName").Call(paramList1)
    fmt.Println(dog.Name) //doggy
}
复制代码

值得注意的是,反射调用函数的过程须要构造大量的 reflect.Value 和中间变量,对函数参数值进行逐一检查,还须要将调用参数复制到调用函数的参数内存中。调用完毕后,还须要将返回值转换为 reflect.Value,用户还须要从中取出调用值。所以反射调用函数的性能问题尤其突出,不建议大量使用反射函数调用。

总结

本文介绍了Go语言中interface的定义、用法以及反作用,并由此引入reflect,经过大量示例详细介绍了reflect的概念,经过reflect获取值、修改值的用法,以及调用函数的用法。内容上能够说至关详实具体了,在此过程当中也让笔者本身对这部分的知识有了更深入的认识,也但愿有幸能带给读者一点帮助吧。

参考资料

【Golang标准库文档】

【Golang的反射reflect深刻理解和示例】

【Go addressable 详解】

相关文章
相关标签/搜索