Interface In Go Part2

有一些时候,一些值需要被转换为另一个不同的类型。转换操作是一个编译时受检的操作,并且整个机制在更早的另一篇文章里面有详细说明。简单来说,看起来就好像是这个样子的:

type T1 struct {
    name string
}

type T2 struct {
    name string
}

func main() {
    vs := []interface{}{T2(T1{"foo"}), string(322), []byte("abł")}
    for _, v := range vs {
        fmt.Printf("%v %T\n", v, v)
    }
}

输出

{foo} main.T2
ł string
[97 98 197 130] []uint8

Golang的可赋值性规则,在一些情况下允许分配不同类型的值给变量(代码如下):

type T struct {
    name string
}func main() {
    v1 := struct{ name string }{"foo"}
    fmt.Printf("%T\n", v1) // struct { name string }
    var v2 T
    v2 = v1
    fmt.Printf("%T\n", v2) // main.T
}

这篇文章主要的精力会放在当接口类型加入进来时golang的转换策略。另外,会介绍几个新结构–类型断言和类型切换。


首先假设我们有两个接口类型的变量,并且我们想将一个分配给另一个(代码如下):

type I1 interface {
    M1()
}

type I2 interface {
    M1()
}

type T struct{}func (T) M1() {}func main() {
    var v1 I1 = T{}
    var v2 I2 = v1
    _ = v2
}

这种方式是简单的,程序也可以正常的工作。第三种赋值情形适用于这里:

T is an interface type and x implements T.

这是因为变量v1的类型实现了I2的接口。不管这些类型的结构如何。(源码如下)

type I1 interface {
    M1()
    M2()
}

type I2 interface {
    M1()
    I3
}

type I3 interface {
    M2()
}

type T struct{}

func (T) M1() {}
func (T) M2() {}

func main() {
    var v1 I1 = T{}
    var v2 I2 = v1
    _ = v2
}
I2I1
type I1 interface {
    M1()
    M2()
}

type I2 interface {
    M1()
}

type T struct{}

func (T) M1() {}
func (T) M2() {}

func main() {
    var v1 I1 = T{}
    var v2 I2 = v1
    _ = v2
}
I2I1I2I1
type I1 interface {
    M1()
}

type I2 interface {
    M1()
    M2()
}

type T struct{}

func (T) M1() {}

func main() {
    var v1 I1 = T{}
    var v2 I2 = v1
    _ = v2
}

上面这段代码无法正确编译,因为会抛出下面这个错误:

main.go:18: cannot use v1 (type I1) as type I2 in assignment:
	I1 does not implement I2 (missing M2 method)

我们已经看到了涉及到两种接口的情形。前面列出的第三种可赋值情形同样适用于右侧为具体类型值(non-interface type)实现了一个接口(源码如下):

type I1 interface {
    M1()
}

type T struct{}

func (T) M1() {}

func main() {
    var v1 I1 = T{}
    _ = v1
}

当需要将一个接口类型的变量赋值给一个具体类型的变量,到底是怎么工作的呢?(源码如下):

type I1 interface {
    M1()
}

type T struct{}

func (T) M1() {}

func main() {
    var v1 I1 = T{}
    var v2 T = v1
    _ = v2
}
cannot use v1 (type I1) as type T in assignment: need type assertion

只有当go编译器可以检查其正确性的时候,转换才可以被完成。无法在编译时验证的场景如下:

  1. 接口类型 → 具体类型 (源码如下):
type I interface {
    M()
}

type T struct {}
func (T) M() {}

func main() {
    var v I = T{}
    fmt.Println(T(v))
}
cannot convert v(type I) to type T: need type assertionIv
  1. 接口类型 → 接口类型,其中右边的方法集不是左边类型的方法集的子集(源码如下):
type I1 interface {
    M()
}

type I2 interface {
    M()
    N()
}

func main() {
    var v I1
    fmt.Println(I2(v))
}

编译的输出:

main.go:16: cannot convert v (type I1) to type I2:
	I1 does not implement I2 (missing N method)
I2I1

这不是严格意义上的类型转换,而是类型断言和类型切换,允许检查/检索接口类型值的动态值或者将接口类型的值转换为不同接口类型的值。

类型断言

类型断言的语法格式如下:

v.(T)

其中v为接口类型,T为抽象类型或具体值类型

具体类型

首先来让我们看一下,非接口类型(non-interface)是如何工作的(代码如下):

type I interface {
    M()
}
type T struct{}

func (T) M() {}func main() {
    var v1 I = T{}
    v2 := v1.(T)
    fmt.Printf("%T\n", v2) // main.T
}
v1I
type I interface {
    M()
}

type T1 struct{}

func (T1) M() {}

type T2 struct{}func main() {
    var v1 I = T1{}
    v2 := v1.(T2)
    fmt.Printf("%T\n", v2)
}
impossible type assertionv1T2T2Iv1I
v1v1T
type I interface {
    M()
}
type T1 struct{}

func (T1) M() {}

type T2 struct{}

func (T2) M() {}

func main() {
    var v1 I = T1{}
    v2 := v1.(T2)
    fmt.Printf("%T\n", v2)
}

程序将会不知所措(panic):

panic: interface conversion: main.I is main.T1, not main.T2

多值转化(请不要惊慌)

T
type I interface {
    M()
}

type T1 struct{}

func (T1) M() {}

type T2 struct{}

func (T2) M() {}

func main() {
    var v1 I = T1{}
    v2, ok := v1.(T2)
    if !ok {
        fmt.Printf("ok: %v\n", ok) // ok: false
        fmt.Printf("%v,  %T\n", v2, v2) // {},  main.T2
    }
}

这种形式不会引起恐慌,因为返回的第二个布尔类型的值可以用来检查断言是否成立。

接口类型

v
type I1 interface {
    M()
}

type I2 interface {
    I1
    N()
}

type T struct{
    name string
}

func (T) M() {}

func (T) N() {}

func main() {
    var v1 I1 = T{"foo"}
    var v2 I2
    v2, ok := v1.(I2)
    fmt.Printf("%T %v %v\n", v2, v2, ok) // main.T {foo} true
}
零值(zero-value)
type I1 interface {
    M()
}

type I2 interface {
    N()
}

type T struct {}

func (T) M() {}func main() {
    var v1 I1 = T{}
    var v2 I2
    v2, ok := v1.(I2)
    fmt.Printf("%T %v %v\n", v2, v2, ok) // <nil> <nil> false
}

当处理接口类型时,还支持类型断言的单值转换。

nil

vT
type I interface {
    M()
}

type T struct{}

func (T) M() {}

func main() {
    var v1 I
    v2 := v1.(T)
    fmt.Printf("%T\n", v2)
}

当开始运行这段程序时,程序将会恐慌。

panic: interface conversion: main.I is nil, not main.T
v

类型区别(Type switch)

类型断言是一种方法,用于检查接口类型值的动态类型是否实现了所需的接口,或者是否与传递的具体类型的值相同。如果代码需要对一个变量做多次这样的测试,那么golang有一种结构比多个类型断言更加紧凑,和传统的switch语句类似:

type I1 interface {
    M1()
}

type T1 struct{}

func (T1) M1() {}

type I2 interface {
    I1
    M2()
}

type T2 struct{}func (T2) M1() {}


func (T2) M2() {}

func main() {
    var v I1
    switch v.(type) {
    case T1:
            fmt.Println("T1")
    case T2:
            fmt.Println("T2")
    case nil:
            fmt.Println("nil")
    default:
            fmt.Println("default")
    }
}
typev
var v I1 = T2{}
T2
var v I1 = T2{}
switch v.(type) {
case I2:
        fmt.Println("I2")
case T1:
        fmt.Println("T1")
case T2:
        fmt.Println("T2")
case nil:
        fmt.Println("nil")
default:
        fmt.Println("default")
}
I2
type I interface {
    M()
}

func main() {
    var v I
    switch v.(type) {
    }
}

这段程序不会恐慌,他会成功的执行完。

每个case多种类型

switch语句的每个case可以指定不止一个类型,多个类型用逗号分隔。可以避免匹配到不同类型,执行相同代码块的重复代码。(代码如下):

type I1 interface {
    M1()
}

type T1 struct{}

func (T1) M1() {}

type T2 struct{}

func (T2) M1() {}func main() {
    var v I1 = T2{}
    switch v.(type) {
    case nil:
            fmt.Println("nil")
    case T1, T2:
            fmt.Println("T1 or T2")
    }
}
T1 or T2vT2

default case

这种情况类似于之前switch的声明,它会在没有一个case被匹配到的时候执行。(代码如下):

var v I
switch v.(type) {
default:
        fmt.Println("fallback")
}

短变量声明(short variable declaration)

v.(type)v
var p *T2
var v I1 = p
switch t := v.(type) {
case nil:
         fmt.Println("nil")
case *T1:
         fmt.Printf("%T is nil: %v\n", t, t == nil)
case *T2:
         fmt.Printf("%T is nil: %v\n", t, t == nil)
}
*main.T2 is nil: truettv
var p *T2
var v I1 = p
switch t := v.(type) {
case nil:
         fmt.Println("nil")
case *T1, *T2:
         fmt.Printf("%T is nil: %v\n", t, t == nil)
}
*main.T2 is nil: falset

重复(duplicates)

在case子句中指定的类型一定要是唯一的(代码如下):

switch v.(type) {
case nil:
    fmt.Println("nil")
case T1, T2:
    fmt.Println("T1 or T2")
case T1:
    fmt.Println("T1")
}
duplicate case T1 in type switch

可选简单的语句(optional simple statement)

guard可以用一个简单的语句,类似短变量声明。(代码如下):

var v I1 = T1{}
switch aux := 1; v.(type) {
case nil:
    fmt.Println("nil")
case T1:
    fmt.Println("T1", aux)
case T2:
    fmt.Println("T2", aux)
}
T1 1

您的点赞与分享是对我们最大的支持