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 ...
随机推荐
- U3D外包团队:五款IDE推荐!
1. Jetbrains RubyMine RubyMine是由捷克Jetbrains公司开发的,目前可提供使用的版本有RubyMine 5.4.而且RubyMine 5.4同时也为Rails 4的发 ...
- English trip V2 - A 1. Fastival Teacher:Julia Key:
In this lesson you will learn to talk about dates and times. 课上内容(Lesson) Tell your tutor about a fe ...
- 『TensorFlow』读书笔记_Word2Vec
由于暂时不会深入接触NLP相关领域,所以本章的内容就不过多涉及了,以后会进行学习,应该. Word2Vec又称Word Embeddings,中文称为"词向量"."词嵌入 ...
- LSTM如何解决梯度消失或爆炸的?
from:https://zhuanlan.zhihu.com/p/44163528 哪些问题? 梯度消失会导致我们的神经网络中前面层的网络权重无法得到更新,也就停止了学习. 梯度爆炸会使得学习不稳定 ...
- win10下vs2015编译的程序如何运行在win7等系统(无需安装Redistributable)
最近新写的程序要做beta测试,在做绿色版(免安装版)时遇到了问题,vs2015做的项目本以为像之前的vs版本一样把msvcrXXX.dll还有另外几个运行时库都放到exe旁边即可,然并卵...,在w ...
- 使用ssm(spring+springMVC+mybatis)创建一个简单的查询实例(三)(错误整理篇)
使用ssm(spring+springMVC+mybatis)创建一个简单的查询实例(一) 使用ssm(spring+springMVC+mybatis)创建一个简单的查询实例(二) 以上两篇已经把流 ...
- css隐藏滚动条依旧可以滚动
在做企业页面的时候,大部分页面都应该是活的,不应该写死,因为要从后台拿数据进行填充.而后台的数据是不确定的,有时候会让我们的容器撑得很大.这时候我们需要隐藏掉滚动条达到美观或者其他布局效果,隐藏掉单个 ...
- Java 1.7 NQuery
package org.rx.common; import java.lang.reflect.Array; import java.util.*; /** * Created by wangxiao ...
- 事件驱动模型和异步IO多路复用
事件驱动模型 协程:遇到IO操作就切换. 但什么时候切回去呢?怎么确定IO操作完了? 很多程序员可能会考虑使用“线程池”或“连接池”.“线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程, ...
- mysql 分组排序取最值
查各个用户下单最早的一条记录 查各个用户下单最早的前两条记录 查各个用户第二次下单的记录 一.建表填数据: SET NAMES utf8mb4; -- 取消外键约束 ; -- ------------ ...