Go 语言里有非常灵活的 接口 概念,通过它可以实现很多面向对象的特性。接口提供了一种方式来 说明 对象的行为。接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量。
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
按照约定,只包含一个方法的接口的名字由方法名加 [e]r
后缀组成,例如 Printer
、Reader
、Writer
、Logger
、Converter
等等。还有一些不常用的方式(当后缀 er
不合适时),比如 Recoverable
,此时接口名以 able
结尾,或者以 I
开头(像 .NET
或 Java
中那样)。
Go 语言中的接口都很简短,通常它们会包含 0 个、最多 3 个方法。
Go 语言中接口可以有值,一个接口类型的变量或一个 接口值 :var ai Namer
,ai
是一个多字(multiword)数据结构,它的值是 nil
。它本质上是一个指针,虽然不完全是一回事。指向接口值的指针是非法的,它们不仅一点用也没有,还会导致代码错误。
类型(比如结构体)实现接口方法集中的方法,每一个方法的实现说明了此方法是如何作用于该类型的:即实现接口,同时方法集也构成了该类型的接口。实现了 Namer
接口类型的变量可以赋值给 ai
(接收者值),此时方法表中的指针会指向被实现的接口方法。当然如果另一个类型(也实现了该接口)的变量被赋值给 ai
,这二者(指针和方法实现)也会随之改变。
- 类型不需要显式声明它实现了某个接口:接口被隐式地实现。
- 多个类型可以实现同一个接口。
- 实现某个接口的类型(除了实现接口方法外)可以有其他的方法。
- 一个类型可以实现多个接口。
- 接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。
package main
import "fmt"
type Shaper interface {
Area() float32
}
type Square struct {
side float32
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func main() {
sq1 := new(Square)
sq1.side = 5
var areaIntf Shaper
areaIntf = sq1
// shorter,without separate declaration:
// areaIntf := Shaper(sq1)
// or even:
// areaIntf := sq1
fmt.Printf("The square has area: %f\n", areaIntf.Area())
}
1、接口嵌套接口
一个接口可以包含一个或多个其他的接口。 ```go type ReadWrite interface { Read(b Buffer) bool Write(b Buffer) bool }
type Lock interface { Lock() Unlock() }
type File interface { ReadWrite Lock Close() }
<a name="Qj13A"></a>
# 2、类型断言
只有接口类型的变量才有类型断言。一个接口类型的变量 `varI` 中可以包含任何类型的值,必须有一种方式来检测它的 **动态** 类型,即运行时在变量中存储的值的实际类型。在执行过程中动态类型可能会有所不同,但是它总是可以分配给接口变量本身的类型。通常我们可以使用 **类型断言** 来测试在某个时刻 `varI` 是否包含类型 `T` 的值。
```go
v := varI.(T) // unchecked type assertion
类型断言可能是无效的,虽然编译器会尽力检查转换是否有效,但是它不可能预见所有的可能性。如果转换在程序运行时失败会导致错误发生。更安全的方式是使用以下形式来进行类型断言:
if v, ok := varI.(T); ok { // checked type assertion
Process(v)
return
}
// varI is not of type T
如果转换合法,v
是 varI
转换到类型 T
的值,ok
会是 true
;否则 v
是类型 T
的零值,ok
是 false
,也没有运行时错误发生。
3、类型判断
接口变量的类型也可以使用一种特殊形式的 switch
来检测:type-switch
switch t := areaIntf.(type) {
case *Square:
fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:
fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil:
fmt.Printf("nil value: nothing to check?\n")
default:
fmt.Printf("Unexpected type %T\n", t)
}
变量 t
得到了 areaIntf
的值和类型, 所有 case
语句中列举的类型(nil
除外)都必须实现对应的接口。
可以用 type-switch
进行运行时类型分析,但是在 type-switch
不允许有 fallthrough
。
如果仅仅是测试变量的类型,不用它的值,那么就可以不需要赋值语句。
switch areaIntf.(type) {
case *Square:
// TODO
case *Circle:
// TODO
default:
// TODO
}
4、空接口
空接口不包含任何方法,它对实现不做任何要求。
type Any interface {}
任何其他类型都实现了空接口,any
或 Any
是空接口一个很好的别名或缩写。
空接口类似 Java/C#
中所有类的基类: Object
类,二者的目标也很相近。
package main
import "fmt"
var i = 5
var str = "ABC"
type Person struct {
name string
age int
}
type Any interface{}
func main() {
var val Any
val = 5
fmt.Printf("val has the value: %v\n", val)
val = str
fmt.Printf("val has the value: %v\n", val)
pers1 := new(Person)
pers1.name = "Rob Pike"
pers1.age = 55
val = pers1
fmt.Printf("val has the value: %v\n", val)
switch t := val.(type) {
case int:
fmt.Printf("Type int %T\n", t)
case string:
fmt.Printf("Type string %T\n", t)
case bool:
fmt.Printf("Type boolean %T\n", t)
case *Person:
fmt.Printf("Type pointer to Person %T\n", t)
default:
fmt.Printf("Unexpected type %T", t)
}
}