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

相关文章

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

flume系列之:查看flume系统日志、查看统计flume日志类型、查看flume日志

遍历指定目录下多个文件查找指定内容 服务器系统日志会记录flume相关日志 cat /var/log/messages |grep -i oom 查找系统日志中关于flume的指定日志 import osdef search_string_in_files(directory, search_string):count = 0

bytes.split的用法和注意事项

当然,我很乐意详细介绍 bytes.Split 的用法和注意事项。这个函数是 Go 标准库中 bytes 包的一个重要组成部分,用于分割字节切片。 基本用法 bytes.Split 的函数签名如下: func Split(s, sep []byte) [][]byte s 是要分割的字节切片sep 是用作分隔符的字节切片返回值是一个二维字节切片,包含分割后的结果 基本使用示例: pa

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚:

Java 后端接口入参 - 联合前端VUE 使用AES完成入参出参加密解密

加密效果: 解密后的数据就是正常数据: 后端:使用的是spring-cloud框架,在gateway模块进行操作 <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.0-jre</version></dependency> 编写一个AES加密

Mysql BLOB类型介绍

BLOB类型的字段用于存储二进制数据 在MySQL中,BLOB类型,包括:TinyBlob、Blob、MediumBlob、LongBlob,这几个类型之间的唯一区别是在存储的大小不同。 TinyBlob 最大 255 Blob 最大 65K MediumBlob 最大 16M LongBlob 最大 4G

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla

MyBatis 切换不同的类型数据库方案

下属案例例当前结合SpringBoot 配置进行讲解。 背景: 实现一个工程里面在部署阶段支持切换不同类型数据库支持。 方案一 数据源配置 关键代码(是什么数据库,该怎么配就怎么配) spring:datasource:name: test# 使用druid数据源type: com.alibaba.druid.pool.DruidDataSource# @需要修改 数据库连接及驱动u