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

相关文章

JDK21对虚拟线程的几种用法实践指南

《JDK21对虚拟线程的几种用法实践指南》虚拟线程是Java中的一种轻量级线程,由JVM管理,特别适合于I/O密集型任务,:本文主要介绍JDK21对虚拟线程的几种用法,文中通过代码介绍的非常详细,... 目录一、参考官方文档二、什么是虚拟线程三、几种用法1、Thread.ofVirtual().start(

Java8 Collectors.toMap() 的两种用法

《Java8Collectors.toMap()的两种用法》Collectors.toMap():JDK8中提供,用于将Stream流转换为Map,本文给大家介绍Java8Collector... 目录一、简单介绍用法1:根据某一属性,对对象的实例或属性做映射用法2:根据某一属性,对对象集合进行去重二、Du

MySQL中C接口的实现

《MySQL中C接口的实现》本节内容介绍使用C/C++访问数据库,包括对数据库的增删查改操作,主要是学习一些接口的调用,具有一定的参考价值,感兴趣的可以了解一下... 目录准备mysql库使用mysql库编译文件官方API文档对象的创建和关闭链接数据库下达sql指令select语句前言:本节内容介绍使用C/

Python中isinstance()函数原理解释及详细用法示例

《Python中isinstance()函数原理解释及详细用法示例》isinstance()是Python内置的一个非常有用的函数,用于检查一个对象是否属于指定的类型或类型元组中的某一个类型,它是Py... 目录python中isinstance()函数原理解释及详细用法指南一、isinstance()函数

Python中的sort方法、sorted函数与lambda表达式及用法详解

《Python中的sort方法、sorted函数与lambda表达式及用法详解》文章对比了Python中list.sort()与sorted()函数的区别,指出sort()原地排序返回None,sor... 目录1. sort()方法1.1 sort()方法1.2 基本语法和参数A. reverse参数B.

vue监听属性watch的用法及使用场景详解

《vue监听属性watch的用法及使用场景详解》watch是vue中常用的监听器,它主要用于侦听数据的变化,在数据发生变化的时候执行一些操作,:本文主要介绍vue监听属性watch的用法及使用场景... 目录1. 监听属性 watch2. 常规用法3. 监听对象和route变化4. 使用场景附Watch 的

Java Instrumentation从概念到基本用法详解

《JavaInstrumentation从概念到基本用法详解》JavaInstrumentation是java.lang.instrument包提供的API,允许开发者在类被JVM加载时对其进行修改... 目录一、什么是 Java Instrumentation主要用途二、核心概念1. Java Agent

Java 中 Optional 的用法及最佳实践

《Java中Optional的用法及最佳实践》在Java开发中,空指针异常(NullPointerException)是开发者最常遇到的问题之一,本篇文章将详细讲解Optional的用法、常用方... 目录前言1. 什么是 Optional?主要特性:2. Optional 的基本用法2.1 创建 Opti

基于Go语言开发一个 IP 归属地查询接口工具

《基于Go语言开发一个IP归属地查询接口工具》在日常开发中,IP地址归属地查询是一个常见需求,本文将带大家使用Go语言快速开发一个IP归属地查询接口服务,有需要的小伙伴可以了解下... 目录功能目标技术栈项目结构核心代码(main.go)使用方法扩展功能总结在日常开发中,IP 地址归属地查询是一个常见需求:

Python函数的基本用法、返回值特性、全局变量修改及异常处理技巧

《Python函数的基本用法、返回值特性、全局变量修改及异常处理技巧》本文将通过实际代码示例,深入讲解Python函数的基本用法、返回值特性、全局变量修改以及异常处理技巧,感兴趣的朋友跟随小编一起看看... 目录一、python函数定义与调用1.1 基本函数定义1.2 函数调用二、函数返回值详解2.1 有返