Go语言入门-接口

Go语言入门-接口

概述

定义

An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil

从官方文档里可以看出:
1. 接口是一种类型,不包含任何变量,不能定义自己的方法,不能实现自己的方法
2. 接口是多个方法的集合,
3. 没有初始化接口是nil
4. 任何类型的方法集中只要包含接口的所有方法,就意味着该类型实现了对应的接口。
5. 补充一点:可以嵌入其他接口类型

语法

type interfaceName interface {
    //方法名+参数列表+返回值列表
    MethodName1(paramsList1) returnList1
    MethodName2(paramsList2) returnList2
    ...
    MethodNameN(paramsListN) returnListN
}
  • 示例-1:
// 定义一个接口类型io,该接口包含了Read、Write和Close三个方法。
type io interface {
    Read([]byte)(int, error)
    Write([]byte)(int, error)
    Close()error
}

使用

实现接口

在Java语言中实现接口有implement关键字用来标识某一对象实现了什么接口,但是go语言中的接口的实现机制是只要目标类型方法集中包含了接口声明中的所有方法,就意味着该类型实现了指定接口。

  • 示例2-简单接口实现演示
type io interface {
    Read([]byte)(int, error)
    Write([]byte)(int, error)
    Close()error
}

type myFile struct {
    fileName string
}

func (m myFile) Read(bytes []byte) (int, error) {
    //传递备份
    fmt.Println(m.fileName + " File Read:" + string(bytes))
    return len(bytes), nil
}

func (m myFile) Write(bytes []byte) (int, error) {
    //传递指针
    fmt.Println(m.fileName + " File Write:" + *(*string)(unsafe.Pointer(&bytes)))
    return len(bytes), nil
}

func (m myFile) Close() error {
    fmt.Println(m.fileName + " The file was closed")
    return nil
}
func main() {
    file := myFile{fileName: "myfile"}
    file.Read([]byte("read123456"))
    file.Write([]byte("write123456"))
    file.Close()
    var i io
    //i 是接口io类型,因为类型myFile实现io的所有接口因此可以把类型的变量file赋值给i。通过接口变量i进行调用。
    i = file
    i.Read([]byte("read123456"))
    i.Write([]byte("write123456"))
    i.Close()
}
/**
output:
myfile File Read:read123456
myfile File Write:write123456
myfile The file was closed
myfile File Read:read123456
myfile File Write:write123456
myfile The file was closed
 */

以上例子myFile实现了接口io,

示例3 -演示实现部分接口方法

// 定义一个接口类型io,该接口包含了Read、Write和Close三个方法。
type io interface {
    Read([]byte)(int, error)
    Write([]byte)(int, error)
    Close()error
}
//定义一个接口
type NetIo struct {
    name string
    ip string
    point int
}
//重写String方法
func (n NetIo) String()(s string)  {
    s = n.name + " " + n.ip + ":" + strconv.Itoa(n.point)
    return s
}
func (n NetIo) Write(bytes []byte) (int, error) {
    //传递指针
    fmt.Println(n.name+ " NetIo Write:" + *(*string)(unsafe.Pointer(&bytes)))
    return len(bytes), nil
}
func main() {
    net := NetIo{
        name:  "net",
        ip:    "127.0.0.1",
        point: 22,
    }
    //Println传入的也是接口
    fmt.Println(net)
    net.Write([]byte("write something"))
    var i io
    //未赋值则为nil
    fmt.Println(i)
    //.\intefaceDemo3.go:40:7: cannot use net (type NetIo) as type io in assignment:
    //	NetIo does not implement io (missing Close method)
    //i = net
    //i.Write([]byte("write something"))
}
/**
output:
net 127.0.0.1:22
net NetIo Write:write something

 */

通过以上例子可以看出,如果类型没有实现对应方法。就无法把变量赋值给接口。

  • 示例4 -演示接口的值接收者和方法接收者
type animal struct {
    name string
}
type Runner interface {
    run()
}
type Flyer interface {
    fly()
}

//鸟既能飞又能跑呀
type bird struct {
    animal
}
//实现接口Runner
func (b bird) run() {
    fmt.Println("bird " + b.name +" can run")
}
//实现接口Flyer
func (b *bird) fly() {
    fmt.Println("bird " + b.name +" can fly")
}
func main() {
    b := bird{
        animal: animal{"bird A"},
    }
    
    bp := &b
    fmt.Println("-----------------------------")
    //自动bird转*bird
    b.fly()
    b.run()
    fmt.Println("-----------------------------")
    bp.fly()
    //自动*bird转bird
    bp.run()
    //重点来了
    //fly是指针接收者,因此只有*bird的方法集里包含了fly,因此无法给i1赋值b,因为b是bird类型
    var i1 Flyer
    //cannot use b (type bird) as type Flyer in assignment:bird does not implement Flyer (fly method has pointer receiver)
    //i1 = b
    i1 = bp
    i1.fly()
    fmt.Println("-----------------------------")
    var i2 Runner
    //run是值接收者,因此 *bird bird的方法集里都包含run,因此可以进行赋值,正常处理
    i2 = bp
    i2.run()
    i2 = b
    i2.run()
}
/**
output:
-----------------------------
bird bird A can fly
bird bird A can run
-----------------------------
bird bird A can fly
bird bird A can run
bird bird A can fly
-----------------------------
bird bird A can run
bird bird A can run
 */

通过以上例子我们可以看出:

  1. 对于类型变量自身,均可调用指针方法和值方法(编译器会自动转换)
  2. 接口变量如果赋值为T类型变量,那么接口变量关联的是T的方法集。无法访问 *T方法集,如果T的方法集包含了接口声明的方法,则赋值成功,否则失败。
  3. 接口变量如果赋值为T类型变量,那么接口变量关联的的方法集是T和T的方法集,如果T和*T方法集都包含了接口声明的办法,则赋值成功,否则失败。
    编译器根据方法集来判断是否实现接口。

接口嵌套

  • 示例5-接口嵌套
type Runner interface {
    run()
}
type Flyer interface {
    fly()
}

type FlyerAndRuner interface {
    Runner
    Flyer
}

可以嵌入其他接口类型

接口解耦与多态

接口既然可以用实现接口的类型变量赋值,那么当存在多个实现相同接口的类型变量,给接口赋值时,接口变量都能按照实际的变量类型与类型相关的方法(每种方法的行为可能不一样),类似于C++和JAVA中的多态的情况,能够根据不同的类型变量调用实际变量的方法。
适用与主调方不需要事先关心入参的类型,只要实现指定接口的类型作为主调方都能调用,便于程序的解耦合扩展。
来个例子

  • 示例6 接口的“多态” 1
type animal struct {
    name string
}
type Runner interface {
    run()
}
type monkey struct {
    animal
    leg int
}
func (m monkey) run() {
    fmt.Println("The monkey " +m.name + " is Running")
}
type person struct {
    animal
    len int
}
func (p person) run() {
    fmt.Println("The person " +p.name + " is Running")
}
func main() {
    p := person{
        animal: animal{"zhansan"},
        len:    2,
    }
    m := monkey{
        animal: animal{"sunwukong"},
        leg:    4,
    }
    //声明接口变量r
    var r Runner
    //使用p初始化接口变量 可以理解为 r = interface.initInterface(m) 
    r = p
    r.run()
    //使用m赋值接口变量可以理解为 r = m.initInterface()
    r = m
    r.run()
}
/**
output:
The person zhansan is Running
The monkey sunwukong is Running
 */

接口变量可以通过赋值不同对象,来按照对象实际的类型调用实际的方法。

  • 示例6 使用接口进行解耦

type animal struct {
    name string
}
type Runner interface {
    run()
}
type monkey struct {
    animal
    leg int
}
func (m monkey) run() {
    fmt.Println("The monkey " +m.name + " is Running")
}
type person struct {
    animal
    len int
}
func (p person) run() {
    fmt.Println("The person " +p.name + " is Running")
}

func RunnerCall(runner Runner)  {
    fmt.Println("我只关注传入的类型是否实现指定接口的方法")
    runner.run()
    fmt.Println("我只关注的传入的类型是否实现指定接口的方法调用成功了")
}
func main() {
    p := person{
        animal: animal{"zhansan"},
        len:    2,
    }
    m := monkey{
        animal: animal{"sunwukong"},
        leg:    0,
    }
    
    RunnerCall(p)
    RunnerCall(m)
    //cannot use "eqweqw" (type string) as type Runner in argument to RunnerCall:
    //string does not implement Runner (missing run method)
    //RunnerCall("eqweqw")
}
/**
output:
我只关注传入的类型是否实现指定接口的方法
The person zhansan is Running
我只关注的传入的类型是否实现指定接口的方法调用成功了
我只关注传入的类型是否实现指定接口的方法
The monkey sunwukong is Running
我只关注的传入的类型是否实现指定接口的方法调用成功了
 */

空接口

interface{}空接口,也是一个接口类型,该接口类型比较特殊,不包活任何方法的声明,可以被赋值为任何类型的对象。也意味着所有类型的都实现了空接口。

  • 示例7-空接口的演示
func MyPrint(i interface{})  {
    fmt.Printf("type:%T value:%v\n", i, i)
}
func main() {
    var i interface{}
    MyPrint(i)
    //字符串
    i = "12312"
    fmt.Printf("type:%T value:%v\n", i, i)
    //字节数组
    i = []byte("12312")
    fmt.Printf("type:%T value:%v\n", i, i)
    //int
    i = 3
    fmt.Printf("type:%T value:%v\n", i, i)
    MyPrint(i)
}
/**
output:
type: value:
type:string value:12312
type:[]uint8 value:[49 50 51 49 50]
type:int value:3
type:int value:3
 */

类型断言(转换)

类型断言,
For an expression x of interface type and a type T, the primary expression

x.(T)
  • x:表示类型为interface{}的变量
  • T:表示断言x可能是的类型。

1. 类型断言可以将接口变量还原为原始变量。

  • 示例8
func main() {
    a := 100
    var i interface{}
    i = a
    ii := i.(int)
    fmt.Printf("%p %T %v\n", &a, a, a)
    //虽然是还原是地址还是不一样的
    fmt.Printf("%p %T %v\n", &ii, ii, ii)
    //类型转换失败引发panic
    iii := i.(string)
    fmt.Println(iii)
}
/**
output:
0xc000072090 int 100
0xc000072098 int 100
panic: interface conversion: interface {} is int, not string

 */
  1. 类型断言也可以判断是否实现某个更具体的接口类型
  • 示例9
type data int

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

func main() {
    var d data = 5
    //x是空接口
    var x interface{} = d
    
    //转为更具体的接口
    if n, ok := x.(fmt.Stringer); ok {
        fmt.Printf("%T %s\n", n, n)
    }
    
    if n, ok := x.(fmt.Scanner); ok {
        fmt.Printf("%T %s\n", n, n)
    } else {
        //ok-idiom 类型转换失败不会pannic
        fmt.Println(ok)
    }
    //panic 非ok-idiom模式,转换失败会panic
    //panic: interface conversion: main.data is not fmt.Scanner: missing method Scan
    //n := x.(fmt.Scanner)
    //fmt.Printf("%T %s\n", n, n)
}
/**
output:
main.data data:5
false
 */
  1. switch类型断言
func main() {
    //字符串
    getType("xxx")
    //字节数组
    getType([]byte("xxx"))
    //空nil
    getType(nil)
    //bool
    getType(false)
    //虽然是转换后的interface{} 但是类型转化可以转换为更具体的接口类型 最后还是123123类型
    getType(interface{}("123123"))
    getType(ast.Field{})
}
/**
output:
this is string  xxx
this is []byte  [120 120 120]
this is nil  
this is bool  false
this is string  123123
this is ??? { []   }
 */

执行机制

以下来在《go语言学习笔记》
接口使用一个*itab的结构存储运行时相关信息。

type iface struct {
    //类型信息
    tab *itab
    //实际对象指针
    data unsafe.Pointer
}
type itab struct {
    //接口类型
    inter *interfacetype
    //实际对象类型
    _type *_type
    //实际对象方法地址
    fun [1]uintptr
}

接口变量实际保留的是接口itab和对应对象地址。接口itab中fun数据宝坤了实际方法的地址因此可以再运行时动态的调用目标方法。

骚操作

  1. 让编译器检查,确保类型实现指定的接口
type person struct {
    animal
    len int
}
type Runner interface {
    run()
}
func (p person) run() {
    fmt.Println("The person " +p.name + " is Running")
}
func main() {
    //检查person是否实现Runner接口
    var _ Runner = person{}
    ////Cannot use 'string{}' (type string) as type Runner in assignment Type does not implement 'Runner' as some methods are missing: run()
    //var _ Runner = string{}
    
}
/**
output:
 */
  1. 当对象赋值给接口变量时,会赋值该对象,因此需要指针来避免对象的复制
package main

import "fmt"

type animal struct {
    name string
}
type Runner interface {
    run()
}
type Flyer interface {
    fly()
}
type monkey struct {
    animal
    leg int
}
func (m monkey) run() {
    fmt.Printf("address=[%p] The monkey [%s] is running \n", &m, m.name)
}
func (m *monkey) fly() {
    fmt.Printf("address=[%p] The monkey [%s] is flying \n", m, m.name)
}

func main() {
    m := monkey{
        animal: animal{"name"},
        leg:    0,
    }
    fmt.Println("值方法====================")
    //变量m的地址
    fmt.Printf("address=[%p] The monkey [%s] is running \n", &m, m.name)
    var iface Runner = m
    //值方法调用本事就会复制对象
    iface.run()
    //还原原始变量,因为是指针因此还原出的地址是和定义的是一样的
    var iPface Runner = &m
    fmt.Printf("address=[%p] The monkey [%s] is running \n", iPface.(*monkey))
    //值方法调用本事就会复制对象
    iPface.run()
    fmt.Println("指针方法====================")
    //Cannot use 'm' (type monkey) as type Flyer in assignment Type does not implement 'Flyer' as 'fly' method has a pointer receiver
    //var iface1 Flyer = m
    //iface1.fly()
    var iPface1 Flyer = &m
    //获取原对象地址的副本解地址找到原来的对象地址
    fmt.Printf("address=[%p] The monkey [%s] is flying \n", iPface1.(*monkey))
    //指针方法模拟穿引用
    m.fly()
    
}
/**
output:
值方法====================
address=[0xc0000044a0] The monkey [name] is running
address=[0xc000004500] The monkey [name] is running
address=[0xc0000044a0] The monkey [%!s(MISSING)] is running
address=[0xc000004520] The monkey [name] is running
指针方法====================
address=[0xc0000044a0] The monkey [%!s(MISSING)] is flying
address=[0xc0000044a0] The monkey [name] is flying
*/
  1. 当对象赋值给接口变量时,会赋值该对象,因此需要指针来避免对象的复制

  2. 只有当接口变量内部指针itab和data都为nil时,接口才等于nil

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)


type iface struct {
    itab, data uintptr
}


func main() {
    //tab = nil, data = nil
    var a interface{} = nil
    //tab ~= *int, data = nil
    var b interface{} = (*int)(nil)
    
    fmt.Println(a == nil, b == nil)
    ia := *(*iface)(unsafe.Pointer(&a))
    ib := *(*iface)(unsafe.Pointer(&a))
    fmt.Println(a == nil, ia)
    fmt.Println(b == nil, ib, reflect.ValueOf(b).IsNil())
}
/**
output:
true false
true {0 0}
false {0 0} true

 */

你可能感兴趣的:(GO语言,关键字)