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. 小结

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

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

相关推荐
喵了几个咪4 小时前
GoWind CMS 拆解:在 Go 里给 TipTap 搭一套 Yjs 协同后端
开发语言·后端·golang
学长毕设4 小时前
基于SpringBoot的青少年编程学习平台系统-附源码
spring boot·学习·青少年编程
小小de风呀8 小时前
de风——【从零开始学习Linux】(九):进程基础 —— 从冯诺依曼体系到 fork 创建进程
linux·学习
李某学编程8 小时前
基于ARM的微机原理学习笔记(一)
arm开发·笔记·学习
Song_RsHt9 小时前
Redis 与微服务,什么场景下无可替代
java·redis·后端·学习·微服务
l1t11 小时前
测试用go语言执行脚本把数据保存到SQLite数据库
数据库·golang·sqlite
~kiss~12 小时前
PRO-LONG: Programmatic Memory Enables Long-Horizon Reasoning 程序式记忆赋能长程推理
学习
程序猿编码12 小时前
不依赖 vLLM/llama.cpp:一套面向学习的 DeepSeek-R1 CUDA 推理实现
c++·学习·语言模型·llama·vllm·大模型推理
ms72wx12 小时前
Docker 命令学习笔记:从入门到进阶的实战指南
笔记·学习·docker
外收内放13 小时前
FastAPI猜字谜实战(4):让AI主持人开口
开发语言·python·学习·fastapi