Golang拾遺之自定義類型和方法集詳解
golang拾遺主要是用來記錄一些遺忘了的、平時(shí)從沒注意過的golang相關(guān)知識(shí)。
很久沒更新了,我們先以一個(gè)謎題開頭練練手:
package main import ( "encoding/json" "fmt" "time" ) type MyTime time.Time func main() { myTime := MyTime(time.Now()) // 假設(shè)獲得的時(shí)間是 2022年7月20日20:30:00,時(shí)區(qū)UTC+8 res, err := json.Marshal(myTime) if err != nil { panic(err) } fmt.Println(string(res)) }
請問上述代碼會(huì)輸出什么:
1.編譯錯(cuò)誤
2.運(yùn)行時(shí)panic
3.{}
4."2022-07-20T20:30:00.135693011+08:00"
很多人一定會(huì)選4吧,然而答案是3:
$ go run customize.go
{}
是不是很意外,MyTime就是time.Time,理論上應(yīng)該也實(shí)現(xiàn)了json.Marshaler,為什么輸出的是空的呢?
實(shí)際上這是最近某個(gè)群友遇到的問題,乍一看像是golang的bug,但其實(shí)還是沒掌握語言的基本規(guī)則。
在深入下去之前,我們先問自己兩個(gè)問題:
- MyTime 真的是 Time 類型嗎?
- MyTime 真的實(shí)現(xiàn)了 json.Marshaler 嗎?
對于問題1,只需要引用spec里的說明即可:
A named type is always different from any other type.
https://go.dev/ref/spec#Type_identity
意思是說,只要是type定義出來的類型,都是不同的(type alias除外),即使他們的underlying type是一樣的,也是兩個(gè)不同的類型。
那么問題1的答案就知道了,顯然MyTime不是time.Time。
既然MyTime不是Time,那它是否能用Time類型的method呢?畢竟MyTime的基底類型是Time呀。我們寫段代碼驗(yàn)證下:
package main import ( "fmt" "time" ) type MyTime time.Time func main() { myTime := MyTime(time.Now()) // 假設(shè)獲得的時(shí)間是 2022年7月20日20:30:00,時(shí)區(qū)UTC+8 res, err := myTime.MarsharlJSON() if err != nil { panic(err) } fmt.Println(string(res)) }
運(yùn)行結(jié)果:
# command-line-arguments
./checkoutit.go:12:24: myTime.MarsharlJSON undefined (type MyTime has no field or method MarsharlJSON)
現(xiàn)在問題2也有答案了:MyTime沒有實(shí)現(xiàn)json.Marshaler。
那么對于一個(gè)沒有實(shí)現(xiàn)json.Marshaler的類型,json是怎么序列化的呢?這里就不賣關(guān)子了,文檔里有寫,對于沒實(shí)現(xiàn)Marshaler的類型,默認(rèn)的流程使用反射獲取所有非export的字段,然后依次序列化,我們再看看time的結(jié)構(gòu):
type Time struct { // wall and ext encode the wall time seconds, wall time nanoseconds, // and optional monotonic clock reading in nanoseconds. // // From high to low bit position, wall encodes a 1-bit flag (hasMonotonic), // a 33-bit seconds field, and a 30-bit wall time nanoseconds field. // The nanoseconds field is in the range [0, 999999999]. // If the hasMonotonic bit is 0, then the 33-bit field must be zero // and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext. // If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit // unsigned wall seconds since Jan 1 year 1885, and ext holds a // signed 64-bit monotonic clock reading, nanoseconds since process start. wall uint64 ext int64 // loc specifies the Location that should be used to // determine the minute, hour, month, day, and year // that correspond to this Time. // The nil location means UTC. // All UTC times are represented with loc==nil, never loc==&utcLoc. loc *Location }
里面都是非公開字段,所以直接序列化后整個(gè)結(jié)果就是{}。當(dāng)然,Time類型自己重新實(shí)現(xiàn)了json.Marshaler,所以可以正常序列化成我們期望的值。
而我們的MyTime沒有實(shí)現(xiàn)整個(gè)接口,所以走了默認(rèn)的序列化流程。
所以我們可以得出一個(gè)重要的結(jié)論:從某個(gè)類型A派生出的類型B,B并不能獲得A的方法集中的任何一個(gè)。
想要B擁有A的所有方法也不是不行,但得和type B A這樣的形式說再見了。
方法一是使用type alias:
- type MyTime time.Time + type MyTime = time.Time func main() { - myTime := MyTime(time.Now()) // 假設(shè)獲得的時(shí)間是 2022年7月20日20:30:00,時(shí)區(qū)UTC+8 + var myTime MyTime = time.Now() // 假設(shè)獲得的時(shí)間是 2022年7月20日20:30:00,時(shí)區(qū)UTC+8 res, err := json.Marshal(myTime) if err != nil { panic(err) } fmt.Println(string(res)) }
類型別名自如其名,就是創(chuàng)建了一個(gè)類型A的別名而沒有定義任何新類型(注意那兩行改動(dòng))。現(xiàn)在MyTime就是Time了,自然也可以直接利用Time的MarshalJSON。
方法二,使用內(nèi)嵌類型:
- type MyTime time.Time + type MyTime struct { + time.Time + } func main() { - myTime := MyTime(time.Now()) // 假設(shè)獲得的時(shí)間是 2022年7月20日20:30:00,時(shí)區(qū)UTC+8 + myTime := MyTime{time.Now} res, err := myTime.MarsharlJSON() if err != nil { panic(err) } fmt.Println(string(res)) }
通過將Time嵌入MyTime,MyTime也可以獲得Time類型的方法集。更具體的可以看我之前寫的另一篇文章:golang拾遺:嵌入類型
如果我實(shí)在需要派生出一種新的類型呢,通常在我們寫一個(gè)通用模塊的時(shí)候需要隱藏實(shí)現(xiàn)的細(xì)節(jié),所以想要對原始類型進(jìn)行一定的包裝,這時(shí)該怎么辦呢?
實(shí)際上我們可以讓MyTime重新實(shí)現(xiàn)json.Marshaler:
type MyTime time.Time func (m MyTime) MarshalJSON() ([]byte, error) { // 我圖方便就直接復(fù)用Time的了 return time.Time(m).MarshalJSON() } func main() { myTime := MyTime(time.Now()) // 假設(shè)獲得的時(shí)間是 2022年7月20日20:30:00,時(shí)區(qū)UTC+8 res, err := myTime.MarsharlJSON() if err != nil { panic(err) } fmt.Println(string(res)) }
這么做看上去違反了DRY原則,其實(shí)未必,這里只是示例寫的爛而已,真實(shí)場景下往往對派生出來的自定義類型進(jìn)行一些定制,因此序列化函數(shù)里會(huì)有額外的一些操作,這樣就和DRY不沖突了。
不管哪一種方案,都可以解決問題,根據(jù)自己的實(shí)際需求做選擇即可。
總結(jié)
總結(jié)一下,一個(gè)派生自A的自定義類型B,它的方法集中的方法只有兩個(gè)來源:
- 直接定義在B上的那些方法
- 作為嵌入類型包含在B里的其他類型的方法
而A的方法是不存在在B中的。
如果是從一個(gè)匿名類型派生的自定義類型B(type B struct {a, b int}),那么B的方法集中的方法只有一個(gè)來源:
直接定義在B上的那些方法
還有最重要的,如果兩個(gè)類型名字不同,即使它們的結(jié)構(gòu)完全相同,也是兩個(gè)不同的類型。
這些邊邊角角的知識(shí)很容易被遺忘,但還是有機(jī)會(huì)在工作中遇到的,記牢了可以省很多事。
到此這篇關(guān)于Golang拾遺之自定義類型和方法集詳解的文章就介紹到這了,更多相關(guān)Golang自定義類型 方法集內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言開發(fā)框架反射機(jī)制及常見函數(shù)示例詳解
這篇文章主要為大家介紹了Go語言開發(fā)框架反射機(jī)制及常見函數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Golang 如何判斷數(shù)組某個(gè)元素是否存在 (isset)
這篇文章主要介紹了Golang 如何判斷數(shù)組某個(gè)元素是否存在 (isset),具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04Go?語言簡單實(shí)現(xiàn)Vigenere加密算法
這篇文章主要介紹了Go語言簡單實(shí)現(xiàn)Vigenere加密算法,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-09-09Ubuntu下安裝Go語言開發(fā)環(huán)境及編輯器的相關(guān)配置
這篇文章主要介紹了Ubuntu下安裝Go語言開發(fā)環(huán)境及編輯器的相關(guān)配置,編輯器方面介紹了包括Vim和Eclipse,需要的朋友可以參考下2016-02-02linux下通過go語言獲得系統(tǒng)進(jìn)程cpu使用情況的方法
這篇文章主要介紹了linux下通過go語言獲得系統(tǒng)進(jìn)程cpu使用情況的方法,實(shí)例分析了Go語言使用linux的系統(tǒng)命令ps來分析cpu使用情況的技巧,需要的朋友可以參考下2015-03-03go開源項(xiàng)目用戶名密碼驗(yàn)證的邏輯鬼才寫法
這篇文章主要為大家介紹了go開源項(xiàng)目中發(fā)現(xiàn)的一個(gè)邏輯鬼才寫法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07