接口
接口是一个或多个方法签名的集合
- 接口命名习惯以
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!") //也能传字符串
}
对于一个空接口,只有其 tab
和 data
均为空,接口才是 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()
}
参考
Q.E.D.