Golang 之 interface接口全面理解
什么是interface
在面向对象编程中,可以这么说:“接口定义了对象的行为”, 那么具体的实现行为就取决于对象了。
在Go中,接口是一组方法签名(声明的是一组方法的集合)。当一个类型为接口中的所有方法提供定义时,它被称为实现该接口。它与oop非常相似。接口指定类型应具有的方法,类型决定如何实现这些方法。
让我们来看看这个例子: Animal 类型是一个接口,我们将定义一个 Animal 作为任何可以说话的东西。这是 Go 类型系统的核心概念:我们根据类型可以执行的操作而不是其所能容纳的数据类型来设计抽象。
type Animal interface {
Speak() string
}
Animal 为任何具有 Speak 方法的类型。Speak 方法没有参数,返回一个字符串。所有定义了该方法的类型我们称它实现了 Animal 接口。Go 中没有 implements 关键字,判断一个类型是否实现了一个接口是完全是自动地。让我们创建几个实现这个接口的类型:type Dog struct {
}
func (d Dog) Speak() string {
return "Woof!"
}
type Cat struct {
}
func (c Cat) Speak() string {
return "Meow!"
}
type Llama struct {
}
func (l Llama) Speak() string {
return "?????"
}
type JavaProgrammer struct {
}
func (j JavaProgrammer) Speak() string {
return "Design patterns!"
}
我们现在有四种不同类型的动物:Dog、Cat、Llama 和 JavaProgrammer。在我们的 main 函数中,我们创建了一个 []Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}} ,看看每只动物都说了些什么:
func main() {
animals := []Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}}
for _, animal := range animals {
fmt.Println(animal.Speak())
}
}
interface{} 类型
interface{} 类型,空接口,是导致很多混淆的根源。interface{} 类型是没有方法的接口。由于没有 implements 关键字,所以所有类型都至少实现了 0 个方法,所以 所有类型都实现了空接口。这意味着,如果您编写一个函数以 interface{} 值作为参数,那么您可以为该函数提供任何值。例如:func DoSomething(v interface{}) {
// ...
}
DoSomething 函数内部,v 的类型是什么?新手们会认为 v 是任意类型的,但这是错误的。v 不是任意类型,它是 interface{} 类型。对的,没错!当将值传递给DoSomething 函数时,Go 运行时将执行类型转换(如果需要),并将值转换为 interface{} 类型的值。所有值在运行时只有一个类型,而 v 的一个静态类型是 interface{} 。interface{} 的值呢?(具体到上例来说就是 []Animal 中存的是啥?)在我们上面的例子中,当我们初始化变量 animals 时,我们不需要像这样 Animal(Dog{}) 来显示的转型,因为这是自动地。这些元素都是 Animal 类型,但是他们的底层类型却不相同。
为什么这很重要呢?理解接口是如何在内存中表示的,可以使得一些潜在的令人困惑的事情变得非常清楚。比如,像 “我可以将 []T 转换为 []interface{}
吗?” 这种问题就容易回答了。下面是一些烂代码的例子,它们代表了对 interface{} 类型的常见误解:
package main import (
"fmt"
) func PrintAll(vals []interface{}) {
for _, val := range vals {
fmt.Println(val)
}
} func main() {
names := []string{"stanley", "david", "oscar"}
PrintAll(names)
}
运行这段代码你会得到如下错误:cannot use names (type []string) as type []interface {} in argument to PrintAll。如果想使其正常工作,我们必须将 []string 转为 []interface{}:
package main import (
"fmt"
) func PrintAll(vals []interface{}) {
for _, val := range vals {
fmt.Println(val)
}
} func main() {
names := []string{"stanley", "david", "oscar"}
vals := make([]interface{}, len(names))
for i, v := range names {
vals[i] = v
}
PrintAll(vals)
}
很丑陋,但是生活就是这样,没有完美的事情。(事实上,这种情况不会经常发生,因为 []interface{} 并没有像你想象的那样有用)
指针和接口
Cat 的 Speak() 方法改为指针接收器:func (c *Cat) Speak() string {
return "Meow!"
}
运行上述代码,会得到如下错误:
cannot use Cat literal (type Cat) as type Animal in array or slice literal:
Cat does not implement Animal (Speak method has pointer receiver)
该错误的意思是:你尝试将 Cat 转为 Animal ,但是只有 *Cat 类型实现了该接口。你可以通过传入一个指针 (new(Cat) 或者 &Cat{})来修复这个错误。
animals := []Animal{Dog{}, new(Cat), Llama{}, JavaProgrammer{}}
让我们做一些相反的事情:我们传入一个 *Dog 指针,但是不改变 Dog 的 Speak() 方法:
animals := []Animal{new(Dog), new(Cat), Llama{}, JavaProgrammer{}}
这种方式可以正常工作,因为一个指针类型可以通过其相关的值类型来访问值类型的方法,但是反过来不行。即,一个 *Dog 类型的值可以使用定义在 Dog 类型上的 Speak() 方法,而 Cat 类型的值不能访问定义在 *Cat 类型上的方法。
这可能听起来很神秘,但当你记住以下内容时就清楚了:Go 中的所有东西都是按值传递的。每次调用函数时,传入的数据都会被复制。对于具有值接收者的方法,在调用该方法时将复制该值。例如下面的方法:
func (t T)MyMethod(s string) {
// ...
}
是 func(T, string) 类型的方法。方法接收器像其他参数一样通过值传递给函数。
*Cat 的方法不能被 Cat 类型的值调用了。任何一个 Cat 类型的值可能会有很多 *Cat 类型的指针指向它,如果我们尝试通过 Cat 类型的值来调用 *Cat 的方法,根本就不知道对应的是哪个指针。相反,如果 Dog 类型上有一个方法,通过 *Dog 来调用这个方法可以确切的找到该指针对应的 Gog 类型的值,从而调用上面的方法。运行时,Go 会自动帮我们做这些,所以我们不需要像 C语言中那样使用类似如下的语句 d->Speak() 。结语
我希望读完此文后你可以更加得心应手地使用 Go 中的接口,记住下面这些结论:
- 通过考虑数据类型之间的相同功能来创建抽象,而不是相同字段
interface{}的值不是任意类型,而是interface{}类型- 接口包含两个字的大小,类似于
(type, value) - 函数可以接受
interface{}作为参数,但最好不要返回interface{} - 指针类型可以调用其所指向的值的方法,反过来不可以
- 函数中的参数甚至接受者都是通过值传递
- 一个接口的值就是就是接口而已,跟指针没什么关系
- 如果你想在方法中修改指针所指向的值,使用
*操作符
Golang 之 interface接口全面理解的更多相关文章
- C#.NET常见问题(FAQ)-interface接口如何理解
个人把interface理解为一种比较特殊的判断技巧,不是常规的变量类型比如判断字符串,判断数组,而是判断类的实例是否拥有某些属性或者方法(比如有十个女的穿一样的衣服,头上盖住,让新郎去猜哪一个是他的 ...
- golang基础--Interface接口
接口是一个或多个方法签名名的集合,定义方式如下 type Interface_Name interface { method_a() string method_b() int .... } 只要某个 ...
- Golang 入门系列(四)如何理解interface接口
前面讲了很多Go 语言的基础知识,包括go环境的安装,go语言的语法等,感兴趣的朋友,可以先看看之前的文章.https://www.cnblogs.com/zhangweizhong/category ...
- golang面向对象和interface接口
一. golang面向对象介绍 1.golang也支持面向对象编程,但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言.2.golang没有类(class),golang语言的结合体(struc ...
- Golang基础(8):go interface接口
一:接口概要 接口是一种重要的类型,他是一组确定的方法集合. 一个接口变量可以存储任何实现了接口方法的具体值.一个重要的例子就是io.Reader和io.Writer type Reader inte ...
- Golang的Interface是个什么鬼
问题概述 Golang的interface,和别的语言是不同的.它不需要显式的implements,只要某个struct实现了interface里的所有函数,编译器会自动认为它实现了这个interfa ...
- golang的interface剖析
背景: golang的interface是一种satisfied式的.A类只要实现了IA interface定义的方法,A就satisfied了接口IA.更抽象一层,如果某些设计上需要一些更抽象的 ...
- Golang的反射reflect深入理解和示例
编程语言中反射的概念 在计算机科学领域,反射是指一类应用,它们能够自描述和自控制.也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examin ...
- golang 关于 interface 的学习整理
Golang-interface(四 反射) go语言学习-reflect反射理解和简单使用 为什么在Go语言中要慎用interface{} golang将interface{}转换为struct g ...
随机推荐
- 《温故而知新》JAVA基础一
一:基本环境 种类 javaSE(核心语法)+javaME(移动设备)+Java EE(JSP/服务等) JVM(java虚拟机) abc.java(源文件)--编译器--abc.class 字节码文 ...
- js数组和数组去重的几种简单的方法
http://blog.csdn.net/liangklfang/article/details/49300417 1.证明一个对象是数组的方法. 方法(1) [].constructor === A ...
- 二叉树放置照相机 Binary Tree Cameras
2019-03-27 15:39:37 问题描述: 问题求解: 很有意思的问题,问题描述简单,求解过程也可以非常的简洁,是个难得的好题. 求解的过程是自底向上进行分析,对于叶子节点,如果在叶子上放置照 ...
- 《SQL 基础教程》第二章:查询基础
这一章的结构如下: SELECT 语句基础 算术运算符和比较运算符 逻辑运算符 SELECT 语句可用于查询数据,并且可以设定条件来查询具有特定值的记录.条件的设定就需要算数运算符.比较运算符和逻辑运 ...
- js里面关于日期转换的问题
我们拿到一个日期字符串:"2017-09-03",我们用new Date("2017-09-03")去转换成日期格式的时候,发现在火狐会报错,是因为火狐不支持这 ...
- laravel 路由的配置
- 【Oracle】【8】大批量update某个字段
正文: 需实现:将A表的某个字段的值复制到B表中 我们一般会这样写:UPDATE B SET B.NAME = (SELECT A.NAME FROM A WHERE A.NO = B.NO) 出现的 ...
- vue2.0s中eventBus实现兄弟组件通信
在vue1.0中,组件之间的通信主要通过vm.$dispatch沿着父链向上传播和用vm.$broadcast向下广播来实现.然而在vue2.0中,已经废除了这种用法. vuex加入后,对组件之间的通 ...
- idea中springboot项目程序入口右键不显示run as的原因
今天在idea中导入了springboot的项目,但是在程序的入口处右键单击没有出现run as 的程序启动方式,主要原因在于idea中右面的maven projects中没加载项目,需要点击“+“ ...
- SSM 框架 整合<SpringMVC+Spring+MyBatis>
一 框架的搭建1.建立一个maven项目 2.建立五个module(entity,dao,service,action,web-view) 3.给予它们之间的依赖关系 dao-->entity ...