Golang基础篇-interface

808 阅读5分钟

本文主要介绍Go语言中接口(interface)的概念、使用等入门级别知识;文中如有描述不对或则不合理的地方,请各位大佬积极留言,我会每日及时查看并核查纠正。

概念

  • 代表一种"协议"或则"约定",是多个方法申明的集合
  • 接口无需依赖类型

定义

  • 一个接口需要包括方法签名,方法签名需要包含:方法名称、参数、返回值。接口内不能有字段,而且不能定义自己的方法。
// Animaler 接口名称建议以 er 结尾
type Animaler interface {
    eat(food string) string
    sleep()
}

基础应用场景

  • 实现多态功能
package main

import (
	"fmt"
)

// Animaler 接口: 所有遵从该接口协议的,均需要实现speak,eat方法
type Animaler interface {
	speak() string
	eat(foot string) string
}

func anmialSpeak(animaler Animaler) string {
	return animaler.speak()
}

func anmialEat(animaler Animaler, food string) string {
	return animaler.eat(food)
}

// Dog 类
type Dog struct {
	name string
}

// speak Dog的方法
func (d Dog) speak() string {
	return "汪汪"
}

// eat Dog的方法
func (d Dog) eat(foot string) string {
	return fmt.Sprintf("%s: eat %s", d.name, foot)
}

// Cat 类
type Cat struct {
	age int
}

// speak Cat的方法
func (c Cat) speak() string {
	return "喵喵"
}

// eat Cat的方法
func (c Cat) eat(foot string) string {
	return fmt.Sprintf("%d cat eat %s", c.age, foot)
}

func test004() {
	dog := Dog{name : "大白"}
	cat := Cat{age: 2}
	fmt.Println(anmialSpeak(dog))  // 汪汪
	fmt.Println(anmialSpeak(cat))  // 喵喵
	fmt.Println(anmialEat(dog, "猪肉"))  // 大白: eat 猪肉
	fmt.Println(anmialEat(cat, "猫粮"))  // 2 cat eat 猫粮

}

func main() {
	test004()
}

空接口

  • 如果接口没有任何方法声明,那么就是一个空接口(interface{}),它的用途类似面向对象里的根类型Object,可被赋值为任何类型的对象(值)
  • 所有类型都实现了空接口
  • interface{}不是任意类型,它就是interface{}类型

// 切记: v不是任意类型,是interface{}类型;go运行时如果需要将执行类型转换为interface{}类型,且所有的值在运行时只会有一个类型,即类型静态化的。
func do(v interface{}) {

}

func done(vals []interface{}) {
	for _, val := range vals {
		fmt.Println(val)
	}
}

func test007() {
	vals := []string {"conk", "pht", "nimibox"}
	// 编译报错: cannot use vals (type []string) as type []interface {} in argument to done
	done(vals)
}

func test008() {
	vals := []string {"conk", "pht", "nimibox"}
	newVals := make([]interface{}, len(vals))
	for index, v := range vals {
		newVals[index] = v
	}
	done(newVals)
}

指针在interface中的使用

  • 接口定义没有规定一个实现者是否应该使用一个指针接收器或一个值接收器来实现接口
  • 一个指针类型可以通过其相关的值类型来访问值类型的方法,但是反过来不行
  • Go 中的所有东西都是按值传递的。每次调用函数时,传入的数据都会被复制。对于具有值接收者的方法,在调用该方法时将复制该值。

// Dog 类
type Dog struct {
	name string
}

// speak Dog的方法
func (d Dog) speak() string {
	return "汪汪"
}

func (d Dog) eat(foot string) string {
	return fmt.Sprintf("%s: eat %s", d.name, foot)
}

// Cat 类
type Cat struct {
	age int
}

// speak Cat的方法
func (c *Cat) speak() string {
	return "喵喵"
}

func (c *Cat) eat(foot string) string {
	return fmt.Sprintf("%d cat eat %s", c.age, foot)
}

// 任何一个 Cat 类型的值可能会有很多 *Cat 类型的指针指向它,如果我们尝试通过 Cat 类型的值来调用 *Cat 的方法,根本就不知道对应的是哪个指针
func test009() {
	// 编译报错: cannot use Cat literal (type Cat) as type Animaler in slice literal: Cat does not implement Animaler (eat method has pointer receiver)
	animals := []Animaler {Dog{"大白"}, Cat{18}}
}

func test010() {
	// 注意细节: Dog类实例两个对象,一个是指针对象,一个是值对象;但是编译都正常
	anmials := []Animaler {Dog{"大白"}, &Cat{18}}, &Dog{"小黑"}}
	for _, animal := range anmials {
		fmt.Println(animal.speak())
	}
}

类型转换

  • ok-idiom模式
package main

import (
	"fmt"
)

type data int

func (d data) String() string {
	return fmt.Sprintf("data : %d\n", d)
}

func test001() {
	// 创建一个data类型的实例
	var d data = 10
	// 创建一个interface实例
	var inter interface{} = d
	fmt.Printf("%T\n", inter)  // main.data

	// 判断inter的更具体接口类型: 初始化为interface{}空接口类型 更具体的为fmt.Stringer接口类型<此处建议idea查看fmt.Stringer源码>
	if x, ok := inter.(fmt.Stringer); ok {
		fmt.Println(x) // data: 10
	}

	// 判断inter的原始类型
	if y, ok := inter.(data); ok {
		fmt.Println(y) // data: 10
	}

	// panic: interface conversion: main.data is not error: missing method Error
	e := inter.(error)  // 不使用ok-idiom模式,转换失败会引发panic
	fmt.Println(e)
}

func main() {
	test001()
}
  • 类型检查
package main

import (
	"fmt"
)

// 通用的接口类型检查函数
func inspect(inter interface{}) {
	switch v := inter.(type) {
	case nil:
		fmt.Println("nil")
	case *int:
		fmt.Println(*v)
	case func(int) string:
		fmt.Println(v(100))
	case fmt.Stringer:
		fmt.Println(v)
	default:
		println("not support")
	}
} 

func test002() {
	var inter interface{} = func(num int) string {
		return fmt.Sprintf("num: %d\n", num)
	}
	inspect(inter)
}

func main() {
	test002()
}
  • 可变长度类型检查
// 可以传n个不限定个数的参数
func inspect(inters ...interface{}) {
	for index, inter := range(inters): {
		switch v := inter.(type) {
		case nil:
			fmt.Println("nil")
		case *int:
			fmt.Println(*v)
		case func(int) string:
			fmt.Println(v(100))
		case fmt.Stringer:
			fmt.Println(v)
		default:
			println("not support")
		}
	}
} 

interface常见的坑位

  • 接口变量默认值为nil
func test005() {
	var inter1, inter2 interface{}
	fmt.Println(inter1 == nil, inter1 == inter2) // true true
	inter1, inter2 = 1, 1
	fmt.Println(inter1 == inter2)  // true
	inter1, inter2 = map[string]int{}, map[string]int{}
	// panic: runtime error: comparing uncomparable type map[string]int
	fmt.Println(inter1 == inter2)
}
  • 比较运算
如果实现接口的类型支持,可做相等运算
  • 嵌套interface

type stringer interface {
	string() string
}

type tester interface {
	stringer
	test()
}

// 目标类型方法集中必须拥有包含嵌入接口方法在内的全部方法才算实现了该接口
type person struct {}

func (p person) string() string {
	return "string 格式化"
}

func (p person) test() {
	fmt.Println("test 方法")
}

func test006() {
	var p person
	var t tester = p
	t.test()  // test 方法
	fmt.Println(t.string())  // string 格式化
}

自我检测

var i interface{}
i = "hello"
// 这句代表什么意思?判断inter的原始类型,你的答案正确吗?☺️
s := i.(string)
fmt.Println(s)  // hello