接口——定义,实现接口的条件,类型与接口的关系,类型断言

Posted acgame

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了接口——定义,实现接口的条件,类型与接口的关系,类型断言相关的知识,希望对你有一定的参考价值。

1、定义

Go 语言的接口设计是非侵入式的,接口编写者无须知道接口被哪些类型实现。而接口实现者只需知道实现的是什么样子的接口,但无须指明实现哪一个接口。编译器知道最终编译时使用哪个类型实现哪个接口,或者接口应该由谁来实现。

每个接口类型由数个方法组成。接口的形式代码如下:

type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2

}

说明:

  • 接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer 等。
  • 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略

 

2、实现接口的条件

如果一个任意类型 T 的方法集为一个接口类型的方法集的超集,则我们说类型 T 实现了此接口类型。T 可以是一个非接口类型,也可以是一个接口类型。
实现关系在 Go语言中是隐式的。两个类型之间的实现关系不需要在代码中显式地表示出来。Go语言中没有类似于 implements 的关键字。 Go编译器将自动在需要的时候检查两个类型之间的实现关系。
接口定义后,需要实现接口,调用方才能正确编译通过并使用接口。接口的实现需要遵循两条规则才能让接口可用。

1)接口的方法与实现接口的类型方法格式一致

在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法中的名称、参数列表、返回参数列表。也就是说,只要实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现。

2)接口中所有方法均被实现

当一个接口中有多个方法时,只有这些方法都被实现了,接口才能被正确编译并使用。

示例:

package main
import (
	"fmt"
)
// 定义一个数据写入器
type DataWriter interface {
	WriteData(data interface{}) error
}
// 定义文件结构,用于实现DataWriter
type file struct {
}
// 实现DataWriter接口的WriteData方法
func (d *file) WriteData(data interface{}) error {
	fmt.Println("WriteData:", data)
	return nil
}
func main() {
	// 实例化file
	f := new(file)
	// 声明一个DataWriter的接口
	var writer DataWriter
	// 将接口赋值f,也就是*file类型
	writer = f
	// 使用DataWriter接口进行数据写入
	writer.WriteData("data")
}

  

3、类型与接口的关系

在 Go语言中类型和接口之间有一对多和多对一的关系。

1)一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。

package main

import (
	"fmt"
	"io"
)

type Writer interface {
	Write(p []byte) (n int, err error)
}
type Closer interface {
	Close() error
}
type Socket struct {
}
func (s *Socket) Write(p []byte) (n int, err error) {
	fmt.Println("Socket write")
	return 0, nil
}
func (s *Socket) Close() error {
	fmt.Println("Socket close")
	return nil
}
// 使用io.Writer的代码, 并不知道Socket和io.Closer的存在
func usingWriter( writer io.Writer){
	writer.Write( nil )
}
// 使用io.Closer, 并不知道Socket和io.Writer的存在
func usingCloser( closer io.Closer) {
	closer.Close()
}
func main() {
	// 实例化Socket
	s := new(Socket)
	usingWriter(s)
	usingCloser(s)
}

  

2)多个类型可以实现相同的接口

一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。也就是说,使用者并不关心某个接口的方法是通过一个类型完全实现的,还是通过多个结构嵌入到一个结构体中拼凑起来共同实现的。

示例:

package main

import "fmt"

// 一个服务需要满足能够开启和写日志的功能
type Service interface {
	Start()  // 开启服务
	Log(string)  // 日志输出
}
// 日志器
type Logger struct {
}
// 实现Service的Log()方法
func (g *Logger) Log(l string) {
	fmt.Println(l)
}
// 游戏服务
type GameService struct {
	Logger  // 嵌入日志器
}
// 实现Service的Start()方法
func (g *GameService) Start() {
	fmt.Println("游戏服启动")
}
func main() {
	var s Service = new(GameService)
	s.Start()
	s.Log("hello")
}

  

 4、类型断言

Go语言中有四种接口相关的类型转换情形:

  • 将一个非接口值转换为一个接口类型。在这样的转换中,此非接口值的类型必须实现了此接口类型。
  • 将一个接口值转换为另一个接口类型(前者接口值的类型实现了后者目标接口类型)。
  • 将一个接口值转换为一个非接口类型(此非接口类型必须实现了此接口值的接口类型)。
  • 将一个接口值转换为另一个接口类型(前者接口值的类型可以实现了也可以未实现后者目标接口类型)。

一个类型断言表达式的语法为 i.(T),其中 i 为一个接口值, T 为一个类型名或者类型字面表示。 类型 T 可以为任意一个非接口类型,或者一个任意接口类型。
在一个类型断言表达式 i.(T) 中, i 称为断言值, T 称为断言类型。 一个断言可能成功或者失败。


对于 T 是一个非接口类型的情况,如果断言值 i 的动态类型存在并且此动态类型和 T 为同一类型,则此断言将成功;否则,此断言失败。 当此断言成功时,此类型断言表达式的估值结果为断言值 i 的动态值的一个复制。可以把此种情况看作是一次拆封动态值的尝试。


对于 T 是一个接口类型的情况,当断言值 i 的动态类型存在并且此动态类型实现了接口类型 T,则此断言将成功;否则,此断言失败。 当此断言成功时,此类型断言表达式的估值结果为一个包裹了断言值i的动态值的一个复制的 T 值。


一个失败的类型断言的估值结果为断言类型的零值。

按照上述规则,如果一个类型断言中的断言值是一个零值 nil 接口值,则此断言必定失败。

对于大多数场合,一个类型断言被用做一个单值表达式。 但是,当一个类型断言被用做一个赋值语句中的唯一源值时,此断言可以返回一个可选的第二个结果并被视作为一个多值表达式。此可选的第二个结果为一个类型不确定的布尔值,用来表示此断言是否成功了。

注意:如果一个断言失败并且它的可选的第二个结果未呈现,则此断言将造成一个恐慌。

示例1:断言类型为非接口类型

package main
import "fmt"
func main() {
	// 编译器将把123的类型推断为内置类型int。
	var x interface{} = 123
	// 情形一:
	n, ok := x.(int)
	fmt.Println(n, ok) // 123 true
	n = x.(int)
	fmt.Println(n) // 123
	// 情形二:
	a, ok := x.(float64)
	fmt.Println(a, ok) // 0 false
	// 情形三:
	a = x.(float64) // 将产生一个恐慌,抛出异常
}

  

示例2:

package main
import "fmt"
type Writer interface {
	Write(buf []byte) (int, error)
}
type DummyWriter struct{}
func (DummyWriter) Write(buf []byte) (int, error) {
	return len(buf), nil
}
func main() {
	var x interface{} = DummyWriter{}
	// y的动态类型为内置类型string。
	var y interface{} = "abc"
	var w Writer
	var ok bool
	// DummyWriter既实现了Writer,也实现了interface{}。
	w, ok = x.(Writer)
	fmt.Println(w, ok) // {} true
	x, ok = w.(interface{})
	fmt.Println(x, ok) // {} true
	// y的动态类型为string。string类型并没有实现Writer。
	w, ok = y.(Writer)
	fmt.Println(w, ok) // <nil> false
	w = y.(Writer) // 将产生一个恐慌
}

  

 

 

以上是关于接口——定义,实现接口的条件,类型与接口的关系,类型断言的主要内容,如果未能解决你的问题,请参考以下文章

go语言学习笔记 — 进阶 — 接口(2.1):实现接口的条件 —— 接口的方法与实现接口的类型方法一致

值交换,接口的基本描述,接口与类接口与接口之间的关系

Java:Effective java学习笔记之 接口只用于定义类型类层次优于标签类

Android基础笔记

go语言学习笔记 — 进阶 — 接口(3.2):接口与类型的关系 —— 多个类型可以实现一个(相同)接口

interface Part2(定义接口)