接口在类型上定义了一组方法。
接口用于抽象行为。
在Go中,接口只是一组方法。 我们使用接口来指定某个对象的行为。
例如:标准库中定义的io.Reader接口:
type Reader interface {
Read(d []byte) (int, error)
}
对二进制数据流进行操作的大多数函数(例如json解码器)都将io.Reader用作数据源。 这样,我们可以为物理文件,内存中的字节,网络连接实现Reader接口,并在所有这些源上使用json.Decode。
下面是定义和实现一个简单的接口:
// Stringer是只有一个方法的接口
type Stringer interface {
String() string
}
// User struct实现了Stringer接口
type User struct {
Name string
}
func (u *User) String() string {
return u.Name
}
// 任何类型都可以实现接口
// 这里我们创建int的别称来实现Stringer接口
type MyInt int
func (mi MyInt) String() string {
return strconv.Itoa(int(mi))
}
// printTypeAndString 接收一个接口作为参数, 's'可以是实现了Stringer接口的任意值
func printTypeAndString(s Stringer) {
fmt.Printf("%T: %s\n", s, s)
}
func main() {
u := &User{Name: "John"}
printTypeAndString(u)
n := MyInt(5)
printTypeAndString(n)
}
*main.User: John
main.MyInt: 5
与大多数其他语言不同,Go的接口是隐式满足的。我们不必明确声明struct User实现了Stringer接口。
接口只能包含方法,不能包含数据。 如果要重用方法和数据,则可以使用结构嵌入。
只能在同一包中定义的类型上定义方法。 上例中我们必须定义类型别名MyInt,因为我们无法将方法添加到Go内置的int类型。
简单接口
type Painter interface {
Paint()
}
类型实现接口时不必声明它正在实现那个接口。 定义相同签名的方法就可以了。
type Rembrandt struct{}
func (r Rembrandt) Paint() {
// use a lot of canvas here
}
现在,我们可以将这个struct用作接口。
var p Painter
p = Rembrandt{}
接口可以被任意数量的类型实现。 一个类型也可以实现任意数量的接口。
type Singer interface {
Sing()
}
type Writer interface {
Write()
}
type Human struct{}
func (h *Human) Sing() {
fmt.Println("singing")
}
func (h *Human) Write() {
fmt.Println("writing")
}
type OnlySinger struct{}
func (o *OnlySinger) Sing() {
fmt.Println("singing")
}
这里Human同时实现了Singer和Writer接口,而OnlySinger只实现了Sinnger接口.
空接口
空接口类型是不包含任何方法的接口。 我们将其声明为interface {}
。 它不包含任何方法,因此每种类型都可以满足要求。 因此,空接口可以包含任何类型值。
var a interface{}
var i int = 5
s := "Hello world"
type StructType struct {
i, j int
k string
}
// 下列声明都合法
a = i
a = s
a = &StructType{1, 2, "hello"}
接口最常见的作用是确保变量支持一个或多个行为。 相比之下,空接口的主要作用是定义一个变量,该变量可以保留任何值,而不管其具体类型是什么。
为了使这些值恢复为原始类型,我们只需要做:
i = a.(int)
s = a.(string)
m := a.(*StructType)
或者
i, ok := a.(int)
s, ok := a.(string)
m, ok := a.(*StructType)
返回值ok表示接口a是否可转换为给定类型。 如果无法执行,ok将为false。
接口值
如果声明了接口的变量,则它可以存储任何实现了接口声明方法的任何类型!
如果我们声明h为接口Singer,则它可以存储Human或OnlySinger类型的值。 这是因为它们都实现了Singer接口指定的方法。
var h Singer
h = &human{}
h.Sing()
从接口确定底层类型
在Go中,有时候需要知道参数的底层类型。 这可以通过switch来完成。 假设我们有两个结构:
type Rembrandt struct{}
func (r Rembrandt) Paint() {}
type Picasso struct{}
func (r Picasso) Paint() {}
都实现了Painter接口:
type Painter interface {
Paint()
}
然后我们可以通过switch来决定参数底层类型:
func WhichPainter(painter Painter) {
switch painter.(type) {
case Rembrandt:
fmt.Println("The underlying type is Rembrandt")
case Picasso:
fmt.Println("The underlying type is Picasso")
default:
fmt.Println("Unknown type")
}
}
确保类型实现了接口
在Go中,接口是隐式满足的, 即不必声明类型是一定要实现某个接口的。
这很方便,但也可能导致无法完全实现接口错误,并且编译器无法检测到这种情况。
有一种方法可以进行编译期的类型检查:
type MyReadCloser struct {
}
func (rc *MyReadCloser) Read(d []byte) (int, error) {
return 0, nil
}
var _ io.ReadCloser = &MyReadCloser{}
./main.go:15:5: cannot use &MyReadCloser literal (type *MyReadCloser) as type io.ReadCloser in assignment:
*MyReadCloser does not implement io.ReadCloser (missing Close method)
exit status 2
我们的意图是让MyReadCloser实现io.ReadCloser接口, 然而我们忘记了实现Close方法,这行代码在编译时就报错了:
var _ io.ReadCloser = &MyReadCloser{}
我们尝试把MyReadCloser赋值给io.ReadCloser
因为MyReadCloser没有实现Close方法,编译器在编译期间检测到这是非法的赋值
我们把值赋给空变量_是因为这个值并不会被使用
空接口
技术上讲, 空接口就是没有方法的接口.从而可以说任何类型都实现了空接口
实际上, Go中的空接口就像Java和C++中的object类型,它结合了类型及其值。
空接口等效于静态语言中的动态类型
空接口也是Go中实现union类型的一种方法.
由于每种类型都符合interface {},因此可以将任何值分配给interface {}类型的变量。
那时你将无法在编译时知道它的真实类型。
空接口的零值为nil.
使用示例:
func printVariableType(v interface{}) {
switch v.(type) {
case string:
fmt.Printf("v is of type 'string'\n")
case int:
fmt.Printf("v is of type 'int'\n")
default:
// generic fallback
fmt.Printf("v is of type '%T'\n", v)
}
}
func main() {
printVariableType("string") // string
printVariableType(5) // int
printVariableType(int32(5)) // int32
}
v is of type 'string'
v is of type 'int'
v is of type 'int32'
编译时,如果变量的类型为interface(包括空接口),则你不知道它的真正类型是什么。
可以在运行时使用类型断言来访问基础类型:
func printTypeAndValue(iv interface{}) {
if v, ok := iv.(string); ok {
fmt.Printf("iv is of type string and has value '%s'\n", v)
return
}
if v, ok := iv.(int); ok {
fmt.Printf("iv is of type int and has value '%d'\n", v)
return
}
if v, ok := iv.(*int); ok {
fmt.Printf("iv is of type *int and has value '%s'\n", v)
return
}
}
func panicOnInvalidConversion() {
var iv interface{} = "string"
v := iv.(int)
fmt.Printf("v is int of value: %d\n", v)
}
func main() {
// pass a string
printTypeAndValue("string")
i := 5
// pass an int
printTypeAndValue(i)
// pass a pointer to int i.e. *int
printTypeAndValue(&i)
panicOnInvalidConversion()
}
iv is of type string and has value 'string'
iv is of type int and has value '5'
iv is of type int and has value '%!s(int=0xc000016048)'
nic: interface conversion: interface {} is string, not introutine 1 [running]:
in.panicOnInvalidConversion()
/tmp/src178978545/type assertion.go:28 +0x45
in.main()
/tmp/src178978545/type assertion.go:41 +0x9a
it status 2
类型断言
类型断言允许你检查空接口的值是否为给定的类型。
为了完整起见,可以使用类型switch的简短版本:v:= iv.(int)(v, ok:= iv.(int))。
它和完整版本的区别在于,如果iv不是断言的类型,则简短版本会panic:
func panicOnInvalidConversion(iv interface{}) {
v := iv.(int)
fmt.Printf("v is int of value: %d\n", v)
}
func main() {
panicOnInvalidConversion("string")
}
panic: interface conversion: interface {} is string, not int
goroutine 1 [running]:
main.panicOnInvalidConversion(0x4a0120, 0x4db030)
/tmp/src459967900/type assertion.go:11 +0xd6
main.main()
/tmp/src459967900/type assertion.go:16 +0x39
exit status 2
As a rule of thumb, you shouldn’t try to discover underlying value of interface type as it pierces through an abstraction.
根据经验,你不应该尝试发现贯穿抽象的接口类型的底层值。
类型switch
switch语句可以基于接口包装的类型进行分派。
如果具有接口值,则可以根据底层类型进行切换:
func smartConvertToInt(iv interface{}) (int, error) {
// inside case statements, v is of type matching case type
switch v := iv.(type) {
case int:
return v, nil
case string:
return strconv.Atoi(v)
case float64:
return int(v), nil
default:
return 0, fmt.Errorf("unsupported type: %T", iv)
}
}
func printSmartConvertToInt(iv interface{}) {
i, err := smartConvertToInt(iv)
if err != nil {
fmt.Printf("Failed to convert %#v to int\n", iv)
return
}
fmt.Printf("%#v of type %T converted to %d\n", iv, iv, i)
}
func main() {
printSmartConvertToInt("5")
printSmartConvertToInt(4)
printSmartConvertToInt(int32(8))
printSmartConvertToInt("not valid int")
}
"5" of type string converted to 5
4 of type int converted to 4
Failed to convert 8 to int
Failed to convert "not valid int" to int