接口

接口是一个或多个方法签名的集合

  • 接口命名习惯以 er 结尾,结构体。
  • 接口只有方法签名,没有实现。
  • 接口没有数据字段。
  • 可在接口中嵌入其他接口。
  • 类型可实现多个接口。
  • 接口可用作变量类型,或结构成员。

任何类型的方法集中只要拥有与之对应的全部方法(指有相同名称、参数列表 (不包括参数名) 以及返回值),就表示它 "实现" 了该接口,无须在该类型上显式添加接口声明。当然,该类型还可以有其他方法。

举个栗子

package main

import "fmt"

type Stringer interface {
	String() string
}

type Printer interface {
	Stringer // 接⼝口嵌⼊入。
	Print()
}

type User struct {
	id   int
	name string
}

// *User 方法集包含 String、Print,即实现了 Printer 接口
func (self *User) String() string {
	return fmt.Sprintf("user %d, %s", self.id, self.name)
}

func (self *User) Print() {
	fmt.Println(self.String())
}

func main() {
	var t Printer = &User{1, "Tom"} 
	t.Print()
}

接口的执行机制

接口对象由接口表 (interface table) 指针和数据指针组成。

底层:

// 接口
struct Iface
{
	Itab* tab; // 接口表
	void* data; // 数据指针
};

// 接口表
struct Itab
{
	InterfaceType* inter; // 接口类型
	Type* type; // 动态类型
	void (*fun[])(void); // 实现接口的方法指针
};

数据指针持有的是目标对象的只读复制品,复制完整对象或指针。

对于下面代码:

package main

import "fmt"

type User struct {
	id   int
	name string
}

func main() {
	u := User{1, "Tom"}
	var i interface{} = u

	u.id = 2
	u.name = "Jack"

	fmt.Printf("%v\n", u) // {2 Jack}
	// 此处 i.(User) 为类型断言,后面会讲到
	fmt.Printf("%v\n", i.(User)) // {1 Tom}
	/*
    	可见
        	var i interface{} = u 中的u是复制品
	*/
}

空接口

空接口 interface{} 没有任何签名,所有的类型都实现了空接口,其作用就相当于 Java 中的 Object

func Print(v interface{}) {
	fmt.Printf("%T: %v\n", v, v)
}

func main() {
	Print(1) //可以传常数
	Print("Hello, World!") //也能传字符串
}

对于一个空接口,只有其 tabdata 均为空,接口才是 nil

var a interface{} = nil // tab = nil, data = nil
var b interface{} = (*int)(nil) // tab 包含 *int 类型信息, data = nil

// 此时 接口a 为空,接口b 不为空

匿名接口

上面提到了 接口可以作为 变量类型,或结构成员

看下面代码

package main

import "fmt"

type Tester struct {
	//尽管此处接口有 名字s,但仍称之为 匿名接口,其实我觉得叫 内部接口 更为合适
	s interface {
		String() string
	}
}

type User struct {
	id   int
	name string
}

// 此处实现了 Tester 中的 内部接口s
func (self *User) String() string {
	return fmt.Sprintf("user %d, %s", self.id, self.name)
}

func main() {
	// 所以此处可以将 *User 当作 s 传入
	t := Tester{&User{1, "Tom"}} 
	fmt.Println(t.s.String())
}

接口转型(类型断言)

在 java 中,做类型转换前需要判断类型,常用关键字 instanceof,那么 Golang 中应该怎么做呢?

这里就要用上 类型断言 了,看代码:

type User struct {
	id int
	name string
}

// 实现 String() string 函数就 实现了 fmt.Stringer 接口
// 这个函数的作用好比 Java中的 toString()
func (self *User) String() string {
	return fmt.Sprintf("%d, %s", self.id, self.name)
}

func main() {
	var o interface{} = &User{1, "Tom"} // 使用空接口接收 *User
	
	/*
		类型断言语法: i,ok := 接口对象.(断言类型)
		i:如果断言成功,那么接口对象就会被转型为断言类型,如果断言失败就为nil
		ok:断言是否成功,bool类型
    */
    if i, ok := o.(fmt.Stringer); ok {
		fmt.Println(i)
	}

	u := o.(*User)
	// u := o.(User) // panic: interface is *main.User, not main.User
	fmt.Println(u)
}

接口转型返回临时对象,只有使用指针才能修改其状态。

package main

import "fmt"

type User struct {
	id   int
	name string
}

func main() {
	u := User{1, "Tom"}
	var vi, pi interface{} = u, &u

	// vi.(User).name = "Jack" // Error: cannot assign to vi.(User).name
	pi.(*User).name = "Jack"

	fmt.Printf("%v\n", vi.(User))
	fmt.Printf("%v\n", pi.(*User))
}

批量类型判断

使用类型断言和流程控制switch就可以做到批量类型判断

func main() {
    var o interface{} = &User{1, "Tom"}
    
    switch v := o.(type) {
    case nil: // o == nil
    	fmt.Println("nil")
    case fmt.Stringer: // interface
    	fmt.Println(v)
    case func() string: // func
    	fmt.Println(v())
    case *User: // *struct
    	fmt.Printf("%d, %s\n", v.id, v.name)
    default:
    	fmt.Println("unknown")
    }
}

接口间转换

超集接口对象可转换为子集接口,反之出错。

type Stringer interface {
	String() string
}

type Printer interface {
	String() string
	Print()
}

type User struct {
	id int
	name string
}

func (self *User) String() string {
	return fmt.Sprintf("%d, %v", self.id, self.name)
}

func (self *User) Print() {
	fmt.Println(self.String())
}


// 由上可发现接口关系:Stringer 是 Printer 的超集,User 实现了 Printer

func main() {
	var o Printer = &User{1, "Tom"}
	var s Stringer = o // 此处并非是自动转型,而是 Pointer 本身就是 Stringer 的一种
    //如果你想手动转型也是可以的		var s Stringer = Stringer(o)
    //如果你愿意,你甚至可以使用类型断言		var s Stringer = o.(Stringer)
	fmt.Println(s.String())
}

使用接口的小技巧

使用编译器检查接口实现

让编译器检查,以确保某个类型实现接口。

var _ 接口类型 = (*检测类型)(nil)

如果转型失败,说明该类型并没有实现该接口

用函数来直接实现接口

package main

type Tester interface {
   Do()
}

type FuncDo func()
// 实现 Tester 接口,函数实现为直接调用自己
func (self FuncDo) Do() { self() }

func main() {
   // 这样程序编写能够更加灵活,可以自行设定接口的函数体内容了
   var t Tester = FuncDo(func() { println("Hello, World!") })
   t.Do()
}

参考

Go 边看边练

Q.E.D.


此 生 无 悔 恋 真 白 ,来 世 愿 入 樱 花 庄 。