接口是不能直接实例化的,因为它只是一个契约的存在,只能通过具体类来实例化。但是在go中我们支持接口赋值操作,从而快速实现接口和示例的映射和转换。
接口赋值有两种情况:

  • 将实现的类实例化后赋值给接口
  • 将一个接口赋值给另一个接口

    将类赋值给接口

    在go中,只要我们的某个类实现了某个接口,实例化后我们就可以将这个对象赋值给接口。 ``` package main

import “fmt”

type Integer int

func (i Integer) Add(a, b Integer) Integer { return a + b }

func (i Integer) Multiply(b Integer) Integer { return i * b }

type Math interface { Add(a, b Integer) Integer Multiply(i Integer) Integer }

func main() { var a Integer = 1 var m Math m = a fmt.Println(m.Add(2, 1)) }

  1. 按照 Go 语言的约定,Integer 类型实现了 Math 接口。然后我们可以这样将 Integer 类型的实例 a 直接赋值给 Math 接口类型的变量 m
var a Integer = 1
var m Math
m = a
fmt.Println(m.Add(2, 1))
对于值方法而言,进行接口赋值时传递 a 实例的指针引用也是可以的:

var a Integer = 1 var m Math = &a fmt.Println(m.Add(1))

因为对于非指针方法,Go 底层会自动生成一个与之对应的指针成员方法:

func (a Integer) Add(i Integer) Integer { return (a).Add(i) } func (a Integer) Multiply(i Integer) Integer { return (a).Multiply(i) }

<a name="dQqQ5"></a>
### 包含指针方法
不过如果 Integer 类型中包含了归属于指针的实现方法:

type Integer int func (a Integer) Add(b Integer) { a = (a) + b } func (a Integer) Multiply(b Integer) Integer { return a b } type Math interface { Add(i Integer) Multiply(i Integer) Integer }

那么在做接口赋值时,就只能传递指针类型的变量了:

var a Integer = 1 var m Math = &a m.Add(2) fmt.Printf(“1 + 2 = %d\n”, a)

因为 Integer 类型不包含指针方法(参考前面介绍的[值方法与指针方法区别](https://geekr.dev/posts/go-struct-and-class#toc-5)),所以此时只有 *Integer 类型实现了 Math 接口,如果我们直接将 a 的值类型赋值给 m,编译时会报错<br />综上所述,如果 Integer 类中实现接口的成员方法都是值方法,则进行接口赋值时,传递类实例的值类型或者指针类型均可,否则只能传递指针类型实例,从代码性能角度来说,值拷贝需要消耗更多的内存空间,统一使用指针类型代码性能会更好。
<a name="QKINf"></a>
## 接口赋值接口
在 Go 语言中,只要两个接口拥有相同的方法列表(与顺序无关),那么它们就是等同的,可以相互赋值。不过,这里有一个前提,那就是接口变量持有的是基于对应实现类的实例值,所以接口与接口间的赋值是基于类实例与接口间的赋值的。
<a name="G0B5w"></a>
### 完全对等

package main

import “fmt”

type Number1 interface { Equal(i int) bool Less(i int) bool More(i int) bool }

type Number2 interface { Equal(i int) bool Less(i int) bool More(i int) bool }

type Number int

func (n Number) Equal(i int) bool { return int(n) == i } func (n Number) Less(i int) bool { return int(n) < i } func (n Number) More(i int) bool { return int(n) > i }

func main() { var num Number = 1 var num2 Number1 num2 = num var num3 = num2 fmt.Println(num3) }

    这里我们定义了两个完全一致的接口,,一个叫 Number1,一个Number2,两者都定义三个相同的方法,只是顺序不同而已。在 Go 语言中,这两个接口实际上并无区别,因为:

- 任何实现了 Number1 接口的类,也实现了 Number2;
- 任何实现了 Number1 接口的类实例都可以赋值给 Number2,反之亦然;
- 在任何地方使用 Number1 接口与使用 Number2 并无差异。
<a name="Wwsf6"></a>
### 方法子集
此外,接口赋值并不要求两个接口完全等价(方法完全相同)。如果接口 A 的方法列表是接口 B 的方法列表的子集,那么接口 B 也可以赋值给接口 A。例如,假设 Number2 接口定义如下:

type Number2 interface { Equal(i int) bool MoreThan(i int) bool LessThan(i int) bool Add(i int) }

要让 Number 类继续保持实现这两个接口,需要在 Number 类定义中新增一个 Add 方法实现(这里定义了一个指针方法):

func (n Number) Add(i int) { n = *n + Number(i) }

接下来,将上面的接口赋值语句改写如下即可:

var num1 Number = 1 var num2 Number2 = &num1 var num3 Number1 = num2

这样一来,就实现了接口赋值,但是反过来不行:

var num1 Number = 1 var num2 Number1 = &num1 var num3 Number2 = num2
``` 因为 Number1 接口中没有声明 Add 方法,或者换句话说,实现了 Number2 接口的类肯定实现了 Number1,但是实现了 Number1 接口的类不一定实现了 Number2。类似我们其他语言中,子类示例化后可以直接赋值给父类,但是父类实例化之后不能赋值给子类。