一,interface 介绍

如果说 goroutine 和 channel 是 go 语言并发的两大基石,那 interface 就是 go 语言类型抽象的关键。在实际项目中,几乎所有的数据结构最底层都是接口类型。说起 C++ 语言,我们立即能想到是三个名词:封装、继承、多态。go 语言虽然没有严格意义上的对象,但通过 interface,可以说是实现了多态性。(由以组合结构体实现了封装、继承的特性)

go 语言中支持将 method、struct、struct 中成员定义为 interface 类型,使用 struct 举一个简单的栗子

 package main

 type animal interface {
Move()
} type bird struct{} func (self *bird) Move() {
println("bird move")
} type beast struct{} func (self *beast) Move() {
println("beast move")
} func animalMove(v animal) {
v.Move()
} func main() {
var a *bird
var b *beast
animalMove(a) // bird move
animalMove(b) // beast move
}

使用 go 语言的 interface 特性,就能实现多态性,进行泛型编程。

二,interface 原理

如果没有充分了解 interface 的本质,就直接使用,那最终肯定会踩到很深的坑,要用就先要了解,先来看看 interface 源码

 type eface struct {
_type *_type
data unsafe.Pointer
} type _type struct {
size uintptr // type size
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldalign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
alg *typeAlg // algorithm table
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type, may be zero
}

可以看到 interface 变量之所以可以接收任何类型变量,是因为其本质是一个对象,并记录其类型和数据块的指针。(其实 interface 的源码还包含函数结构和内存分布,由于不是本文重点,有兴趣的同学可以自行了解)

三,interface 判空的坑

对于一个空对象,我们往往通过 if v == nil 的条件语句判断其是否为空,但在代码中充斥着 interface 类型的情况下,很多时候判空都并不是我们想要的结果(其实了解或聪明的同学从上述 interface 的本质是对象已经知道我想要说的是什么)

 package main

 type animal interface {
Move()
} type bird struct{} func (self *bird) Move() {
println("bird move")
} type beast struct{} func (self *beast) Move() {
println("beast move")
} func animalMove(v animal) {
if v == nil {
println("nil animal")
}
v.Move()
} func main() {
var a *bird // nil
var b *beast // nil
animalMove(a) // bird move
animalMove(b) // beast move
}

还是刚才的栗子,其实在 go 语言中 var a *bird 这种写法,a 只是声明了其类型,但并没有申请一块空间,所以这时候 a 本质还是指向空指针,但我们在 aminalMove 函数进行判空是失败的,并且下面的 v.Move() 的调用也是成功的,本质的原因就是因为 interface 是一个对象,在进行函数调用的时候,就会将 bird 类型的空指针进行隐式转换,转换成实例的 interface animal 对象,所以这时候 v 其实并不是空,而是其 data 变量指向了空。这时候看着执行都正常,那什么情况下坑才会绊倒我们呢?只需要加一段代码

 package main

 type animal interface {
Move()
} type bird struct {
name string
} func (self *bird) Move() {
println("bird move %s", self.name) // panic
} type beast struct {
name string
} func (self *beast) Move() {
println("beast move %s", self.name) // panic
} func animalMove(v animal) {
if v == nil {
println("nil animal")
}
v.Move()
} func main() {
var a *bird // nil
var b *beast // nil
animalMove(a) // panic
animalMove(b) // panic
}

在代码中,我们给派生类添加 name 变量,并在函数的实现中进行调用,就会发生 panic,这时候的 self 其实是 nil 指针。所以这里坑就出来了。有些人觉得这类错误谨慎一些还是可以避免的,那是因为我们是正向思维去代入接口,但如果反向编程就容易造成很难发现的 bug

 package main

 type animal interface {
Move()
} type bird struct {
name string
} func (self *bird) Move() {
println("bird move %s", self.name)
} type beast struct {
name string
} func (self *beast) Move() {
println("beast move %s", self.name)
} func animalMove(v animal) {
if v == nil {
println("nil animal")
}
v.Move()
} func getBirdAnimal(name string) *bird {
if name != "" {
return &bird{name: name}
}
return nil
} func main() {
var a animal
var b animal
a = getBirdAnimal("big bird")
b = getBirdAnimal("") // return interface{data:nil}
animalMove(a) // bird move big bird
animalMove(b) // panic
}

这里我们看到通过函数返回实例类型指针,当返回 nil 时,因为接收的变量为接口类型,所以进行了隐性转换再次导致了 panic(这类反向转换很难发现)。

那我们如何处理上述这类问题呢。我这边整理了三个点

1,充分了解 interface 原理,使用过程中需要谨慎小心

2,谨慎使用泛型编程,接收变量使用接口类型,也需要保证接口返回为接口类型,而不应该是实例类型

3,判空是使用反射 typeOf 和 valueOf 转换成实例对象后再进行判空

go 语言 interface{} 的易错点的更多相关文章

  1. C语言指针的易错点

    1.内存泄漏:申请的堆内存没有释放. 2.内存污染:前面非法操作使用内存(没有报错),后面写着写着就出错.如下代码: 当结构体中只有划线部分代码时,在编译器中编写不会报错,但此时已经造成非法操作内存, ...

  2. R语言 几个易错的地方

    1.列表与向量 定义一个向量,然后向内添加元素,得到一个长向量列表: > a = c() #定义一向量 > for (i in 1:5) + a = c(a,i) > a [1] 1 ...

  3. c语言的一些易错知识积累

    1. #ifdef  和#if defined 的区别: 后者可以组成复杂的预编译条件,而如果判断的是单个宏定义的时候,两种用法的效果都是一样的. 2.#if 0 { code }#endif    ...

  4. *C语言有关指针的变量声明中的几个易错点

    转至:http://my.oschina.net/ypimgt/blog/108265   Technorati 标签:  指针, typedef, const, define 我们都知道,至少听说过 ...

  5. C语言易错点

    C语言易错点 1.每个C语言程序中main函数是有且只有一个的. 2.算法可以没有输入,但必须要有输出. 3.在函数中不可以再定义函数. 4.break可用于循环结构和switch语句. 5.brea ...

  6. 细节!重点!易错点!--面试java基础篇(二)

    今天来给大家分享一下java的重点易错点第二部分,也是各位同学面试需要准备的,欢迎大家交流指正. 1.字符串创建与存储机制:当创建一个字符串时,首先会在常量池中查找是否已经有相同的字符串被定义,其判断 ...

  7. 细节!重点!易错点!--面试java基础篇(一)

    今天来给大家分享一下java的重点易错点部分,也是各位同学面试需要准备的,欢迎大家交流指正. 1.java中的main方法是静态方法,即方法中的代码是存储在静态存储区的. 2.任何静态代码块都会在ma ...

  8. 关于Verilog HDL的一些技巧、易错、易忘点(不定期更新)

    本文记录一些关于Verilog HDL的一些技巧.易错.易忘点等(主要是语法上),一方面是方便自己忘记语法时进行查阅翻看,另一方面是分享给大家,如果有错的话,希望大家能够评论指出. 关键词: ·技巧篇 ...

  9. JavaScript易错知识点整理

    前言 本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一 ...

随机推荐

  1. golang 中Pointers Vs References

    原文: https://spf13.com/post/go-pointers-vs-references/ Pointers Vs References Some languages includin ...

  2. R的数据结构--数组

    数组:可以认为数组是矩阵的扩展,它将矩阵扩展到2维以上.如果给定的数组是1维的则相当于向量,2维的相当于矩阵. R语言中的数组元素的类型也是单一的,可以是数值型,逻辑型,字符型或复数型 参数解释 ar ...

  3. 题解 [51nod1771] 最小生成树中的边

    题面 解析 这题好像没人写过啊(所以好像没题解)... 然后刚了一天才写出来摆了半天. 其实一开始是想错了, 写了个\(O(n^2)\)的近似于暴力的方法. 就是对于每组权值相等的边, 对于每条边先把 ...

  4. NodeList对象的特点

    nodeList对象的特点1,nodeList是一种类数组对象,用于保存一组有序的节点.2,通过方括号来访问nodeList的值,有item方法与length属性.3,它并不是Array的实例,没有数 ...

  5. Navicat创建数据库或导入数据库

    双击点亮数据库 导入数据库 点击开始

  6. CSP-S 模拟53 题解

    题解: T1 u: 一看到修改这么多,但询问其实只有一个不难想到差分,但是他这个形状可以说很不规则,于是我们想到分别维护竖着的和斜着的差分,然后最后合并即可. 考场上瞎调了一波系数莫名AC,其实是维护 ...

  7. luogu4281

    P4281 [AHOI2008]紧急集合 / 聚会 题目描述 欢乐岛上有个非常好玩的游戏,叫做“紧急集合”.在岛上分散有N个等待点,有N-1条道路连接着它们,每一条道路都连接某两个等待点,且通过这些道 ...

  8. elastic search&logstash&kibana 学习历程(三)Logstash使用场景和安装部署

    Logstash基本介绍和使用场景 自我认为:logstash就是日志的采集收集,日志的搬运工,实时去采集日志.读取不同的数据源,并进行过滤,开发者自定义规范输出到目的地.日志的来源很多,如系统日志, ...

  9. Python数据抓取(3) —抓取标题、时间及链接

    本次分享,jacky将跟大家分享如何将第一财经文章中的标题.时间以及链接抓取出来 (一)观察元素抓取位置 网页的原始码很复杂,我们必须找到特殊的元素做抽取,怎么找到特殊的元素呢?使用开发者工具检视每篇 ...

  10. 单链表之Java实现

    初次接触java,用java也写了一个链表.代码如下: import java.io.*; class Node{ public int data; //数据域 public Node next; / ...