searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

golang的interface与反射实现原理

2023-10-07 08:46:02
23
0

1、什么是interface

​ interface通过定义一系列的方法集合指定了其他类型的行为。对于一个类型,当且仅当它实现了某一个interface方法集的所有方法,我们称这个类型满足了这个interface。可以理解为interface是一个抽象的类型,这个类型定义了一系列的方法,当某个其他类型完全实现了该interface的所有的方法,可以称这个类型是当前抽象interface的一个实例。可以把interface理解为一个合约,方法即是合同细节,当其他类型满足了所有细节(方法),即可认为该类型满足了该interface。举个简单的例子:

type Reader interface {
	Read(p []byte) (n int, error)
}

​ io.Reader是io.go中定义的一个interface,那么,所有实现了Read方法的类型都称满足了Reader这个interface,都可以赋值给Reader这个interface。

2、interface的作用

​ 作为一个静态语言,go具有严格的类型的检查。inerface的引入,让go同时具备了动态语言的能力,而且更加便利:不要求像c++等语言去显示的声明类型实现了某个接口,只要类型实现了相关的接口即可。在赋值给某个interface的时候,编译器会去自动检查该类型是否满足该interface。

​ 举个例子,先随便定义一个接口,和以该接口为参数的函数:

type Language interface {
	SayWhoIAm()
}

fun WhoIAm (me Language) {
	me.SayWhoIAM()
}

​ 再定义两个结构体,分别实现了SayWhoIAm方法:

type GO struct{}

func (g GO) SayWhoIAm(){
	fmt.Println("I'm go language")
}

type CPP struct {}

func(cpp CPP)SayWhoIAm(){
	fmt.Println("I'm c plus plus language")
}

​ 在main函数调用:

func main (){
	go := GO{}
	cpp := CPP{}
	
	WhoIAm(go)
	WhoIAm(cpp)
}

程序输出:

I'm go language
I'm c plus plus language

​ 可以看到,GO结构体和CPP结构体并没有显示的声明实现了SayWhoIAm方法,只是在调用WhoIAm函数的时候传入了这两个对象。而这两个对象都拥有SayWhoIAm方法,从go的interface的角度看,认为这两个对象都实现了该interface。从某种意义上讲,interface可以理解为一些对象类型共有的方法集和共同的行为属性,依此interface使go拥有了动态语言的鸭子类型处理能力。后面会分析go如何能够动态的区分各个对象的运行时类型。

3、值与指针接受者

​ 方法能给用户自定义的类型添加自定义的行为。函数和它的区别是:方法比函数多了接收者。这个接收者可以是指针,也可以是值。参考前面的SayWhoIAm的方法,在GO结构体中他的接收者是指针,在CPP结构体中它的接收者是值。

​ 在调用的时候,二者并没有区别。值类型接收者既可以调用值接收者的方法,也可以调用指针接受者的方法;指针型接收者可以调用指针接收者的方法,也可以调用值接收者的方法。总的说来,不管方法类型的接收者是什么样的,值和指针对象都可以调用该对象所属类型所拥有的方法,不必严格遵循接收者的类型。

​ 接着上面的例子,给GO结构体加一个指针类型的方法,SayHello

func (g *GO) SayHello(){
	fmt.Println("hello world")
}

​ 在main函数里分别值调用指针类型方法,和指针调用值类型方法:

func main () {
	ptr := &GO{}
	value := GO{}
	
	ptr.SayHello()
	value.SayHello()
}

​ 那二者有何区别?编译器在背后帮我们做了处理。

  值接收者 指针接收者
值调用者 使用调用者的一个拷贝,类似值传递 使用值的引用来调用,value.SayHello()实际上是(&value).SayHello
指针调用者 指针被解引用为一个值ptr.SayHello()实际上是(*ptr).SayHello() 值传递,指针值的一份拷贝

当值调用者调用值接收者的方法时,值传递,是调用的一份拷贝。

当值调用者调用指针接收者的方法时,编译器会帮我们把值变成值的引用来调用指针接收者的方法。

当指针调用者调用值接收者的方法时,编译器会帮我们把指针解引用成值

当指针调用者调用指针接收者的方法时,值传递,传递的是指针值的一份拷贝。

以上分析表明值调用者可以调用指针接收者的方法,同样指针调用者也可以调用值接收者的方法。但二者还是有一点区别:

实现了接收者是值类型的方法,自动实现了接收者是指针类型的方法。但是实现了接收者是指针类型的方法,不会自动生成接收者是值类型的方法。

举个例子:

package main
import "fmt"
type TestInterface interface{
    Pointer()
    Value()
}

type Test struct {
    Name string

}

func(p Test)Value(){
    fmt.Println("value method")
}

func(p *Test)Pointer(){
    fmt.Println("pointer method")
}

func main(){
    var test TestInterface = &Test{}
    test.Value()
    test.Pointer()
}

可以正常运行得到结果:

如果把test := &Test{}改成 test := Test{},则会在编译的时候出现以下问题:


之所以有这种结论是因为,指针接收者能改变调用者值的属性,而值类型接收者没有办法改变调用者的属性。

所以值接收者类型实现了的方法,默认都实现了指针类型接收者的方法,但是指针接收者实现了的方法,值类型没有被默认实现。

4、iface和eface

​ go里面的interface分为两种:带接口的的iface和不带接口的eface。

​ 先看简单的eface,它的源码如下所示:

type eface struct {
	_type *_type
	data  unsafe.Pointer
}

type _type struct {
	size       uintptr
	ptrdata    uintptr // size of memory prefix holding all pointers
	hash       uint32
	tflag      tflag
	align      uint8
	fieldAlign uint8
	kind       uint8
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal func(unsafe.Pointer, unsafe.Pointer) bool
	// gcdata stores the GC type data for the garbage collector.
	// If the KindGCProg bit is set in kind, gcdata is a GC program.
	// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
	gcdata    *byte
	str       nameOff
	ptrToThis typeOff
}

​ 可以看到是比较简单的结构体,只包含两个成员变量,分别是 指向interface类型信息结构体的指针和指向interface所包含的值的空间的指针。

​ 再看带接口的iface,它的源码如下所示:

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

type itab struct {
	inter *interfacetype
	_type *_type
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [4]byte
	fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod
}

​ iface的整体结构如图所示:
​ iface包含两部分,itab结构和data结构。data结构与eface中的data结构一样,指向的是interface的值。itab结构中包含了interfacetype指针指向了该interface的静态类型,也就是该interface的类型,比如上文提到的TestInterface这个interface,以及这个interface所包含的方法集。itab的_type字段指向了该iface的动态类型类型,也就是我们把一个实现了该interface所有方法的interface赋值给当前interface的类型。fun字段指向了这个动态类型的函数集,这个函数集是大于静态类型的interface的方法集的。因为它可能包含自己一些另外的方法集。这些函数的集合以字母数序表做排序。至于为何这是一个动态数组,系统位数确定的情况下,函数指针大小是 固定的,往后照着排就可以了。

5、interface如何构建

示例代码:

package main
  
import "fmt"

type TestInterface interface{
        Pointer()
        Value()
}

type Test struct {
        Name string
}


func(p Test)Value(){
        fmt.Println("value method")
}

func(p Test)Pointer(){
        fmt.Println("pointer method")
}

func main(){
        i := 10
        var xx interface{} = i

        var test TestInterface = Test{"royalluo"}

        test.Value()
        test.Pointer()
        fmt.Println("", xx)
}

​ 执行go tool compile -S interface.go生成它的汇编代码,查看.main函数:

​ 第一个红框里面在构造空的interface,先把立即数10放在了sp保存的地址里面,然后sp的值加8放在Ax指针,为什么是加8,_type类型的指针大小刚好是8.第二个红框在构造带方法的的interface。

​ 这里有个疑问,翻看网上的资料,更早版本的go编译器生成的汇编代码,这里调用的是runtime里面的下面两个函数:

func convT2E64(t *_type, elem unsafe.Pointer) (e eface)
func convT2I(tab *itab, elem unsafe.Pointer) (i iface)

​ 不知道为何1.14版本以后这里给变了,还望知道的大神指导。

​ 在汇编中找到:

​ 大部分值都是空值,前16字节对应的两个interfacetype和_type指针都是空值,第16字节开始的97 fa 0d 59 是itab的哈希值,后面会做类型判断的时候会用到。接下来就是这个结构体的赋值操作。

​ 可以把这个hash值给打出来,手动构造我们自己的iface。

ackage main
  
import (
        "fmt"
        "unsafe")



type iface struct {
    tab  *itab
    data unsafe.Pointer
}
type itab struct {
    inter uintptr
    _type uintptr
    //link uintptr
    hash  uint32
    _     [4]byte
    fun   [1]uintptr
}


type TestInterface interface{
        Pointer()
        Value()
}

type Test struct {
        Name string
}


func(p Test)Value(){
        fmt.Println("value method")
}

func(p Test)Pointer(){
        fmt.Println("pointer method")
}

func main(){
        //var i EmptyInterface
        i := 10
        var xx interface{} = i

        var test TestInterface = Test{"royalluo"}
        iface := (*iface)(unsafe.Pointer(&test))
        fmt.Printf("iface.tab.hash = %#x\n", iface.tab.hash)
        test.Value()
        test.Pointer()
        fmt.Println("", xx)
}

​ 运行 查看打印结果:


与前面汇编生成的97 fa 0d 59 一模一样。这个哈希值应该只与特定结构的方法、字段等有关系。

6、动态类型与动态值

​ 不论是带方法的iface还是不带方法的eface,他们的值都包括两种类型:动态类型和动态值。其中iface的动态类型存在itab中,这里面又包含了静态的interfacetype和interface方法集以及动态的_type类型和动态的函数集合。其中动态的函数集合包含静态的interfacetype的方法集,不然当前动态类型的不能满足实现了所有该interface的方法。eface的动态类型存在__type类型中,因为它没有方法,只需要一个特定的类型即可。

​ 接口与0值的比较是当且仅当他的动态类型和动态值都为nil的时候,这个接口类型的值才是nil

package main
  
import "fmt"

func main() {
        var i interface{}

        fmt.Println(i == nil)

        fmt.Printf("%T,%v\n", i, i)
}

运行结果:

​ 给interface赋一个nil值

package main
  
import "fmt"

func main() {
        var i interface{}

        i = (*int)(nil)

        fmt.Println(i == nil)

        fmt.Printf("%T,%v\n", i, i)
}

运行结果:

可以参照前面的例子,手动的构建我们自己的iface结构体,然后打印出他的动态值。

package main
  
import (
        "fmt"
        "unsafe"
        )

type iface struct {
    itab, data uintptr
}


func main() {
        var j interface{}

        var i interface{}

        i = (*int)(nil)

        fmt.Println(i == nil)

        x := 5
        var c interface{} = (*int)(&x)

        ia := *(*iface)(unsafe.Pointer(&j))
        ib := *(*iface)(unsafe.Pointer(&i))
        ic := *(*iface)(unsafe.Pointer(&c))

        fmt.Println(ia)
        fmt.Println(ib)
        fmt.Println(ic)

        fmt.Println(ic.data)
        fmt.Println(*(*int)(unsafe.Pointer(ic.data)))

        fmt.Printf("%T,%v\n", i, i)
}

执行结果:

7、类型转换与断言

go是不支持隐式的类型转换的。=号两边不允许出现不同的类型。

类型转换仅支持互相兼容的两种类型

<结果类型> := <目标类型> (<表达式>)
比如上文里面的:
ia := *(*iface)(unsafe.Pointer(&j))

go的断言是针对interface的进行的。他的一个好处就是,我们可以在运行态的时候,动态的断言某个interface的类型。这样可以写出很灵活的代码。

package main
  
import (
        "fmt"
        "unsafe"
        )

type iface struct {
    itab, data uintptr
}


func main() {
        var j interface{}

        var i interface{}

        i = (*int)(nil)

        fmt.Println(i == nil)

        x := 5
        var c interface{} = (*int)(&x)

        ia := *(*iface)(unsafe.Pointer(&j))
        ib := *(*iface)(unsafe.Pointer(&i))
        ic := *(*iface)(unsafe.Pointer(&c))

        fmt.Println(ia)
        fmt.Println(ib)
        fmt.Println(ic)

        fmt.Println(ic.data)
        fmt.Println(*(*int)(unsafe.Pointer(ic.data)))

        fmt.Printf("%T,%v\n", i, i)
        judge(c)
}

func judge (i interface{}) {
        switch v := i.(type) {
                case *int:
                        fmt.Printf("type:%T, value:%v\n", v, v)

                default:
                        fmt.Println("unknown")
        }
}

执行结果:

 // 安全类型断言

<目标类型的值>,<布尔参数> := <表达式>.( 目标类型 )  

建议大家用这种比较安全的的断言方式,因为一旦断言失败,不会出现panic。

8、interface的转换

​ 前面说过,iface包括两部分静态的接口类型interfacetype和动态类型的_type。go编译器会帮我们自动匹配类型的方法集是否包含所有的接口类型的方法,来判定类型是否实现了接口的所有方法。假设接口有m个方法,类型有n方法,如果是没有排序好的方法集,时间复杂度是O(mn),排序好的方法能大幅减少这个时间复杂度。一个类型能转换为另一个类型的前提是它实现了转向类型的所有方法。举个例子:

package main

import "fmt"

type coder interface {
    code()
    run()
}

type runner interface {
    run()
}

type Gopher struct {
    language string
}

func (g Gopher) code() {
    return
}

func (g Gopher) run() {
    return
}

type Person struct {
		language string
}

func (p Person) run(){
		return 
}

func main() {
    var c coder = Gopher{}
		
    var r runner
    c = r
    fmt.Println(c, r)
}

go build一下:

r并不能转换为c,是因为c实现了code和run,也就是实现了coder 这个接口,但是r只有一个方法,是不满足c的。但如果我们换过来,这一行换成 r = c,就可以了。

查看它的汇编代码,发现main函数调用了runtime.convI2I函数。

找到他的源码,代码很简单。

func convI2I(inter *interfacetype, i iface) (r iface) {
	tab := i.tab
	if tab == nil {
		return
	}
	if tab.inter == inter {
		r.tab = tab
		r.data = i.data
		return
	}
	r.tab = getitab(inter, tab._type, false)
	r.data = i.data
	return
}

inter就是要转换的接口类型,i参数表示的是绑定了动态类型的iface,r表示转换类型后的iface。iface由两个字段组成:指向itab结构的指针tab字段和data字段。根据新的inter接口类型找到tab指针和data的值即可。

func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
	if len(inter.mhdr) == 0 {
		throw("internal error - misuse of itab")
	}

	// easy case
	if typ.tflag&tflagUncommon == 0 {
		if canfail {
			return nil
		}
		name := inter.typ.nameOff(inter.mhdr[0].name)
		panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
	}

	var m *itab

	// First, look in the existing table to see if we can find the itab we need.
	// This is by far the most common case, so do it without locks.
	// Use atomic to ensure we see any previous writes done by the thread
	// that updates the itabTable field (with atomic.Storep in itabAdd).
	t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
	if m = t.find(inter, typ); m != nil {
		goto finish
	}

	// Not found.  Grab the lock and try again.
	lock(&itabLock)
	if m = itabTable.find(inter, typ); m != nil {
		unlock(&itabLock)
		goto finish
	}

	// Entry doesn't exist yet. Make a new entry & add it.
	m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
	m.inter = inter
	m._type = typ
	// The hash is used in type switches. However, compiler statically generates itab's
	// for all interface/type pairs used in switches (which are added to itabTable
	// in itabsinit). The dynamically-generated itab's never participate in type switches,
	// and thus the hash is irrelevant.
	// Note: m.hash is _not_ the hash used for the runtime itabTable hash table.
	m.hash = 0
	m.init()
	itabAdd(m)
	unlock(&itabLock)
finish:
	if m.fun[0] != 0 {
		return m
	}
	if canfail {
		return nil
	}
	// this can only happen if the conversion
	// was already done once using the , ok form
	// and we have a cached negative result.
	// The cached result doesn't record which
	// interface function was missing, so initialize
	// the itab again to get the missing function name.
	panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}

前面介绍过itab的哈希值。这个函数会根据接口类型去全局的itabTable找,如果能找到就返回这个itab,并赋给iface;如果没有找到则去申请一片空间,生成一个并插入到全局的itabTable表中,方便一下查找。

9、go语言的多态

​ interface最核心的是帮go实现了多态的能力。所有实现了某个接口方法集的对象都可以直接赋值给这个接口,由运行态根据itab里面的_type的动态的决定调用哪个对象的方法。举个例子:

package main
  
import "fmt"

type Person interface {
        Myname()
}

type Student struct {
        Name string
}

type Programmer struct {
        Name string
}

type Worker struct {
        Name string
}

type Teacher struct {
        Name string
}

func (s Student) Myname() {
        fmt.Println("i am a student")
}

func (p Programmer)Myname() {
        fmt.Println("i am a programmer")
}

func (w Worker)Myname() {
        fmt.Println("i am a worker")
}
func (t Teacher)Myname() {
        fmt.Println("i am a teacher")
}

func printMyname(p Person) {
        p.Myname()
}

func main(){
        s := Student{}
        w := Worker{}
        p := Programmer{}
        t := Teacher{}


        printMyname(s)
        printMyname(w)
        printMyname(p)
        printMyname(t)
}

执行结果:

10、反射

​ 反射是go的一种高级特性,使我们有能力了解任意一个对象的动态结构信息。go的reflect包提供了反射的能力。

​ 反射的应用场景有两个:

​ 1、不确定函数的入参,可能是没有约定好,也可能是传入的类型很多,最典型的是fmt的Print相关的函数。这个时候反射的好处就来了。用一个interface接收任何入参,用反射动态的获取这个入参的类型。根据不同的类型,执行不同的打印。

​ 2、根据条件的不同调用不同的函数,此时用到反射可以写出很灵活的代码。依据反射,获取对象的动态类型,根据不同的动态类型,调用不同的函数执行不同的操作。

​ 基于以上两点,可以发现反射其实也是有不少缺点的:

​ 1、反射写出来的代码可读性比较差,一般逻辑都会比较绕,如果文档写的不好,维护起来很头疼

​ 2、既然是动态的根据对象类型处理,很多错误在编译的时候不会被发现,跑起来的时候只针对特定的情况才会panic,比较容易出现问题,而且还不容易排查。

​ 3、反射影响性能,在高并发和响应要求比较高的场景下,尤其严重。

​ 下面来分析一下反射的实现。

​ 回看第四小节iface的结构,比较重要,这里再重复一下,再次插入第四小节的图:

​ data是数据域存储的是接口的数据信息。

​ itab是类型相关的信息,它又包括两个字段,interfacetype表示的接口信息,它表示的是具体类型实现的接口类型。比如上面多态的例子,Person就是interfacetype里面表述的接口信息。_type表示的是具体的动态类型,比如当把Teacher赋给Person这个interface的时候,它的type里面的信息就是Teacher这个type。

​ eface就更加简单了,因为它没有方法集,所以任何对象都能赋值给它。type字段存储的就是具体对象的类型信息,data字段存储的就是它的值。

下面以go语言中最常用的Reader和Writer接口为例介绍。这里首先提一点,参考第8小节的interface的转换,一个interface能赋值给另外一个的前提是:它实现了被赋值对象的所有接口。

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

​ 这两个接口分别包含了Read 和 Write方法。

var r io.Reader
tty, err := os.OpenFile("xxoo.txt", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty

tty此时是一个*File类型的对象,这个对象实现了Read方法,所以能赋值给r这样一个接口,那此时r的iface结构里面的各项值如图所示:

​ 前面说过,接口类型的方法集一定是动态类型的方法集的一个子集,此时的动态方法集合是*os.File的集合,它包含不止Read方法,还包含其他诸如Write方法,那它能不能断言成为io.Writer,并且赋值给w。

var w io.Writer
w = r.(io.Writer)

​ 断言成功与否取决于动态类型。r的动态类型是*os.File,它是有Write方法的。因此可以断言成功。r可以赋值给w。w的内存值为:

空interface没有任何方法集的要求,所以任何值都可以赋值给一个空的interface。

var empty interface{}
empty = w

​ empty的内存结构如下图所示:

​ 按照前文手动构建自己iface结构的方法,

type iface struct {
    tab  *itab
    data unsafe.Pointer
}
type itab struct {
    inter uintptr
    _type uintptr
    //link uintptr
    hash  uint32
    _     [4]byte
    fun   [1]uintptr
}

​ 我们可以把这些动态值都打出来了,这里就不在贴代码了,有兴趣的可以参照上面的的代码试试。

reflect包中提供了两个基础的反射函数,获取上述动态的类型和值的信息的接口。

func TypeOf(i interface{}) Type 
func ValueOf(i interface{}) Value

​ 先看valueOf函数,返回了一个Value结构体,用于获取这个接口的值:

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
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)
}

再看看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.
	// The lowest bits are flag bits:
	//	- flagStickyRO: obtained via unexported not embedded field, so read-only
	//	- flagEmbedRO: obtained via unexported embedded field, so read-only
	//	- flagIndir: val holds a pointer to the data
	//	- flagAddr: v.CanAddr is true (implies flagIndir)
	//	- flagMethod: v is a method value.
	// The next five bits give the Kind of the value.
	// This repeats typ.Kind() except for method values.
	// The remaining 23+ bits give a method number for method values.
	// If flag.kind() != Func, code can assume that flagMethod is unset.
	// If ifaceIndir(typ), code can assume that flagIndir is set.
	flag

	// A method value represents a curried method invocation
	// like r.Read for some receiver r. The typ+val+flag bits describe
	// the receiver r, but the flag's Kind bits say Func (methods are
	// functions), and the top bits of the flag give the method number
	// in r's type's method table.
}

​ 在value.go里面这个结构体封装了很多方法,帮我们把value的值转换成我们想要的值,随便截取两个举个例子。有兴趣的可以去看源码:

// Bool returns v's underlying value.
// It panics if v's kind is not Bool.
func (v Value) Bool() bool {
	v.mustBe(Bool)
	return *(*bool)(v.ptr)
}

// Bytes returns v's underlying value.
// It panics if v's underlying value is not a slice of bytes.
func (v Value) Bytes() []byte {
	v.mustBe(Slice)
	if v.typ.Elem().Kind() != Uint8 {
		panic("reflect.Value.Bytes of non-byte slice")
	}
	// Slice is always bigger than a word; assume flagIndir.
	return *(*[]byte)(v.ptr)
}

​ 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)
}

// toType converts from a *rtype to a Type that can be returned
// to the client of package reflect. In gc, the only concern is that
// a nil *rtype must be replaced by a nil Type, but in gccgo this
// function takes care of ensuring that multiple *rtype for the same
// type are coalesced into a single Type.
func toType(t *rtype) Type {
	if t == nil {
		return nil
	}
	return t
}

​ 返回的type是一个接口,包含以下方法:

// Type is the representation of a Go type.
//
// Not all methods apply to all kinds of types. Restrictions,
// if any, are noted in the documentation for each method.
// Use the Kind method to find out the kind of type before
// calling kind-specific methods. Calling a method
// inappropriate to the kind of type causes a run-time panic.
//
// Type values are comparable, such as with the == operator,
// so they can be used as map keys.
// Two Type values are equal if they represent identical types.
type Type interface {
	// Methods applicable to all types.

	// Align returns the alignment in bytes of a value of
	// this type when allocated in memory.
	Align() int

	// FieldAlign returns the alignment in bytes of a value of
	// this type when used as a field in a struct.
	FieldAlign() int

	// Method returns the i'th method in the type's method set.
	// It panics if i is not in the range [0, NumMethod()).
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	//
	// Only exported methods are accessible and they are sorted in
	// lexicographic order.
	Method(int) Method

	// MethodByName returns the method with that name in the type's
	// method set and a boolean indicating if the method was found.
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	MethodByName(string) (Method, bool)

	// NumMethod returns the number of exported methods in the type's method set.
	NumMethod() int

	// Name returns the type's name within its package for a defined type.
	// For other (non-defined) types it returns the empty string.
	Name() string

	// PkgPath returns a defined type's package path, that is, the import path
	// that uniquely identifies the package, such as "encoding/base64".
	// If the type was predeclared (string, error) or not defined (*T, struct{},
	// []int, or A where A is an alias for a non-defined type), the package path
	// will be the empty string.
	PkgPath() string

	// Size returns the number of bytes needed to store
	// a value of the given type; it is analogous to unsafe.Sizeof.
	Size() uintptr

	// String returns a string representation of the type.
	// The string representation may use shortened package names
	// (e.g., base64 instead of "encoding/base64") and is not
	// guaranteed to be unique among types. To test for type identity,
	// compare the Types directly.
	String() string

	// Kind returns the specific kind of this type.
	Kind() Kind

	// Implements reports whether the type implements the interface type u.
	Implements(u Type) bool

	// AssignableTo reports whether a value of the type is assignable to type u.
	AssignableTo(u Type) bool

	// ConvertibleTo reports whether a value of the type is convertible to type u.
	ConvertibleTo(u Type) bool

	// Comparable reports whether values of this type are comparable.
	Comparable() bool

	// Methods applicable only to some types, depending on Kind.
	// The methods allowed for each kind are:
	//
	//	Int*, Uint*, Float*, Complex*: Bits
	//	Array: Elem, Len
	//	Chan: ChanDir, Elem
	//	Func: In, NumIn, Out, NumOut, IsVariadic.
	//	Map: Key, Elem
	//	Ptr: Elem
	//	Slice: Elem
	//	Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField

	// Bits returns the size of the type in bits.
	// It panics if the type's Kind is not one of the
	// sized or unsized Int, Uint, Float, or Complex kinds.
	Bits() int

	// ChanDir returns a channel type's direction.
	// It panics if the type's Kind is not Chan.
	ChanDir() ChanDir

	// IsVariadic reports whether a function type's final input parameter
	// is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's
	// implicit actual type []T.
	//
	// For concreteness, if t represents func(x int, y ... float64), then
	//
	//	t.NumIn() == 2
	//	t.In(0) is the reflect.Type for "int"
	//	t.In(1) is the reflect.Type for "[]float64"
	//	t.IsVariadic() == true
	//
	// IsVariadic panics if the type's Kind is not Func.
	IsVariadic() bool

	// Elem returns a type's element type.
	// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
	Elem() Type

	// Field returns a struct type's i'th field.
	// It panics if the type's Kind is not Struct.
	// It panics if i is not in the range [0, NumField()).
	Field(i int) StructField

	// FieldByIndex returns the nested field corresponding
	// to the index sequence. It is equivalent to calling Field
	// successively for each index i.
	// It panics if the type's Kind is not Struct.
	FieldByIndex(index []int) StructField

	// FieldByName returns the struct field with the given name
	// and a boolean indicating if the field was found.
	FieldByName(name string) (StructField, bool)

	// FieldByNameFunc returns the struct field with a name
	// that satisfies the match function and a boolean indicating if
	// the field was found.
	//
	// FieldByNameFunc considers the fields in the struct itself
	// and then the fields in any embedded structs, in breadth first order,
	// stopping at the shallowest nesting depth containing one or more
	// fields satisfying the match function. If multiple fields at that depth
	// satisfy the match function, they cancel each other
	// and FieldByNameFunc returns no match.
	// This behavior mirrors Go's handling of name lookup in
	// structs containing embedded fields.
	FieldByNameFunc(match func(string) bool) (StructField, bool)

	// In returns the type of a function type's i'th input parameter.
	// It panics if the type's Kind is not Func.
	// It panics if i is not in the range [0, NumIn()).
	In(i int) Type

	// Key returns a map type's key type.
	// It panics if the type's Kind is not Map.
	Key() Type

	// Len returns an array type's length.
	// It panics if the type's Kind is not Array.
	Len() int

	// NumField returns a struct type's field count.
	// It panics if the type's Kind is not Struct.
	NumField() int

	// NumIn returns a function type's input parameter count.
	// It panics if the type's Kind is not Func.
	NumIn() int

	// NumOut returns a function type's output parameter count.
	// It panics if the type's Kind is not Func.
	NumOut() int

	// Out returns the type of a function type's i'th output parameter.
	// It panics if the type's Kind is not Func.
	// It panics if i is not in the range [0, NumOut()).
	Out(i int) Type

	common() *rtype
	uncommon() *uncommonType
}

​ 结合之前讲的多态的能力,返回的这个接口,我们可以调用它的任何方法。它的动态值是任何实现了该接口的对象,而go能在调用的时候动态的知道是执行的哪个对象的方法。

​ 通过 Type() 方法和 Interface() 方法可以打通 interfaceTypeValue 三者。Type() 方法也可以返回变量的类型信息,与 reflect.TypeOf() 函数等价。Interface() 方法可以将 Value 还原成原来的 interface。

​ TypeOf() 函数返回一个接口,这个接口定义了一系列方法,利用这些方法可以获取关于类型的所有信息; ValueOf() 函数返回一个结构体变量,包含类型信息以及实际值。

​ 根据 Go 官方关于反射的博客,反射有三大定律:

  1. Reflection goes from interface value to reflection object.

  2. Reflection goes from reflection object to interface value.

  3. To modify a reflection object, the value must be settable.

    第一条:反射能从interface反射出对象,也就是利用reflect.ValueOf和reflect.TypeOf从interface反射出对象的具体信息。

    第二条:反射能从对象构建出interface,也就是通过interface()函数从值反射出interface。也就是跟第一条反着来。

    第三条:要修改一个反射对象,它的值必须是能修改的。简单点说,就是通过反射,必须反射出它的本身,而不是它的一份拷贝,那怎么反射出它的本身,和我们函数传参的时候很像:地址。举个例子:

    var x int = 8
    v := reflect.ValueOf(x)
    v.SetInt(24) // Error: will panic.
    

    x只是v的一个拷贝,对v的修改反应不到x上,所以会panic。

    将上文的v := reflect.ValueOf(x)改为v := reflect.ValueOf(&x)即可。

​ v 还不是代表 x,v.Elem() 才真正代表 v,这样就可以真正操作 v了:

​ 如果想要操作原变量,反射变量 Value 必须要 hold 住原变量的地址才行。

​ 利用反射机制,对于结构体中未导出成员(也就是小写开头),可以读取,但不能修改其值。

​ 通过反射,结构体中可以被修改的成员只有是导出成员,也就是字段名的首字母是大写的。

反射目前在项目中的应用:

​ 综治的接口路由、自动建表,批量任务框架

​ 参数解包回包的序列化与反序列化

 
0条评论
0 / 1000
罗****艺
3文章数
0粉丝数
罗****艺
3 文章 | 0 粉丝
罗****艺
3文章数
0粉丝数
罗****艺
3 文章 | 0 粉丝
原创

golang的interface与反射实现原理

2023-10-07 08:46:02
23
0

1、什么是interface

​ interface通过定义一系列的方法集合指定了其他类型的行为。对于一个类型,当且仅当它实现了某一个interface方法集的所有方法,我们称这个类型满足了这个interface。可以理解为interface是一个抽象的类型,这个类型定义了一系列的方法,当某个其他类型完全实现了该interface的所有的方法,可以称这个类型是当前抽象interface的一个实例。可以把interface理解为一个合约,方法即是合同细节,当其他类型满足了所有细节(方法),即可认为该类型满足了该interface。举个简单的例子:

type Reader interface {
	Read(p []byte) (n int, error)
}

​ io.Reader是io.go中定义的一个interface,那么,所有实现了Read方法的类型都称满足了Reader这个interface,都可以赋值给Reader这个interface。

2、interface的作用

​ 作为一个静态语言,go具有严格的类型的检查。inerface的引入,让go同时具备了动态语言的能力,而且更加便利:不要求像c++等语言去显示的声明类型实现了某个接口,只要类型实现了相关的接口即可。在赋值给某个interface的时候,编译器会去自动检查该类型是否满足该interface。

​ 举个例子,先随便定义一个接口,和以该接口为参数的函数:

type Language interface {
	SayWhoIAm()
}

fun WhoIAm (me Language) {
	me.SayWhoIAM()
}

​ 再定义两个结构体,分别实现了SayWhoIAm方法:

type GO struct{}

func (g GO) SayWhoIAm(){
	fmt.Println("I'm go language")
}

type CPP struct {}

func(cpp CPP)SayWhoIAm(){
	fmt.Println("I'm c plus plus language")
}

​ 在main函数调用:

func main (){
	go := GO{}
	cpp := CPP{}
	
	WhoIAm(go)
	WhoIAm(cpp)
}

程序输出:

I'm go language
I'm c plus plus language

​ 可以看到,GO结构体和CPP结构体并没有显示的声明实现了SayWhoIAm方法,只是在调用WhoIAm函数的时候传入了这两个对象。而这两个对象都拥有SayWhoIAm方法,从go的interface的角度看,认为这两个对象都实现了该interface。从某种意义上讲,interface可以理解为一些对象类型共有的方法集和共同的行为属性,依此interface使go拥有了动态语言的鸭子类型处理能力。后面会分析go如何能够动态的区分各个对象的运行时类型。

3、值与指针接受者

​ 方法能给用户自定义的类型添加自定义的行为。函数和它的区别是:方法比函数多了接收者。这个接收者可以是指针,也可以是值。参考前面的SayWhoIAm的方法,在GO结构体中他的接收者是指针,在CPP结构体中它的接收者是值。

​ 在调用的时候,二者并没有区别。值类型接收者既可以调用值接收者的方法,也可以调用指针接受者的方法;指针型接收者可以调用指针接收者的方法,也可以调用值接收者的方法。总的说来,不管方法类型的接收者是什么样的,值和指针对象都可以调用该对象所属类型所拥有的方法,不必严格遵循接收者的类型。

​ 接着上面的例子,给GO结构体加一个指针类型的方法,SayHello

func (g *GO) SayHello(){
	fmt.Println("hello world")
}

​ 在main函数里分别值调用指针类型方法,和指针调用值类型方法:

func main () {
	ptr := &GO{}
	value := GO{}
	
	ptr.SayHello()
	value.SayHello()
}

​ 那二者有何区别?编译器在背后帮我们做了处理。

  值接收者 指针接收者
值调用者 使用调用者的一个拷贝,类似值传递 使用值的引用来调用,value.SayHello()实际上是(&value).SayHello
指针调用者 指针被解引用为一个值ptr.SayHello()实际上是(*ptr).SayHello() 值传递,指针值的一份拷贝

当值调用者调用值接收者的方法时,值传递,是调用的一份拷贝。

当值调用者调用指针接收者的方法时,编译器会帮我们把值变成值的引用来调用指针接收者的方法。

当指针调用者调用值接收者的方法时,编译器会帮我们把指针解引用成值

当指针调用者调用指针接收者的方法时,值传递,传递的是指针值的一份拷贝。

以上分析表明值调用者可以调用指针接收者的方法,同样指针调用者也可以调用值接收者的方法。但二者还是有一点区别:

实现了接收者是值类型的方法,自动实现了接收者是指针类型的方法。但是实现了接收者是指针类型的方法,不会自动生成接收者是值类型的方法。

举个例子:

package main
import "fmt"
type TestInterface interface{
    Pointer()
    Value()
}

type Test struct {
    Name string

}

func(p Test)Value(){
    fmt.Println("value method")
}

func(p *Test)Pointer(){
    fmt.Println("pointer method")
}

func main(){
    var test TestInterface = &Test{}
    test.Value()
    test.Pointer()
}

可以正常运行得到结果:

如果把test := &Test{}改成 test := Test{},则会在编译的时候出现以下问题:


之所以有这种结论是因为,指针接收者能改变调用者值的属性,而值类型接收者没有办法改变调用者的属性。

所以值接收者类型实现了的方法,默认都实现了指针类型接收者的方法,但是指针接收者实现了的方法,值类型没有被默认实现。

4、iface和eface

​ go里面的interface分为两种:带接口的的iface和不带接口的eface。

​ 先看简单的eface,它的源码如下所示:

type eface struct {
	_type *_type
	data  unsafe.Pointer
}

type _type struct {
	size       uintptr
	ptrdata    uintptr // size of memory prefix holding all pointers
	hash       uint32
	tflag      tflag
	align      uint8
	fieldAlign uint8
	kind       uint8
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal func(unsafe.Pointer, unsafe.Pointer) bool
	// gcdata stores the GC type data for the garbage collector.
	// If the KindGCProg bit is set in kind, gcdata is a GC program.
	// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
	gcdata    *byte
	str       nameOff
	ptrToThis typeOff
}

​ 可以看到是比较简单的结构体,只包含两个成员变量,分别是 指向interface类型信息结构体的指针和指向interface所包含的值的空间的指针。

​ 再看带接口的iface,它的源码如下所示:

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

type itab struct {
	inter *interfacetype
	_type *_type
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [4]byte
	fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod
}

​ iface的整体结构如图所示:
​ iface包含两部分,itab结构和data结构。data结构与eface中的data结构一样,指向的是interface的值。itab结构中包含了interfacetype指针指向了该interface的静态类型,也就是该interface的类型,比如上文提到的TestInterface这个interface,以及这个interface所包含的方法集。itab的_type字段指向了该iface的动态类型类型,也就是我们把一个实现了该interface所有方法的interface赋值给当前interface的类型。fun字段指向了这个动态类型的函数集,这个函数集是大于静态类型的interface的方法集的。因为它可能包含自己一些另外的方法集。这些函数的集合以字母数序表做排序。至于为何这是一个动态数组,系统位数确定的情况下,函数指针大小是 固定的,往后照着排就可以了。

5、interface如何构建

示例代码:

package main
  
import "fmt"

type TestInterface interface{
        Pointer()
        Value()
}

type Test struct {
        Name string
}


func(p Test)Value(){
        fmt.Println("value method")
}

func(p Test)Pointer(){
        fmt.Println("pointer method")
}

func main(){
        i := 10
        var xx interface{} = i

        var test TestInterface = Test{"royalluo"}

        test.Value()
        test.Pointer()
        fmt.Println("", xx)
}

​ 执行go tool compile -S interface.go生成它的汇编代码,查看.main函数:

​ 第一个红框里面在构造空的interface,先把立即数10放在了sp保存的地址里面,然后sp的值加8放在Ax指针,为什么是加8,_type类型的指针大小刚好是8.第二个红框在构造带方法的的interface。

​ 这里有个疑问,翻看网上的资料,更早版本的go编译器生成的汇编代码,这里调用的是runtime里面的下面两个函数:

func convT2E64(t *_type, elem unsafe.Pointer) (e eface)
func convT2I(tab *itab, elem unsafe.Pointer) (i iface)

​ 不知道为何1.14版本以后这里给变了,还望知道的大神指导。

​ 在汇编中找到:

​ 大部分值都是空值,前16字节对应的两个interfacetype和_type指针都是空值,第16字节开始的97 fa 0d 59 是itab的哈希值,后面会做类型判断的时候会用到。接下来就是这个结构体的赋值操作。

​ 可以把这个hash值给打出来,手动构造我们自己的iface。

ackage main
  
import (
        "fmt"
        "unsafe")



type iface struct {
    tab  *itab
    data unsafe.Pointer
}
type itab struct {
    inter uintptr
    _type uintptr
    //link uintptr
    hash  uint32
    _     [4]byte
    fun   [1]uintptr
}


type TestInterface interface{
        Pointer()
        Value()
}

type Test struct {
        Name string
}


func(p Test)Value(){
        fmt.Println("value method")
}

func(p Test)Pointer(){
        fmt.Println("pointer method")
}

func main(){
        //var i EmptyInterface
        i := 10
        var xx interface{} = i

        var test TestInterface = Test{"royalluo"}
        iface := (*iface)(unsafe.Pointer(&test))
        fmt.Printf("iface.tab.hash = %#x\n", iface.tab.hash)
        test.Value()
        test.Pointer()
        fmt.Println("", xx)
}

​ 运行 查看打印结果:


与前面汇编生成的97 fa 0d 59 一模一样。这个哈希值应该只与特定结构的方法、字段等有关系。

6、动态类型与动态值

​ 不论是带方法的iface还是不带方法的eface,他们的值都包括两种类型:动态类型和动态值。其中iface的动态类型存在itab中,这里面又包含了静态的interfacetype和interface方法集以及动态的_type类型和动态的函数集合。其中动态的函数集合包含静态的interfacetype的方法集,不然当前动态类型的不能满足实现了所有该interface的方法。eface的动态类型存在__type类型中,因为它没有方法,只需要一个特定的类型即可。

​ 接口与0值的比较是当且仅当他的动态类型和动态值都为nil的时候,这个接口类型的值才是nil

package main
  
import "fmt"

func main() {
        var i interface{}

        fmt.Println(i == nil)

        fmt.Printf("%T,%v\n", i, i)
}

运行结果:

​ 给interface赋一个nil值

package main
  
import "fmt"

func main() {
        var i interface{}

        i = (*int)(nil)

        fmt.Println(i == nil)

        fmt.Printf("%T,%v\n", i, i)
}

运行结果:

可以参照前面的例子,手动的构建我们自己的iface结构体,然后打印出他的动态值。

package main
  
import (
        "fmt"
        "unsafe"
        )

type iface struct {
    itab, data uintptr
}


func main() {
        var j interface{}

        var i interface{}

        i = (*int)(nil)

        fmt.Println(i == nil)

        x := 5
        var c interface{} = (*int)(&x)

        ia := *(*iface)(unsafe.Pointer(&j))
        ib := *(*iface)(unsafe.Pointer(&i))
        ic := *(*iface)(unsafe.Pointer(&c))

        fmt.Println(ia)
        fmt.Println(ib)
        fmt.Println(ic)

        fmt.Println(ic.data)
        fmt.Println(*(*int)(unsafe.Pointer(ic.data)))

        fmt.Printf("%T,%v\n", i, i)
}

执行结果:

7、类型转换与断言

go是不支持隐式的类型转换的。=号两边不允许出现不同的类型。

类型转换仅支持互相兼容的两种类型

<结果类型> := <目标类型> (<表达式>)
比如上文里面的:
ia := *(*iface)(unsafe.Pointer(&j))

go的断言是针对interface的进行的。他的一个好处就是,我们可以在运行态的时候,动态的断言某个interface的类型。这样可以写出很灵活的代码。

package main
  
import (
        "fmt"
        "unsafe"
        )

type iface struct {
    itab, data uintptr
}


func main() {
        var j interface{}

        var i interface{}

        i = (*int)(nil)

        fmt.Println(i == nil)

        x := 5
        var c interface{} = (*int)(&x)

        ia := *(*iface)(unsafe.Pointer(&j))
        ib := *(*iface)(unsafe.Pointer(&i))
        ic := *(*iface)(unsafe.Pointer(&c))

        fmt.Println(ia)
        fmt.Println(ib)
        fmt.Println(ic)

        fmt.Println(ic.data)
        fmt.Println(*(*int)(unsafe.Pointer(ic.data)))

        fmt.Printf("%T,%v\n", i, i)
        judge(c)
}

func judge (i interface{}) {
        switch v := i.(type) {
                case *int:
                        fmt.Printf("type:%T, value:%v\n", v, v)

                default:
                        fmt.Println("unknown")
        }
}

执行结果:

 // 安全类型断言

<目标类型的值>,<布尔参数> := <表达式>.( 目标类型 )  

建议大家用这种比较安全的的断言方式,因为一旦断言失败,不会出现panic。

8、interface的转换

​ 前面说过,iface包括两部分静态的接口类型interfacetype和动态类型的_type。go编译器会帮我们自动匹配类型的方法集是否包含所有的接口类型的方法,来判定类型是否实现了接口的所有方法。假设接口有m个方法,类型有n方法,如果是没有排序好的方法集,时间复杂度是O(mn),排序好的方法能大幅减少这个时间复杂度。一个类型能转换为另一个类型的前提是它实现了转向类型的所有方法。举个例子:

package main

import "fmt"

type coder interface {
    code()
    run()
}

type runner interface {
    run()
}

type Gopher struct {
    language string
}

func (g Gopher) code() {
    return
}

func (g Gopher) run() {
    return
}

type Person struct {
		language string
}

func (p Person) run(){
		return 
}

func main() {
    var c coder = Gopher{}
		
    var r runner
    c = r
    fmt.Println(c, r)
}

go build一下:

r并不能转换为c,是因为c实现了code和run,也就是实现了coder 这个接口,但是r只有一个方法,是不满足c的。但如果我们换过来,这一行换成 r = c,就可以了。

查看它的汇编代码,发现main函数调用了runtime.convI2I函数。

找到他的源码,代码很简单。

func convI2I(inter *interfacetype, i iface) (r iface) {
	tab := i.tab
	if tab == nil {
		return
	}
	if tab.inter == inter {
		r.tab = tab
		r.data = i.data
		return
	}
	r.tab = getitab(inter, tab._type, false)
	r.data = i.data
	return
}

inter就是要转换的接口类型,i参数表示的是绑定了动态类型的iface,r表示转换类型后的iface。iface由两个字段组成:指向itab结构的指针tab字段和data字段。根据新的inter接口类型找到tab指针和data的值即可。

func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
	if len(inter.mhdr) == 0 {
		throw("internal error - misuse of itab")
	}

	// easy case
	if typ.tflag&tflagUncommon == 0 {
		if canfail {
			return nil
		}
		name := inter.typ.nameOff(inter.mhdr[0].name)
		panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
	}

	var m *itab

	// First, look in the existing table to see if we can find the itab we need.
	// This is by far the most common case, so do it without locks.
	// Use atomic to ensure we see any previous writes done by the thread
	// that updates the itabTable field (with atomic.Storep in itabAdd).
	t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
	if m = t.find(inter, typ); m != nil {
		goto finish
	}

	// Not found.  Grab the lock and try again.
	lock(&itabLock)
	if m = itabTable.find(inter, typ); m != nil {
		unlock(&itabLock)
		goto finish
	}

	// Entry doesn't exist yet. Make a new entry & add it.
	m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
	m.inter = inter
	m._type = typ
	// The hash is used in type switches. However, compiler statically generates itab's
	// for all interface/type pairs used in switches (which are added to itabTable
	// in itabsinit). The dynamically-generated itab's never participate in type switches,
	// and thus the hash is irrelevant.
	// Note: m.hash is _not_ the hash used for the runtime itabTable hash table.
	m.hash = 0
	m.init()
	itabAdd(m)
	unlock(&itabLock)
finish:
	if m.fun[0] != 0 {
		return m
	}
	if canfail {
		return nil
	}
	// this can only happen if the conversion
	// was already done once using the , ok form
	// and we have a cached negative result.
	// The cached result doesn't record which
	// interface function was missing, so initialize
	// the itab again to get the missing function name.
	panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}

前面介绍过itab的哈希值。这个函数会根据接口类型去全局的itabTable找,如果能找到就返回这个itab,并赋给iface;如果没有找到则去申请一片空间,生成一个并插入到全局的itabTable表中,方便一下查找。

9、go语言的多态

​ interface最核心的是帮go实现了多态的能力。所有实现了某个接口方法集的对象都可以直接赋值给这个接口,由运行态根据itab里面的_type的动态的决定调用哪个对象的方法。举个例子:

package main
  
import "fmt"

type Person interface {
        Myname()
}

type Student struct {
        Name string
}

type Programmer struct {
        Name string
}

type Worker struct {
        Name string
}

type Teacher struct {
        Name string
}

func (s Student) Myname() {
        fmt.Println("i am a student")
}

func (p Programmer)Myname() {
        fmt.Println("i am a programmer")
}

func (w Worker)Myname() {
        fmt.Println("i am a worker")
}
func (t Teacher)Myname() {
        fmt.Println("i am a teacher")
}

func printMyname(p Person) {
        p.Myname()
}

func main(){
        s := Student{}
        w := Worker{}
        p := Programmer{}
        t := Teacher{}


        printMyname(s)
        printMyname(w)
        printMyname(p)
        printMyname(t)
}

执行结果:

10、反射

​ 反射是go的一种高级特性,使我们有能力了解任意一个对象的动态结构信息。go的reflect包提供了反射的能力。

​ 反射的应用场景有两个:

​ 1、不确定函数的入参,可能是没有约定好,也可能是传入的类型很多,最典型的是fmt的Print相关的函数。这个时候反射的好处就来了。用一个interface接收任何入参,用反射动态的获取这个入参的类型。根据不同的类型,执行不同的打印。

​ 2、根据条件的不同调用不同的函数,此时用到反射可以写出很灵活的代码。依据反射,获取对象的动态类型,根据不同的动态类型,调用不同的函数执行不同的操作。

​ 基于以上两点,可以发现反射其实也是有不少缺点的:

​ 1、反射写出来的代码可读性比较差,一般逻辑都会比较绕,如果文档写的不好,维护起来很头疼

​ 2、既然是动态的根据对象类型处理,很多错误在编译的时候不会被发现,跑起来的时候只针对特定的情况才会panic,比较容易出现问题,而且还不容易排查。

​ 3、反射影响性能,在高并发和响应要求比较高的场景下,尤其严重。

​ 下面来分析一下反射的实现。

​ 回看第四小节iface的结构,比较重要,这里再重复一下,再次插入第四小节的图:

​ data是数据域存储的是接口的数据信息。

​ itab是类型相关的信息,它又包括两个字段,interfacetype表示的接口信息,它表示的是具体类型实现的接口类型。比如上面多态的例子,Person就是interfacetype里面表述的接口信息。_type表示的是具体的动态类型,比如当把Teacher赋给Person这个interface的时候,它的type里面的信息就是Teacher这个type。

​ eface就更加简单了,因为它没有方法集,所以任何对象都能赋值给它。type字段存储的就是具体对象的类型信息,data字段存储的就是它的值。

下面以go语言中最常用的Reader和Writer接口为例介绍。这里首先提一点,参考第8小节的interface的转换,一个interface能赋值给另外一个的前提是:它实现了被赋值对象的所有接口。

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

​ 这两个接口分别包含了Read 和 Write方法。

var r io.Reader
tty, err := os.OpenFile("xxoo.txt", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty

tty此时是一个*File类型的对象,这个对象实现了Read方法,所以能赋值给r这样一个接口,那此时r的iface结构里面的各项值如图所示:

​ 前面说过,接口类型的方法集一定是动态类型的方法集的一个子集,此时的动态方法集合是*os.File的集合,它包含不止Read方法,还包含其他诸如Write方法,那它能不能断言成为io.Writer,并且赋值给w。

var w io.Writer
w = r.(io.Writer)

​ 断言成功与否取决于动态类型。r的动态类型是*os.File,它是有Write方法的。因此可以断言成功。r可以赋值给w。w的内存值为:

空interface没有任何方法集的要求,所以任何值都可以赋值给一个空的interface。

var empty interface{}
empty = w

​ empty的内存结构如下图所示:

​ 按照前文手动构建自己iface结构的方法,

type iface struct {
    tab  *itab
    data unsafe.Pointer
}
type itab struct {
    inter uintptr
    _type uintptr
    //link uintptr
    hash  uint32
    _     [4]byte
    fun   [1]uintptr
}

​ 我们可以把这些动态值都打出来了,这里就不在贴代码了,有兴趣的可以参照上面的的代码试试。

reflect包中提供了两个基础的反射函数,获取上述动态的类型和值的信息的接口。

func TypeOf(i interface{}) Type 
func ValueOf(i interface{}) Value

​ 先看valueOf函数,返回了一个Value结构体,用于获取这个接口的值:

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
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)
}

再看看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.
	// The lowest bits are flag bits:
	//	- flagStickyRO: obtained via unexported not embedded field, so read-only
	//	- flagEmbedRO: obtained via unexported embedded field, so read-only
	//	- flagIndir: val holds a pointer to the data
	//	- flagAddr: v.CanAddr is true (implies flagIndir)
	//	- flagMethod: v is a method value.
	// The next five bits give the Kind of the value.
	// This repeats typ.Kind() except for method values.
	// The remaining 23+ bits give a method number for method values.
	// If flag.kind() != Func, code can assume that flagMethod is unset.
	// If ifaceIndir(typ), code can assume that flagIndir is set.
	flag

	// A method value represents a curried method invocation
	// like r.Read for some receiver r. The typ+val+flag bits describe
	// the receiver r, but the flag's Kind bits say Func (methods are
	// functions), and the top bits of the flag give the method number
	// in r's type's method table.
}

​ 在value.go里面这个结构体封装了很多方法,帮我们把value的值转换成我们想要的值,随便截取两个举个例子。有兴趣的可以去看源码:

// Bool returns v's underlying value.
// It panics if v's kind is not Bool.
func (v Value) Bool() bool {
	v.mustBe(Bool)
	return *(*bool)(v.ptr)
}

// Bytes returns v's underlying value.
// It panics if v's underlying value is not a slice of bytes.
func (v Value) Bytes() []byte {
	v.mustBe(Slice)
	if v.typ.Elem().Kind() != Uint8 {
		panic("reflect.Value.Bytes of non-byte slice")
	}
	// Slice is always bigger than a word; assume flagIndir.
	return *(*[]byte)(v.ptr)
}

​ 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)
}

// toType converts from a *rtype to a Type that can be returned
// to the client of package reflect. In gc, the only concern is that
// a nil *rtype must be replaced by a nil Type, but in gccgo this
// function takes care of ensuring that multiple *rtype for the same
// type are coalesced into a single Type.
func toType(t *rtype) Type {
	if t == nil {
		return nil
	}
	return t
}

​ 返回的type是一个接口,包含以下方法:

// Type is the representation of a Go type.
//
// Not all methods apply to all kinds of types. Restrictions,
// if any, are noted in the documentation for each method.
// Use the Kind method to find out the kind of type before
// calling kind-specific methods. Calling a method
// inappropriate to the kind of type causes a run-time panic.
//
// Type values are comparable, such as with the == operator,
// so they can be used as map keys.
// Two Type values are equal if they represent identical types.
type Type interface {
	// Methods applicable to all types.

	// Align returns the alignment in bytes of a value of
	// this type when allocated in memory.
	Align() int

	// FieldAlign returns the alignment in bytes of a value of
	// this type when used as a field in a struct.
	FieldAlign() int

	// Method returns the i'th method in the type's method set.
	// It panics if i is not in the range [0, NumMethod()).
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	//
	// Only exported methods are accessible and they are sorted in
	// lexicographic order.
	Method(int) Method

	// MethodByName returns the method with that name in the type's
	// method set and a boolean indicating if the method was found.
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	MethodByName(string) (Method, bool)

	// NumMethod returns the number of exported methods in the type's method set.
	NumMethod() int

	// Name returns the type's name within its package for a defined type.
	// For other (non-defined) types it returns the empty string.
	Name() string

	// PkgPath returns a defined type's package path, that is, the import path
	// that uniquely identifies the package, such as "encoding/base64".
	// If the type was predeclared (string, error) or not defined (*T, struct{},
	// []int, or A where A is an alias for a non-defined type), the package path
	// will be the empty string.
	PkgPath() string

	// Size returns the number of bytes needed to store
	// a value of the given type; it is analogous to unsafe.Sizeof.
	Size() uintptr

	// String returns a string representation of the type.
	// The string representation may use shortened package names
	// (e.g., base64 instead of "encoding/base64") and is not
	// guaranteed to be unique among types. To test for type identity,
	// compare the Types directly.
	String() string

	// Kind returns the specific kind of this type.
	Kind() Kind

	// Implements reports whether the type implements the interface type u.
	Implements(u Type) bool

	// AssignableTo reports whether a value of the type is assignable to type u.
	AssignableTo(u Type) bool

	// ConvertibleTo reports whether a value of the type is convertible to type u.
	ConvertibleTo(u Type) bool

	// Comparable reports whether values of this type are comparable.
	Comparable() bool

	// Methods applicable only to some types, depending on Kind.
	// The methods allowed for each kind are:
	//
	//	Int*, Uint*, Float*, Complex*: Bits
	//	Array: Elem, Len
	//	Chan: ChanDir, Elem
	//	Func: In, NumIn, Out, NumOut, IsVariadic.
	//	Map: Key, Elem
	//	Ptr: Elem
	//	Slice: Elem
	//	Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField

	// Bits returns the size of the type in bits.
	// It panics if the type's Kind is not one of the
	// sized or unsized Int, Uint, Float, or Complex kinds.
	Bits() int

	// ChanDir returns a channel type's direction.
	// It panics if the type's Kind is not Chan.
	ChanDir() ChanDir

	// IsVariadic reports whether a function type's final input parameter
	// is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's
	// implicit actual type []T.
	//
	// For concreteness, if t represents func(x int, y ... float64), then
	//
	//	t.NumIn() == 2
	//	t.In(0) is the reflect.Type for "int"
	//	t.In(1) is the reflect.Type for "[]float64"
	//	t.IsVariadic() == true
	//
	// IsVariadic panics if the type's Kind is not Func.
	IsVariadic() bool

	// Elem returns a type's element type.
	// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
	Elem() Type

	// Field returns a struct type's i'th field.
	// It panics if the type's Kind is not Struct.
	// It panics if i is not in the range [0, NumField()).
	Field(i int) StructField

	// FieldByIndex returns the nested field corresponding
	// to the index sequence. It is equivalent to calling Field
	// successively for each index i.
	// It panics if the type's Kind is not Struct.
	FieldByIndex(index []int) StructField

	// FieldByName returns the struct field with the given name
	// and a boolean indicating if the field was found.
	FieldByName(name string) (StructField, bool)

	// FieldByNameFunc returns the struct field with a name
	// that satisfies the match function and a boolean indicating if
	// the field was found.
	//
	// FieldByNameFunc considers the fields in the struct itself
	// and then the fields in any embedded structs, in breadth first order,
	// stopping at the shallowest nesting depth containing one or more
	// fields satisfying the match function. If multiple fields at that depth
	// satisfy the match function, they cancel each other
	// and FieldByNameFunc returns no match.
	// This behavior mirrors Go's handling of name lookup in
	// structs containing embedded fields.
	FieldByNameFunc(match func(string) bool) (StructField, bool)

	// In returns the type of a function type's i'th input parameter.
	// It panics if the type's Kind is not Func.
	// It panics if i is not in the range [0, NumIn()).
	In(i int) Type

	// Key returns a map type's key type.
	// It panics if the type's Kind is not Map.
	Key() Type

	// Len returns an array type's length.
	// It panics if the type's Kind is not Array.
	Len() int

	// NumField returns a struct type's field count.
	// It panics if the type's Kind is not Struct.
	NumField() int

	// NumIn returns a function type's input parameter count.
	// It panics if the type's Kind is not Func.
	NumIn() int

	// NumOut returns a function type's output parameter count.
	// It panics if the type's Kind is not Func.
	NumOut() int

	// Out returns the type of a function type's i'th output parameter.
	// It panics if the type's Kind is not Func.
	// It panics if i is not in the range [0, NumOut()).
	Out(i int) Type

	common() *rtype
	uncommon() *uncommonType
}

​ 结合之前讲的多态的能力,返回的这个接口,我们可以调用它的任何方法。它的动态值是任何实现了该接口的对象,而go能在调用的时候动态的知道是执行的哪个对象的方法。

​ 通过 Type() 方法和 Interface() 方法可以打通 interfaceTypeValue 三者。Type() 方法也可以返回变量的类型信息,与 reflect.TypeOf() 函数等价。Interface() 方法可以将 Value 还原成原来的 interface。

​ TypeOf() 函数返回一个接口,这个接口定义了一系列方法,利用这些方法可以获取关于类型的所有信息; ValueOf() 函数返回一个结构体变量,包含类型信息以及实际值。

​ 根据 Go 官方关于反射的博客,反射有三大定律:

  1. Reflection goes from interface value to reflection object.

  2. Reflection goes from reflection object to interface value.

  3. To modify a reflection object, the value must be settable.

    第一条:反射能从interface反射出对象,也就是利用reflect.ValueOf和reflect.TypeOf从interface反射出对象的具体信息。

    第二条:反射能从对象构建出interface,也就是通过interface()函数从值反射出interface。也就是跟第一条反着来。

    第三条:要修改一个反射对象,它的值必须是能修改的。简单点说,就是通过反射,必须反射出它的本身,而不是它的一份拷贝,那怎么反射出它的本身,和我们函数传参的时候很像:地址。举个例子:

    var x int = 8
    v := reflect.ValueOf(x)
    v.SetInt(24) // Error: will panic.
    

    x只是v的一个拷贝,对v的修改反应不到x上,所以会panic。

    将上文的v := reflect.ValueOf(x)改为v := reflect.ValueOf(&x)即可。

​ v 还不是代表 x,v.Elem() 才真正代表 v,这样就可以真正操作 v了:

​ 如果想要操作原变量,反射变量 Value 必须要 hold 住原变量的地址才行。

​ 利用反射机制,对于结构体中未导出成员(也就是小写开头),可以读取,但不能修改其值。

​ 通过反射,结构体中可以被修改的成员只有是导出成员,也就是字段名的首字母是大写的。

反射目前在项目中的应用:

​ 综治的接口路由、自动建表,批量任务框架

​ 参数解包回包的序列化与反序列化

 
文章来自个人专栏
golang开发相关
3 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
2
1