【14】GO语言的接口类型

简介:
原创作品,允许转载,转载时请务必以超链接形式标明文章  原始出处 、作者信息和本声明。否则将追究法律责任。 http://qingkechina.blog.51cto.com/5552198/1675115

1、什么是接口?

在面向对象的语言中,接口用来限制实现类行为的。怎么理解这句话呢?


定义一个Person接口,我只会站在我的角度上考虑问题,比如Person(人),自然想到会吃饭、睡觉等:

interface Person

{

      // 人会吃饭

      void eat();


      // 人会睡觉

      void sleep();

}


我是站在接口角度上考虑接口如何定义,此时不会过多考虑实现类的行为。

这很正常,因为我不能确定谁会使用我的接口,有一天SuperMan说:“我要用你定义的接口”,那SuperMan必须用implements实现Person接口的行为:

// SuperMan实现Person接口

public class SuperMan implements Person

{

      // 超人会吃饭

       public void eat()

       {

              System.out.println("super man can eat.");

       }


       // 超人会睡觉

       public void sleep()

       {

              System.out.println("super man can sleep.");

       }

}


等到SuperMan实现完了之后,他对我说:“作为超人,我是会飞的哦~”

这时作为Person定义者的我,只有两个选择:

  • 对SuperMan说:“飞是你自己的行为,我不帮你定义这种行为!”。可是经过若干万年之后人类进化了怎么办?

  • 对SuperMan说:“好吧,我帮你定义这种行为,可是一旦Person有了fly,你也必须实现fly”

其实无论上面哪种结果,都相当于接口把实现类绑架了。


【备注】:悄悄地告诉你,上面的代码是Java语言                                                                                                      



2、GO语言的接口呢?

GO语言有接口类型interface{}),它与面向对象的接口含义不同,GO语言的接口类型与数组(array)、切片(slice)、集合(map)、结构体(struct)是同等地位的。怎么理解这句话呢?

我们前面已知道:

var num int  // 定义了一个int型变量num

同理:

var any interface{} // 定义了一个接口类型变量any

从这个角度上看,GO的interface{}与面向对象的接口是不一样吧。C_0009.gif   更加不一样的是,interface{}是一个任意类型,或者说是万能类型。


3、GO语言的任意类型

也就是说定义一个变量为interface{}类型,可以把任意的值赋给这个变量,例如:

var v1 interface{} = 250       // 把int值赋给interface{}

var v2 interface{} = "eagle" // 把string值赋给interface{}

var v3 interface{} = &v1      // 把v1的地址赋给interface{}

当然函数的入参类型也可以是interface{},这样函数就可以接受任意类型的参数,例如GO语言标准库fmt中的函数Println()

func Println(args ...interface{}){

        // 略

}


任意类型看起来很爽,可以把任意的值都赋给interface{}类型变量,就像JDK1.4时的Vector,那时候Java还没有泛型的概念,任意值都可以向Vector里面放,但问题也接踵而至:

// 定义一个长度为3的Any类型数组,求数组元素之和,即anyArr[0]+anyArr[1]+anyArr[2]

var anyArr [3]interface{}

anyArr[0] = "eagle"    // anyArr[0]赋值为字符串

anyArr[1] = 20           // anyArr[1]赋值为int

anyArr[2] = 75.3        // anyArr[2]赋值为float64

// 此时若求和,会有什么结果呢?

fmt.Println(anyArr[0] + anyArr[1] + anyArr[2])


4、类型判断

上例直观上来看,string不能和int直接相加,所以我们需要判断元素类型,若元素类型是数字型的,我们就执行“+”操作;若元素类型是字符串型的,我们就跳过。这里需要引入另外一个知识:switch-type

即:拿到一个interface{}之后,可以结合switch语句判断变量的类型                                                                            

例如:

var v interface{} = 3

switch v.(type){

      case int:

              fmt.Println("3 is int")

      case string:

              fmt.Println("3 is string")

      default:

              fmt.Println("unkown type")

}

所以上面的例子可以进一步修改如下:

// 定义一个长度为3的Any类型数组,求数组元素之和,即anyArr[0]+anyArr[1]+anyArr[2]

var anyArr [3]interface{}

anyArr[0] = "eagle"

anyArr[1] = 20

anyArr[2] = 75.3


// 定义一个总和变量total

var total float64 = 0


// 遍历Any类型数组

for i := 0; i < len(anyArr); i++ {

        // 针对Any类型数组中的每个元素进行类型查询

        switch vType := anyArr[i].(type) {

              case int:

                      total += float64(vType)

              case float64:

                      total += vType

              default:

                      // do nothing

        }

}


// 打印Any类型数组中数字之和

fmt.Println(total)


5、interface类型与struct类型

 从上面看interface类型很简单嘛,或许吧。

我们再回顾一下struct类型,struct类型是一个结构体,里面可以定义成员,它类似面向对象的一个类,类里面可以定义成员变量,比如:

// 定义一个person结构体,里面有姓名、年龄、身高、体重成员

type person struct{

        name                 string

        age                    int

        height, weight   float64

}

那么interface类型是否也可以这样定义呢?如下:

/**

 * 定义一个手表接口,通过手表接口我们可以知道小时、分钟和秒

 */

type watch interface {

        getHour() int

        getMinute() int

        getSecond() int

}

通过编译(go build myIf.go)会发现并没有抛出错误!

wKiom1WlKLnStanZAADiDZWEgn0265.jpg

从结果可以看出完全可以这样定义一个类型为interface的变量watch,并且还可以为watch增加相应的方法;但与struct不同的是:struct里面的成员是变量,而interface里面的成员是函数,即我们可以使用interface定义接口。


6、interface定义接口示例

(1)GO语言接口实现

       周围的不少朋友现在都有一款iWatch智能手表,一般都用来运动时监控心率,这也意味着iWatch不仅能看时间这么简单。下面我们定义一个iWatch类型:

type iWatch int    // 定义一个iWatch类型,它实际上就是int型;相当于为int型取一个别名iWatch

接下来为iWatch类型增加三个方法,分别为getHour()、getMinute()、getSecond()

// 为iWatch增加getHour()方法

func (w iWatch) getHour() int {

        return time.Now().Hour()

}


// 为iWatch增加getMinute()方法

func (w iWatch) getMinute() int {

        return time.Now().Minute()

}


// 为iWatch增加getSecond()方法

func (w iWatch) getSecond() int {

        return time.Now().Second()

}

下面是GO语言的精彩内容,请各位看客睁大眼睛:

func main() {

        var w watch  // 定义类型为watch的变量w

        var t iWatch  // 定义类型为iWatch的变量t

        w = t             // 把类型为watch的变量w赋值给类型为iWatch的变量t,这样能行的通吗?

        fmt.Println("Current Hour:", w.getHour(), ", Minute:", w.getMinute(), ", Second:", w.getSecond())

}

在这个测试代码中:

var w watch

相当于定义了一个接口变量

var t iWatch

相当于定义了一个iWatch对象

w = t

直接把对象t 赋给了接口变量w,但没有像其它面向对象语言那样,让iWatch implements watch,这样能行的通吗?

把“吗”字去掉,请看结果:

wKioL1WmVRujSREcAAB1baVrR5Y574.jpg

神奇吧 :)

以上是GO语言的接口实现。


(2)Java语言的接口实现

用面向对象的编程语言来解释:

wKiom1WmVGnjDIhoAAAaPrHbUKw134.jpg

在面向对象的编程语言中,比如Son是一个实现类,Father是一个接口,Son要实现Father接口,必须使用implements显式地声明,同时在Son中实现Father里面定义的方法,比如:

interface Father{

         getHour();

}


class Son implements Father{

         // 实现父接口Father定义的方法getHour()

         public int getHour(){

                 return 20;

         }      

}

一旦接口Father增加一个方法getSecond(),那么实现该接口的所有孩儿都必须实现getSecond()方法。在使用时:

Father father = new Son();

即孩儿对象可以赋值给Father接口


【备注】:若对上面面向对象编程语言不熟悉的话,建议看一下设计模式相关的书籍



(3)侵入式接口和非侵入式接口

像上面(2)中的Java就是侵入式接口。

GO语言中,像上面(1)所示,尽管定义了接口watch,但实现类iWatch并没有显示地声明实现该接口,只是watch中的方法都已在iWatch中实现,那么这种父子关系已建立,这种接口被称为“非侵入式接口”


7、引申

上面例子中,为iWatch定义了三个方法,现在我们修改一下这三个方法:

// 为*iWatch增加getHour()方法

func (w *iWatch) getHour() int {

        return time.Now().Hour()

}


// 为*iWatch增加getMinute()方法

func (w *iWatch) getMinute() int {

        return time.Now().Minute()

}


// 为*iWatch增加getSecond()方法

func (w *iWatch) getSecond() int {

        return time.Now().Second()

}

这相当于并不是为iWatch类型增加了三个方法,而是为*iWatch类型增加了三个方法,那么调用时也需要相应修改:

func main() {

        var w watch

        var t iWatch

        w = &t        

        fmt.Println("Current Hour:", w.getHour(), ", Minute:", w.getMinute(), ", Second:", w.getSecond())

}


好了,关于GO语言的接口类型就聊到这里,请记住这么三句话:

  • interface是类型

  • interface类型的变量可以赋值

  • 任何实现了interface类型的具体类型变量,都可以赋值给interface类型的变量

本文出自 “青客” 博客,请务必保留此出处http://qingkechina.blog.51cto.com/5552198/1675115

目录
相关文章
|
21小时前
|
存储 编译器 Go
Go语言学习12-数据的使用
【5月更文挑战第5天】本篇 Huazie 向大家介绍 Go 语言数据的使用,包含赋值语句、常量与变量、可比性与有序性
17 6
Go语言学习12-数据的使用
|
2天前
|
Java Go
一文带你速通go语言指针
Go语言指针入门指南:简述指针用于提升效率,通过地址操作变量。文章作者sharkChili是Java/CSDN专家,维护Java Guide项目。文中介绍指针声明、取值,展示如何通过指针修改变量值及在函数中的应用。通过实例解析如何使用指针优化函数,以实现对原变量的直接修改。作者还邀请读者加入交流群深入探讨,并鼓励关注其公众号“写代码的SharkChili”。
9 0
|
2天前
|
存储 缓存 Java
来聊聊go语言的hashMap
本文介绍了Go语言中的`map`与Java的不同设计思想。作者`sharkChili`是一名Java和Go开发者,同时也是CSDN博客专家及JavaGuide项目的维护者。文章探讨了Go语言`map`的数据结构,包括`count`、`buckets指针`和`bmap`,解释了键值对的存储方式,如何利用内存对齐优化空间使用,并展示了`map`的初始化、插入键值对以及查找数据的源码过程。此外,作者还分享了如何通过汇编查看`map`操作,并鼓励读者深入研究Go的哈希冲突解决和源码。最后,作者提供了一个交流群,供读者讨论相关话题。
10 0
|
3天前
|
Java Go
Go语言学习11-数据初始化
【5月更文挑战第3天】本篇带大家通过内建函数 new 和 make 了解Go语言的数据初始化过程
17 1
Go语言学习11-数据初始化
|
3天前
|
自然语言处理 安全 Java
速通Go语言编译过程
Go语言编译过程详解:从词法分析(生成token)到句法分析(构建语法树),再到语义分析(类型检查、推断、匹配及函数内联)、生成中间码(SSA)和汇编码。最后,通过链接生成可执行文件。作者sharkchili,CSDN Java博客专家,分享技术细节,邀请读者加入交流群。
22 2
|
4天前
|
Java Linux Go
一文带你速通Go语言基础语法
本文是关于Go语言的入门介绍,作者因其简洁高效的特性对Go语言情有独钟。文章首先概述了Go语言的优势,包括快速上手、并发编程简单、设计简洁且功能强大,以及丰富的标准库。接着,文章通过示例展示了如何编写和运行Go代码,包括声明包、导入包和输出语句。此外,还介绍了Go的语法基础,如变量类型(数字、字符串、布尔和复数)、变量赋值、类型转换和默认值。文章还涉及条件分支(if和switch)和循环结构(for)。最后,简要提到了Go函数的定义和多返回值特性,以及一些常见的Go命令。作者计划在后续文章中进一步探讨Go语言的其他方面。
10 0
|
5天前
|
JavaScript 前端开发 Go
Go语言的入门学习
【4月更文挑战第7天】Go语言,通常称为Golang,是由Google设计并开发的一种编程语言,它于2009年公开发布。Go的设计团队主要包括Robert Griesemer、Rob Pike和Ken Thompson,这三位都是计算机科学和软件工程领域的杰出人物。
13 1
|
5天前
|
Go
|
5天前
|
分布式计算 Java Go
Golang深入浅出之-Go语言中的分布式计算框架Apache Beam
【5月更文挑战第6天】Apache Beam是一个统一的编程模型,适用于批处理和流处理,主要支持Java和Python,但也提供实验性的Go SDK。Go SDK的基本概念包括`PTransform`、`PCollection`和`Pipeline`。在使用中,需注意类型转换、窗口和触发器配置、资源管理和错误处理。尽管Go SDK文档有限,生态系统尚不成熟,且性能可能不高,但它仍为分布式计算提供了可移植的解决方案。通过理解和掌握Beam模型,开发者能编写高效的数据处理程序。
134 1
|
6天前
|
算法 关系型数据库 MySQL
Go语言中的分布式ID生成器设计与实现
【5月更文挑战第6天】本文探讨了Go语言在分布式系统中生成全局唯一ID的策略,包括Twitter的Snowflake算法、UUID和MySQL自增ID。Snowflake算法通过时间戳、节点ID和序列号生成ID,Go实现中需处理时间回拨问题。UUID保证全局唯一,但长度较长。MySQL自增ID依赖数据库,可能造成性能瓶颈。选择策略时需考虑业务需求和并发、时间同步等挑战,以确保系统稳定可靠。
113 0