接口转回成具体类型

接口实例中可以存放各种实现了接口的类型实例,在有需要的时候,还可以通过ins.(Type)ins.(*Type)的方式将接口实例ins直接转回Type类型的实例。

var i int = 30
var ins interface{} // 接口实例ins中保存的是int类型
ins = i
x := ins.(int) // 接口转回int类型的实例i
println(x) //输出30

但注意,这时候的i和x在底层不是同一个对象,它们的地址是不同的。

var i int = 30
var ins interface{} ins = i
x := ins.(int)
println("i addr: ",&i,"x addr: ",&x)

输出:

0xc042049f68
0xc042049f60

注意,接口实例转回时,接口实例中存放的是什么类型,才能转换成什么类型。同类型的值类型实例和指针类型实例不能互转,不同类型更不能互转。

在不能转换时,Golang将直接以Panic的方式终止程序。但可以处理转换失败时的panic,这时需要类型断言,也即类型检测。

接口类型探测:类型断言

类型探测的方式和类型转换的方式都是ins.(Type)ins.(*Type)。当处于单个返回值上下文时,做的是类型转换,当处于两个返回值的上下文时,做的是类型探测。类型探测的第一个返回值是类型转换之后的类型实例,第二个返回值是布尔型的ok返回值。

// 如果ins保存的是值类型的Type,则输出
if t, ok := ins.(Type); ok {
fmt.Printf("%T\n", v)
} // 如果ins保存的是指针类型的*Type,则输出
if t, ok := ins.(*Type); ok {
fmt.Printf("%T\n", v)
} // 一个返回值的探测
t := ins.(Type)
t := ins.(*Type)

以下是一个例子:

package main

import "fmt"

// Shaper 接口类型
type Shaper interface {
Area() float64
} // Square struct类型
type Square struct {
length float64
} // Square类型实现Shaper中的方法Area()
func (s Square) Area() float64 {
return s.length * s.length
} func main() {
var ins1, ins2 Shaper // 指针类型的实例
s1 := new(Square)
s1.length = 3.0
ins1 = s1
if v, ok := ins1.(*Square); ok {
fmt.Printf("ins1: %T\n", v)
} // 值类型的实例
s2 := Square{4.0}
ins2 = s2
if v, ok := ins2.(Square); ok {
fmt.Printf("ins2: %T\n", v)
}
}

上面两个Printf都会输出,因为它们的类型判断都返回true。如果将ins2.(Square)改为ins2.(*Square),第二个Printf将不会输出,因为ins2它保存的是值类型的实例。

以下是输出结果:

ins1: *main.Square
ins2: main.Square

特别需要注意的是,ins必须明确是接口实例。例如,以下前两种声明是有效的,第三种推断类型是错误的,因为它可能是接口实例,也可能是类型的实例副本。

var ins Shaper     // 正确
ins := Shaper(s1) // 正确
ins := s1 // 错误

当ins不能确定是接口实例时,用它来进行测试,例如ins.(Square)将会报错:

invalid type assertion:ins.(Square) (non-interface type (type of ins) on left)

它说明了左边的ins是非接口类型(non-interface type)。

另一方面,通过接口类型断言(ins.(Type)),如果Type是一个接口类型,就可以判断接口实例ins中所保存的类型是否也实现了Type接口。例如:

var r io.Read
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty var w io.Writer
w = r.(io.Writer)

上面的r是io.Read接口的一个实例变量,它里面保存的是tty和它的类型,即(tty, *os.File),然后断言r的类型,探测它里面的类型*File是否也实现了io.Writer接口,如果实现了,则保存到io.Writer接口的实例变量w中,这样w实例也将保存(tty,*os.File)

由于任意内容都实现了空接口,所以,总是可以把一个接口实例无需通过任何断言地赋值给一个空接口实例:

var empty interface{}
empty = w

现在empty也保存了(tty,*os.File)

type Switch结构

直接用if v,ok := ins.(Type);ok {}的方式做类型探测在探测类型数量多时不是很方便,需要重复写if结构。

Golang提供了switch...case结构用于做多种类型的探测,所以这种结构也称为type-switch。这是比较方便的语法,比如可以判断某接口如果是A类型,就执行A类型里的特有方法,如果是B类型,就执行B类型里的特有方法。

用法如下:

switch v := ins.(type) {
case *Square:
fmt.Printf("Type Square %T\n", v)
case *Circle:
fmt.Printf("Type Circle %T\n", v)
case nil:
fmt.Println("nil value: nothing to check?")
default:
fmt.Printf("Unexpected type %T", v)
}

其中ins.(type)中的小写type是固定的词语。

以下是一个使用示例:

package main

import (
"fmt"
) // Shaper 接口类型
type Shaper interface {
Area() float64
} // Circle struct类型
type Circle struct {
radius float64
} // Circle类型实现Shaper中的方法Area()
func (c *Circle) Area() float64 {
return 3.14 * c.radius * c.radius
} // Square struct类型
type Square struct {
length float64
} // Square类型实现Shaper中的方法Area()
func (s Square) Area() float64 {
return s.length * s.length
} func main() {
s1 := &Square{3.3}
whichType(s1) s2 := Square{3.4}
whichType(s2) c1 := new(Circle)
c1.radius = 2.3
whichType(c1)
} func whichType(n Shaper) {
switch v := n.(type) {
case *Square:
fmt.Printf("Type Square %T\n", v)
case Square:
fmt.Printf("Type Square %T\n", v)
case *Circle:
fmt.Printf("Type Circle %T\n", v)
case nil:
fmt.Println("nil value: nothing to check?")
default:
fmt.Printf("Unexpected type %T", v)
}
}

上面的type-switch中,之所以没有加上case Circle,是因为Circle只实现了指针类型的receiver,根据Method Set对接口的实现规则,只有指针类型的Circle示例才算是实现了接口Shaper,所以将值类型的示例case Circle放进type-switch是错误的。

Go基础系列:接口类型断言和type-switch的更多相关文章

  1. Java基础系列 - 接口(功能,用途和优势)

    package com.test1; /** * 接口的使用 */ public class test1 { public static void main(String[] args) { //创建 ...

  2. Python3基础系列——枚举类型大揭秘

    为什么使用枚举 枚举类型是定义常量的一种最优选择. 常量的广义概念是:不变化的量 对于常量的通俗比喻--如同大山不被轻而易举地改变 地球上的重力加速度到海枯石烂也会改变 人们使用的常量是时间不很漫长的 ...

  3. 带你学够浪:Go语言基础系列 - 10分钟学方法和接口

    文章每周持续更新,原创不易,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) 对于一般的语言使用者来说 ,20% 的语言特性就能够满 ...

  4. C#基础系列——一场风花雪月的邂逅:接口和抽象类

    前言:最近一个认识的朋友准备转行做编程,看他自己边看视频边学习,挺有干劲的.那天他问我接口和抽象类这两个东西,他说,既然它们如此相像, 我用抽象类就能解决的问题,又整个接口出来干嘛,这不是误导初学者吗 ...

  5. 夯实Java基础系列6:一文搞懂抽象类和接口,从基础到面试题,揭秘其本质区别!

    目录 抽象类介绍 为什么要用抽象类 一个抽象类小故事 一个抽象类小游戏 接口介绍 接口与类相似点: 接口与类的区别: 接口特性 抽象类和接口的区别 接口的使用: 接口最佳实践:设计模式中的工厂模式 接 ...

  6. 带你学够浪:Go语言基础系列 - 8分钟学复合类型

    ★ 文章每周持续更新,原创不易,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) " 对于一般的语言使用者来说 ,20% ...

  7. JVM基础系列第10讲:垃圾回收的几种类型

    我们经常会听到许多垃圾回收的术语,例如:Minor GC.Major GC.Young GC.Old GC.Full GC.Stop-The-World 等.但这些 GC 术语到底指的是什么,它们之间 ...

  8. Go基础系列:Go接口

    接口用法简介 接口(interface)是一种类型,用来定义行为(方法). type Namer interface { my_method1() my_method2(para) my_method ...

  9. 7.翻译:EF基础系列---EF中的实体类型

    原文地址:http://www.entityframeworktutorial.net/Types-of-Entities.aspx 在Entity Framework中有两种实体类型:一种是POCO ...

随机推荐

  1. 哈夫曼(Huffman)树和哈夫曼编码

    一.哈夫曼(Huffman)树和哈夫曼编码 1.哈夫曼树(Huffman)又称最优二叉树,是一类带权路径长度最短的树, 常用于信息检测. 定义: 结点间的路径长度:树中一个结点到另一个结点之间分支数目 ...

  2. rails应用的部署

    简单部署 RAILS_ENV=production rake secret /etc/profile export SECRET_KEY_BASE=刚才生成的密钥 source /etc/profil ...

  3. 【Solidity】学习(1)

    string string类型不可以直接通过length读取字符串长度,也不可以直接通过下标直接访问数据元素 使用的方法是:强制类型转换为bytes 其中," " 和‘ ’都可以表 ...

  4. c#项目减少源代码大小

    这次的代码缩减主要通过了这几个方面 1.bin和obj文件的删除(以前真的不知道,只是通过右键属性发现这些文件太大,然后上网搜索才知道,这些文件在源代码备份的时候是建议删掉的) 删掉的好处: 1.减少 ...

  5. SSH连接Linux操作:

    Centos6.5的操作: 1:需要下载一个Xshell连接工具: 2:在Linux输入ifconfig,查看IP地址, 3:使用Xshell连接 Ubuntu的操作: 1:需要下载一个Xshell连 ...

  6. 《JavaScript》高级程序设计第21章:Ajax和Comet,jsonp

    一.创建XMLHttpRequest对象 二.XHR的用法 五.跨域资源共享 六.其他跨域技术七.安全七.安全 1. 图像Ping 2. JSONP(JSON with padding,填充式JSON ...

  7. 原来你离BAT只有一步之遥

    ladies and乡亲们 喜迎全民嗨购双11 i春秋准备搞一波大优惠 优惠力度有多大 跨店凑单满400-50? 指定商品199减100? 史无钜惠 不凑单 不指定 一次直降9000元 原价:2580 ...

  8. Javascript高级编程学习笔记(45)——DOM 操作表格及DOM动态集合

    操作DOM表格 早些时候,HTML 还是以表格布局为主, 所以DOM操作表格是比较重要的一点 但是现如今 有其它的选择,所以表格的操作也就慢慢地淡出了人们的视线 所以这里也就不过多去详细展开,这里也就 ...

  9. Javascript高级编程学习笔记(21)—— 对象原型

    JS中对象相关的最重要的恐怕就是原型链了 原型链也是JS中对象继承的实现的基础 接昨天的文章,我们使用构造函数创建对象的时候仍然存在一些问题 那就是所有的实例没法共用一个函数 这样无疑会造成极大的内存 ...

  10. Android自定义控件总结

    自定义控件分类: 1.使用系统控件,实现自定义的效果 2.自己定义一个类继承View ,如textView.ImageView等,通过重写相关的方法来实现新的效果 3.自己定义一个类继承ViewGro ...