Golang | 既是接口又是类型,interface是什么神仙用法?

2024-03-21 14:18

本文主要是介绍Golang | 既是接口又是类型,interface是什么神仙用法?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文始发于个人公众号:TechFlow,原创不易,求个关注


今天是golang专题的第12篇文章,我们来继续聊聊interface的使用。

在上一篇文章当中我们介绍了面向对象的一些基本概念,以及golang当中interface和多态的实现方法。今天我们继续来介绍interface当中其他的一些方法。


万能类型interface


在Java以及其他语言当中接口是一种写法规范,而在golang当中,interface其实也是一种值,它可以像是值一样传递。并且在它的底层,它其实是一个值和类型的元组。

这里我们来看下golang官方文档当中的一个例子:

package mainimport ("fmt""math"
)type I interface {M()
}type T struct {S string
}func (t *T) M() {fmt.Println(t.S)
}type F float64func (f F) M() {fmt.Println(f)
}func main() {var i Ii = &T{"Hello"}describe(i)i.M()i = F(math.Pi)describe(i)i.M()
}func describe(i I) {fmt.Printf("(%v, %T)\n", i, i)
}

在上面的代码当中定义了一个叫做describe的方法,在这个方法当中我们输出了两个值,一个是接口i对应的值,另一个是接口i的类型

我们输出的结果如下:

image-20200724084346988

可以看到接口当中既存储了对应的结构体的实例的信息,也存储了结构体的类型。因此interface可以理解成一种特殊的类型。

实际上也的确如此,我们可以把interface理解成一种万能数据类型,它可以接收任何类型的值。我们看下下面这种用法:

var a1 interface{} = 1
var a2 interface{} = "abc"
list := make([]interface{}, 0)
list = append(list, a1)
list = append(list, a2)
fmt.Println(list)

在代码当中我们创建了一个interface{}类型的slice,它可以接收任何类型的值和实例。另外我们用interface{}这个类型也可以接收任何结构体的值。这里可能会有些迷惑,其实很容易想明白。interface表示一种类型,可以接收任何实现了interface当中规定的方法的类型的值。当我们定义inteface{}的时候,其实是定义了空的interface,相当于不需要实现任何方法的空interface,所以任何类型都可以接收,这也就是它成为万能类型的原因。

我们接收当然没有问题,问题是我们怎么使用这些interface类型的值呢?

一种方法是我们可以判断一个interface的变量类型。判断的方法非常简单,我们在interface的变量后面用.(type)的方法来判断。它和map的key值判断一样,会返回一个值和bool类型的标记。我们可以通过这个标记判断这个类型是否正确。

if v, ok := a1.(int); ok {fmt.Println(v)
}

如果类型比较多的话使用switch也是可以的:

switch v := i.(type) {
case int:fmt.Println("int")
case string:fmt.Println("string")
}

空值nil


interface类型的空值是nil,和Python当中的None是一个意思,表示一个指针指向空。如果我们在Java或者是其他语言当中对一个空指针调用方法,那么会触发NullPointerMethodError,也就是空指针报错。这也是我们初学者在编程当中最容易遇到的错误,往往原因是忘记了对声明进行初始化导致的。

但是在golang当中不会,即使是nil也可以调用interface的方法。举个例子:

type T struct {S string
}func (t *T) M() {fmt.Println(t.S)
}func main() {var i Ivar t *Ti = ti.M()
}

我们将t赋值给了i,问题是t并没有进行初始化,所以它是一个nil,那么我们的i也就会是一个nil。我们对nil调用M方法,在M方法当中我们打印了t的局部变量S。由于t此刻是一个nil,它并没有这个变量,所以会引发一个invalid memory address or nil pointer derefernce的错误,也就是对空指针进行寻址的错误。

要解决这个错误,其实很简单,我们可以在M方法当中对t进行判断,如果发现t是一个nil,那么我们则跳过执行的逻辑。当我们把M函数改成这样之后,就不会触发空指针的问题了。

func (t *T) M() {if t == nil {fmt.Println("nil")return}fmt.Println(t.S)
}

nil触发异常的问题也是初学者经常遇到的问题之一,这也要求我们在实现结构体内方法的时候一定要记得判断调用的对象是否为nil,避免不必要的问题。


赋值的类型选择


我们都知道golang当中通过interface来实现多态,只要是实现了interface当中定义的函数,那么我们就可以将对应的实例赋值给这个interface类型。

这看起来没有问题,但是在实际执行的时候仍然会有一点点小小的问题。比如说我们有这样一段代码:

type Integer inttype Operation interface {Less(b Integer) boolAdd(b Integer)
}func (a Integer) Less(b Integer) bool {return a < b
}func (a *Integer) Add(b Integer) {*a += b
}

这段代码非常简单,我们定义了一个Operation的interface,并且实现了Integer类型的两个方法。表面上看一切正常,但是有一个细节。Less和Add这两个方法针对的类型是不同的,Less方法我们不需要修改原值,所以我们传入的是Integer的值,而Add方法,我们需要修改原值, 所以我们传入的类型是Integer的指针。

那么问题来了,这两个方法的类型不同, 我们还可以将它的值赋值给Operation这个interface吗?如果可以的话,我们应该传递的是值还是指针呢?下面代码当中的第二行和第三行究竟哪个是正确的呢?

var a Integer = 1
var b Operation = &a
var b Operation = a

答案是第二行的是正确的,原因也很简单,因为我们传入指针之后,golang的编译器会自动生成一个新的Less方法。在这个转换了类型的方法当中去调用了原本的方法,相当于做了一层中转。

func (a *Integer) Less(b Integer) bool{return (*a).Less(b)
}

那反过来行不行呢?我们也写出代码:

func (a Integer) Add (b Integer) {(&a).Add(b)
}

显然这样是不行的,因为函数执行之后修改的只能是Add这个方法当中a这个参数的值,而没办法修改原值。这和我们想要的不符合,所以golang没有选择这种策略。


总结


在今天的文章当中我们介绍了golang当中interface的一些高级用法,比如将它作为万能类型来接收各种格式的值。比如interface的空指针调用问题,以及interface中的两个函数接收类型不一致的问题。

也就是说在go语言当中,interface既是一种多态实现的规范,又有全能类型这样衍生的功能,这个设计的确是很惊艳的。对interface的熟练使用可以在一些问题当中大大降低我们编码的复杂度,以及运行的效率。这也是golang的原生优势之一。


相关阅读


面向对象回顾,golang中多态的实现方法

今天的文章到这里就结束了,如果喜欢本文的话,请来一波素质三连,给我一点支持吧(关注、转发、点赞)。

在这里插入图片描述

这篇关于Golang | 既是接口又是类型,interface是什么神仙用法?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/832869

相关文章

详解Java如何向http/https接口发出请求

《详解Java如何向http/https接口发出请求》这篇文章主要为大家详细介绍了Java如何实现向http/https接口发出请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用Java发送web请求所用到的包都在java.net下,在具体使用时可以用如下代码,你可以把它封装成一

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck

Golang的CSP模型简介(最新推荐)

《Golang的CSP模型简介(最新推荐)》Golang采用了CSP(CommunicatingSequentialProcesses,通信顺序进程)并发模型,通过goroutine和channe... 目录前言一、介绍1. 什么是 CSP 模型2. Goroutine3. Channel4. Channe

IDEA如何将String类型转json格式

《IDEA如何将String类型转json格式》在Java中,字符串字面量中的转义字符会被自动转换,但通过网络获取的字符串可能不会自动转换,为了解决IDEA无法识别JSON字符串的问题,可以在本地对字... 目录问题描述问题原因解决方案总结问题描述最近做项目需要使用Ai生成json,可生成String类型

Java后端接口中提取请求头中的Cookie和Token的方法

《Java后端接口中提取请求头中的Cookie和Token的方法》在现代Web开发中,HTTP请求头(Header)是客户端与服务器之间传递信息的重要方式之一,本文将详细介绍如何在Java后端(以Sp... 目录引言1. 背景1.1 什么是 HTTP 请求头?1.2 为什么需要提取请求头?2. 使用 Spr

oracle中exists和not exists用法举例详解

《oracle中exists和notexists用法举例详解》:本文主要介绍oracle中exists和notexists用法的相关资料,EXISTS用于检测子查询是否返回任何行,而NOTE... 目录基本概念:举例语法pub_name总结 exists (sql 返回结果集为真)not exists (s

Mysql 中的多表连接和连接类型详解

《Mysql中的多表连接和连接类型详解》这篇文章详细介绍了MySQL中的多表连接及其各种类型,包括内连接、左连接、右连接、全外连接、自连接和交叉连接,通过这些连接方式,可以将分散在不同表中的相关数据... 目录什么是多表连接?1. 内连接(INNER JOIN)2. 左连接(LEFT JOIN 或 LEFT

Golang使用minio替代文件系统的实战教程

《Golang使用minio替代文件系统的实战教程》本文讨论项目开发中直接文件系统的限制或不足,接着介绍Minio对象存储的优势,同时给出Golang的实际示例代码,包括初始化客户端、读取minio对... 目录文件系统 vs Minio文件系统不足:对象存储:miniogolang连接Minio配置Min

Golang使用etcd构建分布式锁的示例分享

《Golang使用etcd构建分布式锁的示例分享》在本教程中,我们将学习如何使用Go和etcd构建分布式锁系统,分布式锁系统对于管理对分布式系统中共享资源的并发访问至关重要,它有助于维护一致性,防止竞... 目录引言环境准备新建Go项目实现加锁和解锁功能测试分布式锁重构实现失败重试总结引言我们将使用Go作

Redis的Hash类型及相关命令小结

《Redis的Hash类型及相关命令小结》edisHash是一种数据结构,用于存储字段和值的映射关系,本文就来介绍一下Redis的Hash类型及相关命令小结,具有一定的参考价值,感兴趣的可以了解一下... 目录HSETHGETHEXISTSHDELHKEYSHVALSHGETALLHMGETHLENHSET