接口转回成具体类型

接口实例中可以存放各种实现了接口的类型实例,在有需要的时候,还可以通过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. spring aop 切面编程中获取具体方法的方法

    spring 切面编程中获取具体方法的方法 工作中,使用环绕通知,用来捕获异常,然后通过获取方法的返回值,返回不同的数据给到调用方. 由于方法的返回值不同,我们处理异常时,也需要返回不同的格式. 这时 ...

  2. ABAP 常见系统表

    TRDIRT Program nameTFTIT Function module nameDD02T Table name

  3. 公用表表达式 (CTE)、递归、所有子节点、sqlserver

    指定临时命名的结果集,这些结果集称为公用表表达式 (CTE).公用表表达式可以包括对自身的引用.这种表达式称为递归公用表表达式. 对于递归公用表达式来说,实现原理也是相同的,同样需要在语句中定义两部分 ...

  4. android-基础编程-ViewPager

    ViewPager android 提供的基础V4包,android studio 导入gradle compile 'com.android.support:support-v4:25.0.0' 1 ...

  5. 微软develop apps在QQ上部分功能的实现

    最近我对微软的develop apps的文档进行了简读,在感叹UWP在支持服务上的全面的同时,我不禁在在常用的APP上对于这些功能支持进行了部分的寻找对应.而我进行功能对照的,就是平时很常用的一款手机 ...

  6. 三级菜单,可以退出到上一级菜单和全部退出(low版本)

    menu = { '北京':{ '海淀':{ '五道口':{ 'soho':{}, '网易':{}, 'google':{} }, '中关村':{ '爱奇艺':{}, '汽车之家':{}, 'youk ...

  7. 利用Python+163邮箱授权码发送带附件的邮件

    背景 前段时间写了个自动爬虫的脚本,定时在阿里云服务器上执行,会从某个网站上爬取链接保存到txt文本中,但是脚本不够完善,我需要爬虫完毕之后通过邮件把附件给我发送过来,之前写过一个<利用Pyth ...

  8. 手工检测SQL注入漏洞

    SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,具体来说,它是利用现有应用程序将(恶意的)SQL命令注入到后台数据库引擎执 ...

  9. DOM1级问题与DOM2级事件

    前几天有小伙伴问过我一个问题,为什么有DOM 0级事件以及DOM2级事件,但是却没有DOM1级事件呢?那我们今天就来说一说DOM的级别问题. 同时推荐伙伴们可以看看尚学堂有关JavaScript BO ...

  10. Android优化指南

    Android系统中GC内存泄漏的原因 主动回收内存System.gc();.getruntime.runtime.gc 导致内存泄漏主要的原因是,申请了内存空间而忘记了释放.如果程序中存在对无用对象 ...