Go——接口运算(类型断言、接口查询)

1、类型断言(Type Assertion)

接口类型断言的语法形式如下:

i.(TypeName)

i必须是接口变量,如果是具体类型变量,则编译器会报non-interface type xxx on left,TypeName可以是接口类型名,也可以是具体类型名。

接口查询的两层语义
  1. 如果TypeNname是一个具体类型名,则类型断言用于判断接口变量i绑定的实例类型是否就是具体类TypeName。
  2. 如果TypeName是一个接口类型名,则类型断言用于判断接口变量i绑定的实例类型是否同时实现了TypeName接口。
接口断言的两种语法表现

直接赋值模式如下:

o := i.(TypeName)

语义分析:

  1. TypeName是具体类型名,此时如果接口i绑定的实例类型就是具体类型TypeName,则变量o的类型就是TypeName,变量o的值就是接口绑定的实例值的副本(当然实例可能是
    指针值,那就是指针值的副本)。
  2. TypeName是接口类型名,如果接口i绑定的实例类型满足接口类型TypeName,则变量o的类型就是接口类型TypeName,o底层绑定的具体类型实例是i绑定的实例的副本(当然实例可能是指针值,那就是指针值的副本)。
  3. 如果上述两种情况都不满足,则程序抛出panic。

示例如下:

package main

import "fmt"

func main() {
	st := &St{"andes"}
	var i interface{} = st

	//判断i绑定的实例是否实现了接口类型Inter
	o := i.(Inter)
	o.Ping()
	o.Pang()

	//如下语句会引发panic,因为i没有实现接口Anter
	//p := i.(Anter)
	//p.String()

	//判断i绑定的实例是否就是具体类型St
	s := i.(*St)
	fmt.Printf("%s", s.Name)
}

type Inter interface {
	Ping()
	Pang()
}

type Anter interface {
	Inter
	String()
}

type St struct {
	Name string
}

func (St) Ping() {
	println("ping")
}

func (*St) Pang() {
	println("pang")
}

comma,ok表达式模式如下:

if o, ok := i.(TypeName); ok {

}

语义分析:

  1. TypeName是具体类型名,此时如果接口i绑定的实例类型就是具体类型TypeName,则ok为true,变量o的类型就是TypeName,变量o的值就是接口绑定的实例值的副本(当然
    实例可能是指针值,那就是指针值的副本)。
  2. TypeName是接口类型名,此时如果接口i绑定的实例的类型满足接口类型TypeName,则ok为true,变量o的类型就是接口类型TypeName,o底层绑定的具体类型实例是i绑定的实例的副本(当然实例可能是指针值,那就是指针值的副本)。
  3. 如果上述两个都不满足,则ok为false,变量o是TypeName类型的“零值”,此种条件分支下程序逻辑不应该再去引用o,因为此时的·没有意义。
    示例如下:
package main

import "fmt"

func main() {
	st := &St{"andes"}
	var i interface{} = st

	//判断i绑定的示例是否实现了接口类型Inter
	if o, ok := i.(Inter); ok {
		o.Ping() //ping
		o.Pang() //pang
	}

	if p, ok := i.(Anter); ok {
		//i没有实现接口Anter,所以程序不会执行到这里
		p.String()
	}

	//判断i绑定的实例是否就是具体类型St
	if s, ok := i.(*St); ok {
		fmt.Printf("%s", s.Name) //andes
	}
}

type Inter interface {
	Ping()
	Pang()
}

type Anter interface {
	Inter
	String()
}

type St struct {
	Name string
}

func (St) Ping() {
	println("ping")
}

func (*St) Pang() {
	println("pang")
}

2、类型查询(Type Switches)

接口类型查询的语法格式如下:

switch v := i.(type) {
case type1:
	xxx
case type2:
	xxx
default:
	xxx
}

语义分析:
接口查询有两层语义,一是查询一个接口变量底层绑定的底层变量的具体类型是什么,二是查询接口变量绑定的底层变量是否还实现了其他接口。

  1. i必须是接口类型。
    具体类型实例的类型是静态的,在类型声明后就不再变化,所以具体类型的变量不存在类型查询,类型查询一定是对一个接口变量进行操作。也就是说,上文中的ⅰ必须是接口变量,如果i是未初始化接口变量,则v的值是nil。例如:
var i io.Reader
	switch v := i.(type) { //此处i是为未初始化的接口变量,所以v为nil
	case nil:
		fmt.Printf("%T\n", v) //nil
	default:
		fmt.Printf("default")
	}
  1. case字句后面可以跟非接口类型名,也可以跟接口类型名,匹配是按照case子句的顺序进行的。
  • 如果case后面是一个接口类型名,且接口变量i绑定的实例类型实现了该接口类型的方法,则匹配成功,ⅴ的类型是接口类型,ⅴ底层绑定的实例是i绑定具体类型实例的副本。例如:
f, err := os.OpenFile("notes.txt", os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	var i io.Reader = f

	switch v := i.(type) {
	//i的绑定的实例是*osFile类型,实现了io.ReadWriter接口,所以case匹配成功
	case io.ReadWriter:
		//v是io.ReadWriter接口类型,所以可以调用Write方法
		v.Write([]byte("io.ReadWriter\n"))
	//由于上一个case已经匹配,就算这个case也匹配,也不会走到这里
	case *os.File:
		v.Write([]byte("*os.File\n"))
		v.Sync()
	default:
		return
  • 如果case后面是一个具体类型名,且接口变量i绑定的示例类型和该具体类型相同,则匹配成功,此时v就是该具体类型变量,v的值是i绑定的实例值的副本。例如:
	f, err := os.OpenFile("notes.txt", os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	var i io.Reader = f

	switch v := i.(type) {
	//匹配成功,v的类型就是具体类型*os.File
	case *os.File:
		v.Write([]byte("*os.File\n"))
		v.Sync()
	//由于上一个case已经匹配,就算这个case也匹配,也不会走到这里
	case io.ReadWriter:
		v.Write([]byte("io.ReadWriter\n"))

	default:
		return
	}
  • 如果case后面跟着多个类型,使用逗号分隔,接口变量i绑定的实例类型只要和其中一个类型匹配,则直接使用o赋值给V,相当于ⅴ:=o。这个语法有点奇怪,按理说编译
    器不应该允许这种操作,语言实现者可能想让type switch语句和普通的switch语句保持一样的语法规则,允许发生这种情况。例如:
f, err := os.OpenFile("notes.txt", os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	var i io.Reader = f

	switch v := i.(type) {
	//多个类型,f满足其中任何一个就算匹配
	case *os.File, io.ReadWriter:
		//此时相当于执行v := i,v和i是等价的,使用v没有意义
		if v == i {
			fmt.Println(true) //true
		}
	default:
		return
	}
  • 如果所有的case子句都不满足,则执行default语句,此时执行的仍然是 v:=o,最终v的值是o。此时使用v没有任何意义。
  • fallthrough语句不能在Type Switch语句中使用。

注意:Go和很多标准库使用如下的格式:

switch i := i.(type) {

}

这种使用方式存在争议:首先在switch语句块内新声明局部变量i覆盖原有的同名变量i不是一种好的编程方式,其次如果类型匹配成功,则ⅰ的类型就发生了变化,如果没有匹配成功,则ⅰ还是原来的接口类型。除非使用者对这种模糊语义了如指掌,不然很容易出错,所以不建议使用这种方式。
推荐的方式是将i.(ype)赋值给一个新变量:

switch v := i.(type) {

}
类型查询和类型断言
  1. 类型查询和类型断言具有相同的语义,只是语法格式不同。二者都能判断接口变量绑定的实例的具体类型,以及判断接口变量绑定的实例是否满足另一个接口类型。
  2. 类型查询使用cse字句一次判断多个类型,类型断言一次只能判断一个类型,当然类型断言也可以使用if else if语句达到同样的效果。

3、接口优点和使用形式

接口优点
  1. 解耦:复杂系统进行垂直和水平的分割是常用的设计手段,在层与层之间使用接口进行抽象和解耦是一种好的编程策略。Go的非侵入式的接口使层与层之间的代码更加干净,具体类型和实现的接口之间不需要显式声明,增加了接口使用的自由度。
  2. 实现泛型:由于现阶段Go语言还不支持泛型,使用空接口作为函数或方法参数能够用在需要泛型的场景中。
接口使用形式

接口类型是"第一公民",可以用在任何使用变量的地方,使用灵活,方便解耦,主要使用在如下地方:

  1. 作为结构内嵌字段。
  2. 作为函数或方法的形参。
  3. 作为函数或方法的返回值。
  4. 作为其他接口定义的嵌入字段。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值