Java 转 go 学习 - 接口

文章目录

  • [1. 接口](#1. 接口)
  • [2. 隐式实现](#2. 隐式实现)
  • [3. 类型断言](#3. 类型断言)
  • [4. 空接口](#4. 空接口)
  • [5. 接口和 nil 值](#5. 接口和 nil 值)
  • [6. 嵌套接口](#6. 嵌套接口)
  • [7. 小结](#7. 小结)

本系列文章:


上一篇文章我们学习了结构体,这一篇文章就来看下 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. 小结

好了,到这里接口部分就先结束,下面再来看下并发编程的部分。

如有错误,欢迎指出!!!

相关推荐
小小龙学IT1 小时前
Go 语言 reflect 反射包深度解析:从 Type/Value 到三大定律与工业实践
前端·golang
I Am a robert girl1 小时前
从零读懂世界模型的持续学习:一份组合式基准的源码级拆解
学习·具身智能·持续学习·灾难性遗忘·机器人学习·世界模型·组合式基准
扶风ff1 小时前
练题簿在线免费刷题:刷题、背题、错题复习,一个小程序安排日常备考
学习·小程序
東隅已逝,桑榆非晚1 小时前
数据结构队列
数据结构·笔记·学习
老王爱玩车1 小时前
字符串和字符串函数
c语言·开发语言·数据结构·学习
扶风ff1 小时前
练题簿在线免费刷题:共享题库、学习任务、在线考试,岗位培训更方便
学习·小程序
JWASX1 小时前
Java 转 go 学习 - 并发编程(3)
学习·golang
传奇开心果编程2 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
欣欣之王来了2 小时前
5G网络基础:5G的核心特性,eMBB、uRLLC、mMTC的应用场景
网络·学习·计算机网络·安全·教程