GoLang类型断言使用汇总(type assertion)

7,147 阅读6分钟

我正在参与掘金创作者训练营第6期,点击了解活动详情

为什么要有类型断言(type assertion)

GoLang中的 interface{}any 可以代表所有类型,包括基本类型string、int、int64,以及自定义的 struct 类型。

interface{} 好比 java 中的 Objectjava 中的所有类都实现了Object

下面这个函数接收一个 interface{} 的参数,就代表了它可以传递任何类型的变量。如果在代码中强转 astring,如以下的代码:

func funcName(a interface{}) string {
    return string(a)
}

此时编译器将会返回:

cannot convert a (type interface{}) to type string: need type assertion

编译器提示我们用类型断言(type assertion)约束变量。接下来介绍几种使用类型断言的方法。

直接断言使用

直接断言使用,返回对应类型的值

  • 如果断言成功,则 变量b == 变量a
  • 如果断言失败,则 panic

语法如下:

变量b :=变量a.(类型)

举例

// testTypeAssertion
// @Description: 测试 断言
func testTypeAssertion1() {

    // define a variable, type is any
    var name any = "tom"
    
    nameType := reflect.TypeOf(name)

    // type assertion string
    fmt.Printf("name type = %v, name = %v\n", nameType, name.(string))
}

如果 断言为 string,则输出正常

name type = string, name = tom

如果断言为 非 string

// testTypeAssertion
// @Description: 测试 断言
func testTypeAssertion1() {

    // define a variable, type is any
    var name any = "tom"
    
    nameType := reflect.TypeOf(name)

    // type assertion string
    fmt.Printf("name type = %v, name = %v\n", nameType, name.(string))
    // type assertion int
    fmt.Printf("name type = %v, name = %v\n", nameType, name.(int))
}

panic, 提示 interface {} is string, not int, namestring 类型,不是int

image.png

输出


name = tom
panic: interface conversion: interface {} is string, not int

goroutine 1 [running]:
main.testTypeAssertion()
        D:/Project/Golang/src/go_code/project34-reflect/main.go:86 +0xd3
main.main()
        D:/Project/Golang/src/go_code/project34-reflect/main.go:71 +0x30a

断言判断并返回是否成功,可配合if

从一些业务角度来讲,如果断言不成功直接 panic,程序会终止,显然并不能满足一些业务的需求,比如记录错误日志,或走其他分支等等,所以 GoLang 也提供了另一个参数,返回断言是否成功

  • 如果断言成功,返回对应类型的值,即 变量b == 变量aoktrue
  • 如果断言失败,变量b为对应 断言类型默认值okfalse

语法如下:

变量b, ok = 变量a.(类型)

举例

// testTypeAssertion
// @Description:  测试 断言返回是否成功
func testTypeAssertion2() {

    // define a variable, type is any
    var name any = "tom"

    // type assertion string
    nameStringData, ok1 := name.(string)
    fmt.Printf("name type = %v, name = %v, assert success ? %v, ok type = %T\n", reflect.TypeOf(nameStringData), nameStringData, ok1, ok1)
    
    // type assertion int
    nameIntData, ok2 := name.(int)
    fmt.Printf("name type = %v, name = %v, assert success ? %v, ok type = %T\n", reflect.TypeOf(nameIntData), nameIntData, ok2, ok2)
}

输出结果,不出意外,没有 panic,可以看出,第二个参数 ok 返回了断言是否成功, bool类型

name type = string, name = tom, assert success ? true, ok type = bool
name type = int, name = 0, assert success ? false, ok type = bool

也可以看出转 int 未成功后的 nameIntDataint 类型,默认值是 0

拓展一下,断言成功或失败,结合 if 执行不同的操作

// to more thing
if ok1 {
    name = "Mike"
    fmt.Printf("ok1 true, change name to %v\n", name)
} else {
    name = "mike"
    fmt.Printf("ok1 false, change name to %v\n", name)
}

// to more thing
if ok2 {
    name = "sara"
    fmt.Printf("ok2 true, change name to %v\n", name)
} else {
    name = ""
    fmt.Printf("ok2 false, change name to %v\n", name)
}

输出结果

name type = string, name = tom, assert success ? true, ok type = bool
name type = int, name = 0, assert success ? false, ok type = bool
ok1 true, change name to Mike
ok2 false, change name to

还可以将断言语句与 if 结合起来使用,语法如下

if aData, ok := a.(T); ok {
    fmt.Println(aData, ok)
}

测试:

// testTypeAssertion
// @Description:  测试 断言返回是否成功
func testTypeAssertion3() {

    // define a variable, type is any
    var name any = "tom"
    
    // type assertion string and to more thing
    if nameStringData, ok1 := name.(string); ok1 {
        nameStringData = "Mike"
        fmt.Printf("ok1 true, change name to %v\n", nameStringData)
    } else {
        nameStringData = "mike"
        fmt.Printf("ok1 false, change name to %v\n", nameStringData)
    }
    
    // type assertion int and to more thing
    if nameIntData, ok2 := name.(int); ok2 {
        nameIntData = 10086
        fmt.Printf("ok2 true, change name to %v\n", nameIntData)
    } else {
        nameIntData = 10010
        fmt.Printf("ok2 false, change name to %v\n", nameIntData)
    }
}

输出结果

ok1 true, change name to Mike
ok2 false, change name to 10010

多说一句,出于代码可读性角度来讲,编码时可以使用卫语句 减少代码复杂度,少用 if else,提前退出逻辑。

switch 断言类型

如果一次性断言多种类型,会写很多的 if else,也可以用 switch 减少代码复杂度,用 变量.(type) 作为判断条件,格式如下:

switch variable := variable.(type){
default:
    fmt.Printf("unexpected type %T", variable)
case string:
    fmt.Printf("type is %T, variable = %v", variable, variable)
case int:
    fmt.Printf("type is %T, variable = %v", variable, variable)
case bool:
    fmt.Printf("type is %T, variable = %v", variable, variable)
case float32:
    fmt.Printf("type is %T, variable = %v", variable, variable)
}

测试

func testTypeAssertion4() {
    var a any = 2
    switch a := a.(type) {
    default:
        fmt.Printf("unexpected type %T", a)
    case string:
        fmt.Printf("type is %T, value = %v", a, a)
    case int:
        fmt.Printf("type is %T, value = %v", a, a)
    case bool:
        fmt.Printf("type is %T, value = %v", a, a)
    case float32:
        fmt.Printf("type is %T, value = %v", a, a)
    }
}

输出

type is int, value = 2

结构体类型断言

首先定义接口和结构体

// 颜色 接口
type Color interface {
    // 获取颜色
    getColor() Color
    // 修改颜色
    setColor(value int64, content string)
}

// 红色 结构体
type Red struct {
  // 颜色值
    Value int64 `json:"value"`
    // 颜色描述
    Content string `json:"content"`
}

// 黄色 结构体
type Yellow struct {
    // 颜色值
    Value int64 `json:"value"`
    // 颜色描述
    Content string `json:"content"`
}

然后让 Red 实现 ColorYellow不实现Color

func (y Yellow) getColor2() *Yellow {
    return &Yellow{
        Value:   0xF6FF33,
        Content: "黄色",
    }
}
func (y Yellow) setColor(value int64, content string) {
    y.Value = value
    y.Content = content
}

func (r Red) getColor() Color {
    return &Red{
        Value:   0xFF5733,
        Content: "红色",
    }
}
func (r Red) setColor(value int64, content string) {
    r.Value = value
    r.Content = content
}

此时编译器会告诉我们,Red 实现了 Color的方法

image.png

测试:


// testTypeAssertion5
// @Description:  结构体断言
func testTypeAssertion5() {

    var red interface{} = &Red{}
    var yellow interface{} = &Yellow{}

    // 可以用 if 判断
    if color, ok := red.(Color); ok {
        fmt.Printf("red ok = %v, color = %v\n", ok, color.getColor())
    } else {
        fmt.Printf("red ok = %v, color = %v\n", ok, red)
    }
    if color, ok := yellow.(Color); ok {
        fmt.Printf("yellow ok = %v, color = %v\n", ok, color.getColor())
    } else {
        fmt.Printf("yellow ok = %v, color = %v\n", ok, yellow)
    }

    // 可以用 switch 判断
    switch red := red.(type) {
    default:
        fmt.Printf("default no color, red = %v\n", red)
    case Red:
        fmt.Printf("color = Red, %v\n", red)
    case *Red:
        fmt.Printf("color = *Red, %v\n", red)
    case Color:
        fmt.Printf("color = Color, %v\n", red)
    }
}

输出

red ok = true, color = &{16734003 红色}
yellow ok = false, color = &{0 }
color = *Red, &{0 }

从测试代码中可以看出,结构体可以断言原本的类型,也可以断言是否继承,实际上这两个分支都是可以进去的

case *Red:
    fmt.Printf("color = *Red, %v\n", red)
case Color:
    fmt.Printf("color = Color, %v\n", red)

因为先匹配到了 *Red,再加上**switch **判断类型 不可以使用 fallthrough,所以如果切换顺序,会匹配到 Color

case Color:
    fmt.Printf("color = Color, %v\n", red)	
case *Red:
    fmt.Printf("color = *Red, %v\n", red)

输出

red ok = true, color = &{16734003 红色}
yellow ok = false, color = &{0 }
color = Color, &{0 }

以下三种方式都可以在编译前看到结构体是否实现了接口

    // 在编译之前判断结构体是否实现了某个接口
    // 实现了接口,没有错误提示
    var _ Color = &Red{}
    var _ Color = Red{}
    var _ Color =(*Red)(nil)

    // 没有实现接口,错误提示
    // var _ Color = &Yellow{}       

Red 使用可,对 Yellow 使用,编译器会提示错误

image.png

不能使用 '&Yellow{}' (type *Yellow) 作为类型 Color 类型。因为缺少getColor() Color方法,所以没有实现 'Color'