接口转回成具体类型

接口实例中可以存放各种实现了接口的类型实例,在有需要的时候,还可以通过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. MySQL数据库(四)多表查询

    两张假设有两张表格A和B,把表格当作一个集合,那么表格中的记录就是集合中的一个元素. 两张表格如下: TableA:TableB: 2.1 内连接(只有一种场景) inner join 或者join( ...

  2. 【慕课网实战】八、以慕课网日志分析为例 进入大数据 Spark SQL 的世界

    用户行为日志:用户每次访问网站时所有的行为数据(访问.浏览.搜索.点击...)     用户行为轨迹.流量日志   日志数据内容: 1)访问的系统属性: 操作系统.浏览器等等 2)访问特征:点击的ur ...

  3. How Does Closure Work in Javascript?

    Simply, closure is the scope that it can visite and operate the variables outside of the function wh ...

  4. Equal 路由类

    1.Route 原型 class Route { /* 获取请求路径和查询字符串 */ /* 获取模块.控制器.动作名称 */ /* 获取 URI 参数 */ }

  5. Splinter常用API介绍(转)

    # Example from splinter import Browser with Browser() as browser: # Visit URL url = "http://www ...

  6. 内存栅栏(memory barrier):解救peterson算法的应用陷阱

    最近一个项目中用到了peterson算法来做临界区的保护,简简单单的十几行代码,就能实现两个线程对临界区的无锁访问,确实很精炼.但是在这不是来分析peterson算法的,在实际应用中发现peterso ...

  7. 安装virtualbox出现2503、2502的错误提示解决方法

    安装virtualbox右键选择以管理员的身份打开即可

  8. some knowledge of language

    1:编译型语言2:解释型语言编译型:编译形成结果,再整体运行解释型:运行产生结果,边解释运行java 特殊(.class)再解释3:脚本语言是解释语言它的优点是方便阅读,不需要写非常多的类型相关的代码 ...

  9. 剑指offer面试题16:反转链表

    题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后的链表的头结点.解题思路:单向链表只能实现单向遍历,改变链表方向就是要把当前链表的节点指向它的前一个节点,一旦当前链表指向发生了变化,就 ...

  10. 漏洞利用教程:msfpayload 和 Backdooring EXEs

    漏洞利用教程:msfpayload 和 Backdooring EXEs 此版本的PrimalSec漏洞教程系列将介绍如何使用msfpayload创建各种有效负载.msfpayload是Metaspl ...