接口的定义
接口是一种抽象的类型,不会暴露它所代表对象的内部值的结构和这个对象支持的基础操作的集合,它们只会展示出它们自己的方法。
//接口的定义
type Writer interface{
//方法
Write(p []byte) (n int,err error)
}
type People interface{
ReName() string
}
接口的实现
Golang的接口实现是隐式的,怎么说呢,就比如说Java,Java实现一个接口需要显示,利用implements关键字,但是Golang里没有使用类似的关键字去实现接口。
type ByteCounter int
type MyName struct {
Name string
}
//接口实现的最简单方式
var w Writer
//这个方法同接口的Write的所有方法,可以认为变量ByteCounter实现了接口
func (b *ByteCounter) Write(p []byte) (n int, err error) {
*b += ByteCounter(len(p))
return len(p), nil
}
//这个方法同接口的People的所有方法,可以认为结构体MyName实现了接口
func (p Myname) ReName() string{
return p.Name
}
指针与值类型实现接口的区别
从上面可以看出Write是用指针实现接口的,ReName是用值类型实现接口的,那么它们有什么区别呢???我从新定义了两个结构体,以区别它们之间的比较没有其他外因影响。
package main
import (
"fmt"
)
type People interface {
ReturnName() string
}
type Student struct {
Name string
}
type Teacher struct {
Name string
}
func (s Student) ReturnName() string {
return s.Name
}
func (t *Teacher) ReturnName() string {
return t.Name
}
func main() {
cbs := Student{Name: "咖啡色的羊驼"}
sss := Teacher{Name: "咖啡色的羊驼的老师"}
// 值类型
var a People
a = cbs
name := a.ReturnName()
fmt.Println(name)
// 指针类型
// a = sss <- 这样写不行!!!
a = &sss // 由于是指针类型,所以赋值的时候需要加上&
name = a.ReturnName()
fmt.Println(name) // 输出"咖啡色的羊驼的老师"
}
“a = sss”这样写会发生报错,因为是Teacher的指针实现了ReturnName方法,Teacher本身没实现。
判断接口是否被实现
如果不确定接口是否被实现有两种方法:
- 第一种,直接调用,如果没实现会报错
func main() {
cbs := Myname{Name: "John"}
var a People
//因为Myname实现了接口所以直接赋值没有问题
a = cbs
name := a.ReName()
fmt.Println(name)
}
- 第二种就是使用go语言中的断言判断接口实现
类型断言的语法:x.(T),x表示一个接口的类型,T表示一个类型
func CheckWriter(test interface{}) {
_, err := test.(People)
if err {
fmt.Println("Myname implements People")
}
}
若是没有接触过golang语言的人,可能会对test interface{}会疑惑,一个空接口,不包含任何方法的接口,起到什么作用??
正因为是空接口,所以所有的类型都实现了空接口
var s1 interface{}
var s2 interface{}
var s3 interface{}
var s4 interface{}
i:=5
s:="hello world"
q:=[]int{1,2,3}
qq:=nil
s1=i
s2=s
s3=q
s4=nil
接口居然还可以这样赋值??下面说明原因
接口值
先来说说接口问什么能这样赋值
接口值是由两部分组成的,一个具体的类型和那个类型的值,它们被称为接口的动态类型和动态值。接口的初始值就是它的类型和值的部分都是nil。
一个接口值基于它的动态类型被描述为空或者非空,所以这是一个空的接口值。
var w io.Writer
w = os.Stdout
if w == nil {
fmt.Println("1.w is Empty interface!")
}
w = new(bytes.Buffer)
if w == nil {
fmt.Println("2.w is Empty interface!")
}
w = nil
if w == nil {
fmt.Println("3.w is Empty interface!")
}
调用一个空接口值上的任意方法都会产生panic:
w.Write([]byte("hello"))
通常在编译期,我们不知道接口值的动态类型是什么,所以一个接口上的调用必须使用动态分配。
w = os.Stdout
/*因为不是直接进行调用,所以编译器必须把代码生成在类型描述符的方法Write上,然后间接调用那个地址。等价于直接调用:os.Stdout.Write([]byte("hello"))*/
w.Write([]byte("hello"))
接口不仅仅可以被这样赋值,接口和接口之间还能做比较。
接口值可以使用 ==和!=来进行比较。两个接口值相等仅当它们都是nil值或者它们的动态类型相同并且动态值也根据这个动态类型的 ==操作相等。
然而,如果两个接口值的动态类型相同,但是这个动态类型是不可比较的(比如切片),将它们进行比较就会失败并且panic
- 可比较类型:基础类型和指针
- 不可比较类型:切片、映射类型和函数
因此,当比较接口值或者包含了接口值的聚合类型时,我们必须要意识到潜在的panic
接口内嵌
Golang是没有继承关系的,那么怎样实现类似Java父类子类这样的功能,那就是通过组合实现的。
type Reader interface {
Read(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
用这种方式以一个简写命名另一个接口,而不用声明它所有的方法,这种方式本称为接口内嵌。
参考链接:link