文章目录
- [1. 接口](#1. 接口)
- [2. 隐式实现](#2. 隐式实现)
- [3. 类型断言](#3. 类型断言)
- [4. 空接口](#4. 空接口)
- [5. 接口和 nil 值](#5. 接口和 nil 值)
- [6. 嵌套接口](#6. 嵌套接口)
- [7. 小结](#7. 小结)
本系列文章:
- Java 转 go 学习 - 项目管理
- Java 转 go 学习 - 基本语法
- Java 转 go 学习 - 类型转换
- Java 转 go 学习 - 流程控制结构
- Java 转 go 学习 - 数组和切片
- Java 转 go 学习 - map
- Java 转 go 学习 - 函数(1)
- Java 转 go 学习 - 函数(2)
- Java 转 go 学习 - 结构体
上一篇文章我们学习了结构体,这一篇文章就来看下 Go 中的接口。
1. 接口
Go 不是面向对象编程语言,不像 Java,没有类和继承的概念,但是和 Java 一样,Go 里面也有接口,通过接口同样可以实现很多面向对象的特性,下面是基本语法。
go
type 接口名 interface {
方法名1(参数) 返回值
方法名2(参数) 返回值
}
下面举个例子,其实除了语法和 Java 不一样,其他大体结构是一样的。
go
type Person interface {
sayHello() string
}
2. 隐式实现
Go 和 Java 对接口的实现不同,Java 如果要实现一个接口,得先创建一个 class,然后需要显式声明 implements Interface 实现接口,重写里面的方法,也是面像对象编程的特点。
但是 Go 语言不需要,只要一个类型实现了接口中定义的所有方法,它就自动实现了该接口,这里的实现 可以是同包,也可以是实现其他包的接口,下面举个例子:
go
package main
import "fmt"
// 1. 定义接口
type Animal interface {
Speak() string
}
// 2. 定义结构体
type Cat struct{}
type Dog struct{}
// 3. Cat 实现 Speak 方法
func (c Cat) Speak() string {
return "Meow"
}
// 4. Dog 实现 Speak 方法
func (d Dog) Speak() string {
return "Woof"
}
// 5. 使用接口作为参数
func MakeSound(s Animal) {
fmt.Println(s.Speak())
}
func main() {
cat := Cat{}
dog := Dog{}
// 调用具体实现
MakeSound(cat) // Meow
MakeSound(dog) // Woof
}
3. 类型断言
假设一个接口有多个实现类,我们可以通过 var.(T) 去断言测试 var 这个接口变量是不是 T 实现类的,可以通过返回值中的第二个 bool 参数去判断是否断言成功,下面来看下例子,还是上面的几个接口。
go
func test2() {
var animal Animal = Cat{}
// animal 是不是 Cat 类型的
if _, ok := animal.(Cat); ok {
fmt.Println("animal type is: Cat")
}
// animal 是不是 Dog 类型的
if _, ok := animal.(Dog); ok {
fmt.Println("animal type is: Dog")
}
// animal type is: Cat
}
除了这种 if-else 的写法,也可以用 switch 来判断。
go
func test3() {
var animal Animal = Cat{}
// t 是转换之后的结构体
switch t := animal.(type) {
case Cat:
fmt.Printf("animal type is: Cat, %v\n", t)
case Dog:
fmt.Printf("animal type is: Dog, %v\n", t)
}
// animal type is: Cat, {}
}
4. 空接口
如果一个接口没有 任何方法 ,也就是 interface{}(Go 1.18+ 也可以写成 any),这种情况就是空接口,因为 Go 中的接口是隐式实现,所以任何结构体都自动实现了空接口。
空接口就类似 Java 的 Object,可以存任何类型的值比较常用的,如:
[]interface{}可以存混合类型,类似 Java 的List<Object>。fmt.Println(v ...interface{})打印不确定类型的参数。- 解析未知结构的 JSON 时,通常用
map[string]interface{}。
同样的,当我们用了空接口之后,如果要判断接口值,需要用断言。
go
func test4() {
var i interface{} = "Hello"
// 方式 1: 断言成具体类型, 如果断言失败会 panic
s := i.(string)
fmt.Println(s)
// Hello
// s := i.(int) -> panic: interface conversion: interface {} is string, not int
// 方式 2: 安全断言, 接收断言返回值并判断
if s, ok := i.(string); ok {
fmt.Printf("字符串: %s\n", s)
} else {
fmt.Println("不是字符串")
}
// 字符串: Hello
// 方式 3: type switch 处理多种可能
switch t := i.(type) {
case string:
fmt.Println("字符串: ", t)
case int:
fmt.Println("整数", t)
default:
fmt.Println("未知类型")
}
// 字符串: Hello
}
5. 接口和 nil 值
首先接口本质是一个二元组:(动态类型,动态值),当两个部分都为 nil 的时候接口才等于 nil,只要其中一个不是 nil,接口就不等于 nil。
go
func test5() {
var s Animal = nil
fmt.Println(s == nil) // true
var p *Cat = nil
s = p
fmt.Println(s == nil) // false
// panic: value method main.Cat.Speak called using nil *Cat pointer
// s.Speak()
}
下面来看下步骤:
var s Animal = nil声明了一个接口变量,此时是一个纯的空接口,动态类型和动态值都是 nil。var p *Cat = nil; s = p声明了一个动态类型*Cat赋值给空接口,但是这个指针后面指向的动态值是nil。这个阶段后接口的 动态类型是*Cat,动态值是 nil,因此判断s == nil返回 false。- 最后调用
s.Speak()触发空指针 panic,因为动态值是nil。
如果想要避免这种情况,在方法里面进行下 nil 判断就行。
go
func (c *Cat) Speak() {
if c == nil {
fmt.Println("Meow from nil cat!")
return
}
fmt.Println("Meow! My name is", c.Name)
}
6. 嵌套接口
嵌套接口意思就是 Go 中接口可以嵌入其他接口,意味者一个接口可以继承现有接口的所有方法,然后添加自己的方法。
go
type NewInterface interface {
ExistingInterface // 嵌入现有接口
NewMethod() // 添加新方法
}
上面的 NewInterface 就是继承了 ExistingInterface 的所有方法,同时加上自己的方法 NewMethod,所以如果要实现 NewInterface 接口,必须实现 ExistingInterface 的所有方法 加上 NewMethod。这种写法本质上就是将各个接口组合在一起。
下面就来看一个例子,首先我们定义两个小接口。
go
// 1. 定义两个小接口
type Flyer interface {
Fly()
}
type Swimmer interface {
Swim()
}
下面在定义一个大接口,组合两个小接口。
go
type FlyingSwimmer interface {
Flyer
Swimmer
}
接下来定义两个结构体,一个实现 FlyingSwimmer,一个只实现 Flyer。
go
// 3. duck 实现大接口
type Duck struct{}
func (d *Duck) Fly() {
fmt.Println("duck can fly")
}
func (d *Duck) Swim() {
fmt.Println("duck can swim")
}
// 4. Eagle 只实现 Fly 接口
type Eagle struct{}
func (e *Eagle) Fly() {
fmt.Println("eagle can fly")
}
然后再定义一个函数,参数是 FlyAndSwim 接口。
go
func FlyAndSwim(f FlyingSwimmer) {
f.Swim()
f.Fly()
}
最后测试如下。
go
func test6() {
// eagle := &Eagle{}
// FlyAndSwim(&eagle) // 报错 Type does not implement FlyingSwimmer as some methods are missing
duck := &Duck{}
FlyAndSwim(duck)
// duck can swim
// duck can fly
}
可以看到由于 eagle 只实现了 Fly 方法,也就是说 eagle 只实现了 Flyer,没有实现 FlyingSwimmer,所以传入 FlyAndSwim 会报错:Type does not implement FlyingSwimmer as some methods are missing,而 duck 两个方法都实现了,所以能够正常调用。
那下面我们再加一个函数,这个函数接收 Flyer 接口,调用 Fly 方法。
go
func FlyPrint(f Flyer) {
f.Fly()
}
然后测试。
go
func test6() {
// eagle := &Eagle{}
// FlyAndSwim(&eagle) // 报错 Type does not implement FlyingSwimmer as some methods are missing:
duck := &Duck{}
FlyPrint(duck)
// duck can fly
}
可以看到实现了 FlyAndSwim 接口的结构体可以传入 FlyPrint 中,也就是:大接口可以作为参数传入小接口,因为实现了大接口一定实现了小接口,而实现小接口不一定实现大接口。
7. 小结
好了,到这里接口部分就先结束,下面再来看下并发编程的部分。
如有错误,欢迎指出!!!