網站首頁 編程語言 正文
Go 中接口也是一個使用得非常頻繁的特性,好的軟件設計往往離不開接口的使用,比如依賴倒置原則(通過抽象出接口,分離了具體實現與實際使用的耦合)。 今天,就讓我們來了解一下 Go 中接口的一些基本用法。
概述
Go 中的接口跟我們常見的編程語言中的接口不太一樣,go 里面實現接口是不需要使用?implements
?關鍵字顯式聲明的, go 的接口為我們提供了難以置信的一系列的靈活性和抽象性。接口有兩個特點:
- 接口本質是一種自定義類型。(跟 Java 中的接口不一樣)
- 接口是一種特殊的自定義類型,其中沒有數據成員,只有方法(也可以為空)。
go 中的接口定義方式如下:
type?Flyable?interface?{ ????Fly()?string }
接口是完全抽象的,不能將其實例化。但是我們創建變量的時候可以將其類型聲明為接口類型:
var?a?Flyable
然后,對于接口類型變量,我們可以把任何實現了接口所有方法的類型變量賦值給它,這個過程不需要顯式聲明。 例如,假如?Bird
?實現了?Fly
?方法,那么下面的賦值就是合法的:
//?Bird?實現了?Flyable?的所有方法 var?a?Flyable?=?Bird{}
go 實現接口不需要顯式聲明。
由此我們引出 go 接口的最重要的特性是:
只要某個類型實現了接口的所有方法,那么我們就說該類型實現了此接口。該類型的值可以賦給該接口的值。
因為 interface{} 沒有任何方法,所以任何類型的值都可以賦值給它(類似 Java 中的 Object)
基本使用
Java 中的 interface(接口)
先看看其他語言中的 interface 是怎么使用的。
我們知道,很多編程語言里面都有?interface
?這個關鍵字,表示的是接口,應該也用過,比如 Java 里面的:
//?定義一個?Flyable?接口 interface?Flyable?{ ????public?void?fly(); } //?定義一個名為?Bird?的類,顯式實現了?Flyable?接口 class?Bird??implements?Flyable?{ ????public?void?fly()?{ ????????System.out.println("Bird?fly."); ????} } class?Test?{ ????//?fly?方法接收一個實現了?Flyable?接口的類 ????public?static?void?fly(Flyable?flyable)?{ ????????flyable.fly(); ????} ????public?static?void?main(String[]?args)?{ ????????Bird?b?=?new?Bird(); ????????//?b?實現了?Flyable?接口,所以可以作為?fly?的參數 ????????fly(b); ????} }
在這個例子中,我們定義了一個?Flyable
?接口,然后定義了一個實現了?Flyable
?接口的?Bird
?類, 最后,定義了一個測試的類,這個類的?fly
?方法接收一個?Flyable
?接口類型的參數, 因為?Bird
?類實現了?Flyable
?接口,所以可以將?b
?作為參數傳遞給?fly
?方法。
這個例子就是 Java 中?interface
?的典型用法,如果一個類要實現一個接口,我們必須顯式地通過?implements
?關鍵字來聲明。 然后使用的時候,對于需要某一接口類型的參數的方法,我們可以傳遞實現了那個接口的對象進去。
Java 中類實現接口必須顯式通過?implements
?關鍵字聲明。
go 中的 interface(接口)
go 里面也有?interface
?這個關鍵字,但是 go 與其他語言不太一樣。 go 里面結構體與接口之間不需要顯式地通過?implements
?關鍵字來聲明的,在 go 中,只要一個結構體實現了?interface
?的所有方法,我們就可以將這個結構體當做這個?interface
?類型,比如下面這個例子:
package?main import?"fmt" //?定義一個?Flyable?接口 type?Flyable?interface?{ ?Fly()?string } //?Bird?結構體沒有顯式聲明實現了?Flyable?接口(沒有?implements?關鍵字) //?但是?Bird?定義了?Fly()?方法, //?所以可以作為下面?fly?函數的參數使用。 type?Bird?struct?{ } func?(b?Bird)?Fly()?string?{ ?return?"bird?fly." } //?只要實現了?Flyable?的所有方法, //?就可以作為?output?的參數。 func?fly(f?Flyable)?{ ?fmt.Println(f.Fly()) } func?main()?{ ?var?b?=?Bird{} ?//?在?go?看來,b?實現了?Fly?接口, ?//?因為?Bird?里面實現了?Fly?接口的所有方法。 ?fly(b) }
在上面這個例子中,Person
?結構體實現了?Stringer
?接口的所有方法,所以在需要?Stringer
?接口的地方,都可以用?Person
?的實例作為參數。
Go 中結構體實現接口不用通過?implements
?關鍵字聲明。(實際上,Go 也沒有這個關鍵字)
go interface 的優勢
go 接口的這種實現方式,有點類似于動態類型的語言,比如 Python,但是相比 Python,go 在編譯期間就可以發現一些明顯的錯誤。
比如像 Python 中下面這種代碼,如果傳遞的?coder
?沒有?say_hello
?方法,這種錯誤只有運行時才能發現:
def?hello_world(coder): ????coder.say_hello()
但如果是 go 的話,下面這種寫法中,如果傳遞給?hello_world
?沒有實現?say
?接口,那么編譯的時候就會報錯,無法通過編譯:
type?say?interface?{ ?say_hello() } func?hello_world(coder?say)?{ ?coder.say_hello() }
因此,go 的這種接口實現方式有點像動態類型的語言,在一定程度上給了開發者自由,但是也在語言層面幫開發者做了類型檢查。
go 中不必像靜態類型語言那樣,所有地方都明確寫出類型,go 的編譯器幫我們做了很多工作,讓我們在寫 go 代碼的時候更加的輕松。 interface 也是,我們無需顯式實現接口,只要我們的結構體實現了接口的所有類型,那么它就可以當做那個接口類型使用(duck typing)。
空接口
go 中的?interface{}
?表示一個空接口(在比較新版本中也可以使用?any
?關鍵字來代替?interface{}
),這個接口沒有任何方法。因此可以將任何變量賦值給?interface{}
?類型的變量。
這在一些允許不同類型或者不確定類型參數的方法中用得比較廣泛,比如?fmt
?里面的?println
?等方法。
如何使用 interface{} 類型的參數?
這個可能是大部分人所需要關心的地方,因為這可能在日常開發中經常需要用到。
類型斷言
當實際開發中,我們接收到一個接口類型參數的時候,我們可能會知道它是幾種可能的情況之一了,我們就可以使用類型斷言來判斷?interface{}
?變量是否實現了某一個接口:
func?fly(f?interface{})?{ ?//?第一個返回值?v?是?f?轉換為接口之前的值, ?//?ok?為?true?表示?f?是?Bird?類型 ?if?v,?ok?:=?f.(Flyable);?ok?{ ??fmt.Println("bird?"?+?v.Fly()) ?} ?//?斷言形式:接口.(類型) ?if?_,?ok?:=?f.(Bird);?ok?{ ??fmt.Println("bird?flying...") ?} }
在實際開發中,我們可以使用?xx.(Type)
?這種形式來判斷:
-
interface{}
?類型的變量是否是某一個類型 -
interface{}
?類型的變量是否實現了某一個接口
如,f.(Flyable)
?就是判斷?f
?是否實現了?Flyable
?接口,f.(Bird)
?就是判斷?f
?是否是?Bird
?類型。
另外一種類型斷言方式
可能我們會覺得上面的那種?if
?的判斷方式有點繁瑣,確實如此,但是如果我們不能保證?f
?是某一類型的情況下,用上面這種判斷方式是比較安全的。
還有另外一種判斷方式,用在我們確切地知道?f
?具體類型的情況:
func?fly2(f?interface{})?{ ?fmt.Println("bird?"?+?f.(Flyable).Fly()) }
在這里,我們斷言?f
?是?Flyable
?類型,然后調用了它的?Fly
?方法。
這是一種不安全的調用,如果?f
?實際上沒有實現了?Flyable
?接口,上面這行代碼會引發?panic
。 而相比之下,v, ok := f.(Flyable)
?這種方式會返回第二個值讓我們判斷這個斷言是否成立。
switch...case 中判斷接口類型
除了上面的斷言方式,還有另外一種判斷?interface{}
?類型的方法,那就是使用?switch...case
?語句:
func?str(f?interface{})?string?{ ?//?判斷?f?的類型 ?switch?f.(type)?{ ?case?int: ??//?f?是?int?類型 ??return?"int:?"?+?strconv.Itoa(f.(int)) ?case?int64: ??//?f?是?int64?類型 ??return?"int64:?"?+?strconv.FormatInt(f.(int64),?10) ????case?Flyable: ????????return?"flyable..." ?} ?return?"???" }
編譯器自動檢測類型是否實現接口
上面我們說過了,在 go 里面,類型不用顯式地聲明實現了某個接口(也不能)。那么問題來了,我們開發的時候, 如果我們就是想讓某一個類型實現某個接口的時候,但是漏實現了一個方法的話,IDE 是沒有辦法知道我們漏了的那個方法的:
type?Flyable?interface?{ ?Fly()?string } //?沒有實現?Flyable?接口,因為沒有?Fly()?方法 type?Bird?struct?{ } func?(b?Bird)?Eat()?string?{ ?return?"eat." }
比如這段代碼中,我們本意是要?Bird
?也實現?Fly
?方法的,但是因為沒有顯式聲明,所以 IDE 沒有辦法知道我們的意圖。 這樣一來,在實際運行的時候,那些我們需要?Flyable
?的地方,如果我們傳了?Bird
?實例的話,就會報錯了。
一種簡單的解決方法
如果我們明確知道?Bird
?將來是要當做?Flyable
?參數使用的話,我們可以加一行聲明:
var?_?Flyable?=?Bird{}
這樣一來,因為我們有?Bird
?轉?Flyable
?類型的操作,所以編譯器就會去幫我們檢查?Bird
?是否實現了?Flyable
?接口了。 如果?Bird
?沒有實現?Flyable
?中的所有方法,那么編譯的時候會報錯,這樣一來,這些錯誤就不用等到實際運行的時候才能發現了。
實際上,很多開源項目都能看到這種寫法。看起來定義了一個空變量,但是實際上確可以幫我們進行類型檢查。
這種解決方法還有另外一種寫法如下:
var?_?Flyable?=?(*Bird)(nil)
類型轉換與接口斷言
我們知道了,接口斷言可以獲得一個具體類型(也可以是接口)的變量,同時我們也知道了,在 go 里面也有類型轉換這東西, 實際上,接口斷言與類型轉換都是類型轉換,它們的差別只是:
interface{}
?只能通過類型斷言來轉換為某一種具體的類型,而一般的類型轉換只是針對普通類型之間的轉換。
//?類型轉換:f?由?float32?轉換為?int var?f?float32?=?10.8 i?:=?int(f) //?接口的類型斷言 var?f?interface{} v,?ok?:=?f.(Flyable)
如果是 interface{},需要使用類型斷言轉換為某一具體類型。
一個類型可以實現多個接口
上文我們說過了,只要一個類型實現了接口中的所有方法,那么那個類型就可以當作是那個接口來使用:
type?Writer?interface?{ ????Write(p?[]byte)?(n?int,?err?error) } type?Closer?interface?{ ????Close()?error } type?myFile?struct?{ } //?實現了?Writer?接口 func?(m?myFile)??Write(p?[]byte)?(n?int,?err?error)?{ ?return?0,?nil } //?實現了?Closer?接口 func?(m?myFile)?Close()?error?{ ?return?nil }
在上面這個例子中,myFile
?實現了?Write
?和?Close
?方法,而這兩個方法分別是?Writer
?和?Closer
?接口中的所有方法。 在這種情況下,myFile
?的實例既可以作為?Writer
?使用,也可以作為?Closer
?使用:
func?foo(w?Writer)?{ ?w.Write([]byte("foo")) } func?bar(c?Closer)?{ ?c.Close() } func?test()?{ ?m?:=?myFile{} ?//?m?可以作為?Writer?接口使用 ?foo(m) ?//?m?也可以作為?Closer?接口使用 ?bar(m) }
接口與 nil 不相等
有時候我們會發現,明明傳了一個?nil
?給?interface{}
?類型的參數,但在我們判斷實參是否與?nil
?相等的時候,卻發現并不相等,如下面這個例子:
func?test(i?interface{})?{ ?fmt.Println(reflect.TypeOf(i)) ?fmt.Println(i?==?nil) } func?main()?{ ?var?b?*int?=?nil ?test(b)?//?會輸出:*int?false ?test(nil)?//?會輸出:<nil>?true }
這是因為 go 里面的?interface{}
?實際上是包含兩部分的,一部分是?type
,一部分是?data
,如果我們傳遞的?nil
?是某一個類型的?nil
, 那么?interface{}
?類型的參數實際上接收到的值會包含對應的類型。 但如果我們傳遞的?nil
?就是一個普通的?nil
,那么?interface{}
?類型參數接收到的?type
?和?data
?都為?nil
, 這個時候再與?nil
?比較的時候才是相等的。
嵌套的接口
在 go 中,不僅結構體與結構體之間可以嵌套,接口與接口也可以通過嵌套創造出新的接口。
type?Writer?interface?{ ????Write(p?[]byte)?(n?int,?err?error) } type?Closer?interface?{ ????Close()?error } //?下面這個接口包含了?Writer?和?Closer?的所有方法 type?WriteCloser?interface?{ ????Writer ????Closer }
WriteCloser
?是一個包含了?Writer
?和?Closer
?兩個接口所有方法的新接口,也就是說,WriteCloser
?包含了?Write
?和?Close
?方法。
這樣的好處是,可以將接口拆分為更小的粒度。比如,對于某些只需要?Close
?方法的地方,我們就可以用?Closer
?作為參數的類型, 即使參數也實現了?Write
?方法,因為我們并不關心除了?Close
?以外的其他方法:
func?foo(c?Closer)?{ ?//?... ?c.Close() }
而對于上面的?myFile
,因為同時實現了?Writer
?接口和?Closer
?接口,而?WriteCloser
?包含了這兩個接口, 所以實際上?myFile
?可以當作?WriteCloser
?或者?Writer
?或?Closer
?類型使用。
總結
- 接口里面只聲明了方法,沒有數據成員。
- go 中的接口不需要顯式聲明(也不能)。
- 只要一個類型實現了接口的所有方法,那么該類型實現了此接口。該類型的值可以賦值給該接口類型。
-
interface{}
/any
?是空接口,任何類型的值都可以賦值給它。 - 通過類型斷言我們可以將?
interface{}
?類型轉換為具體的類型。 - 我們通過聲明接口類型的?
_
?變量來讓編譯器幫我們檢查我們的類型是否實現了某一接口。 - 一個類型可以同時實現多個接口,可以當作多個接口類型來使用。
-
nil
?與值為?nil
?的?interface{}
?實際上不想等,需要注意。 - go 中的接口可以嵌套,類似結構體的嵌套。
原文鏈接:https://mp.weixin.qq.com/s/XI_dnDGow_tExFrcKSVnPA
相關推薦
- 2022-08-10 Pandas?reindex重置索引的使用_python
- 2022-04-06 一篇文章帶你了解C/C++的回調函數_C 語言
- 2022-10-14 eclipse創建maven項目
- 2022-08-30 MQTT - 消息隊列遙測傳輸協議
- 2022-09-20 C#?Winform實現復制文件顯示進度_C#教程
- 2022-08-03 如何一鍵理清大型Python項目依賴樹_python
- 2023-03-20 C#如何讓winform程序中的輸入文本框保留上次的輸入_C#教程
- 2022-07-15 Golang配置解析神器go?viper使用詳解_Golang
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支