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
<nil>
 */

通過以上例子可以看出,如果類型沒有實現對應方法。就無法把變量賦值給接口。

  • 示例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:<nil> value:<nil>
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  <nil>
this is bool  false
this is string  123123
this is ??? {<nil> [] <nil> <nil> <nil>}
 */

執行機制

以下來在《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

 */

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章