golang拾遺主要是用來記錄一些遺忘了的、平時從沒注意過的golang相關知識,
很久沒更新了,我們先以一個謎題開頭練練手:
package main
import (
"encoding/json"
"fmt"
"time"
)
type MyTime time.Time
func main() {
myTime := MyTime(time.Now()) // 假設獲得的時間是 2022年7月20日20:30:00,時區UTC+8
res, err := json.Marshal(myTime)
if err != nil {
panic(err)
}
fmt.Println(string(res))
}
請問上述代碼會輸出什么:
- 編譯錯誤
- 運行時panic
- {}
- "2022-07-20T20:30:00.135693011+08:00"
很多人一定會選4吧,然而答案是3:
$ go run customize.go
{}
是不是很意外,MyTime就是time.Time,理論上應該也實作了json.Marshaler,為什么輸出的是空的呢?
實際上這是最近某個群友遇到的問題,乍一看像是golang的bug,但其實還是沒掌握語言的基本規則,
在深入下去之前,我們先問自己兩個問題:
- MyTime 真的是 Time 型別嗎?
- MyTime 真的實作了
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是一樣的,也是兩個不同的型別,
那么問題1的答案就知道了,顯然MyTime不是time.Time,
既然MyTime不是Time,那它是否能用Time型別的method呢?畢竟MyTime的基底型別是Time呀,我們寫段代碼驗證下:
package main
import (
"fmt"
"time"
)
type MyTime time.Time
func main() {
myTime := MyTime(time.Now()) // 假設獲得的時間是 2022年7月20日20:30:00,時區UTC+8
res, err := myTime.MarsharlJSON()
if err != nil {
panic(err)
}
fmt.Println(string(res))
}
運行結果:
# command-line-arguments
./checkoutit.go:12:24: myTime.MarsharlJSON undefined (type MyTime has no field or method MarsharlJSON)
現在問題2也有答案了:MyTime沒有實作json.Marshaler,
那么對于一個沒有實作json.Marshaler的型別,json是怎么序列化的呢?這里就不賣關子了,檔案里有寫,對于沒實作Marshaler的型別,默認的流程使用反射獲取所有非export的欄位,然后依次序列化,我們再看看time的結構:
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
}
里面都是非公開欄位,所以直接序列化后整個結果就是{},當然,Time型別自己重新實作了json.Marshaler,所以可以正常序列化成我們期望的值,
而我們的MyTime沒有實作整個介面,所以走了默認的序列化流程,
所以我們可以得出一個重要的結論:從某個型別A派生出的型別B,B并不能獲得A的方法集中的任何一個,
想要B擁有A的所有方法也不是不行,但得和type B A這樣的形式說再見了,
方法一是使用type alias:
- type MyTime time.Time
+ type MyTime = time.Time
func main() {
- myTime := MyTime(time.Now()) // 假設獲得的時間是 2022年7月20日20:30:00,時區UTC+8
+ var myTime MyTime = time.Now() // 假設獲得的時間是 2022年7月20日20:30:00,時區UTC+8
res, err := json.Marshal(myTime)
if err != nil {
panic(err)
}
fmt.Println(string(res))
}
型別別名自如其名,就是創建了一個型別A的別名而沒有定義任何新型別(注意那兩行改動),現在MyTime就是Time了,自然也可以直接利用Time的MarshalJSON,
方法二,使用內嵌型別:
- type MyTime time.Time
+ type MyTime struct {
+ time.Time
+ }
func main() {
- myTime := MyTime(time.Now()) // 假設獲得的時間是 2022年7月20日20:30:00,時區UTC+8
+ myTime := MyTime{time.Now}
res, err := myTime.MarsharlJSON()
if err != nil {
panic(err)
}
fmt.Println(string(res))
}
通過將Time嵌入MyTime,MyTime也可以獲得Time型別的方法集,更具體的可以看我之前寫的另一篇文章:golang拾遺:嵌入型別
如果我實在需要派生出一種新的型別呢,通常在我們寫一個通用模塊的時候需要隱藏實作的細節,所以想要對原始型別進行一定的包裝,這時該怎么辦呢?
實際上我們可以讓MyTime重新實作json.Marshaler:
type MyTime time.Time
func (m MyTime) MarshalJSON() ([]byte, error) {
// 我圖方便就直接復用Time的了
return time.Time(m).MarshalJSON()
}
func main() {
myTime := MyTime(time.Now()) // 假設獲得的時間是 2022年7月20日20:30:00,時區UTC+8
res, err := myTime.MarsharlJSON()
if err != nil {
panic(err)
}
fmt.Println(string(res))
}
這么做看上去違反了DRY原則,其實未必,這里只是示例寫的爛而已,真實場景下往往對派生出來的自定義型別進行一些定制,因此序列化函式里會有額外的一些操作,這樣就和DRY不沖突了,
不管哪一種方案,都可以解決問題,根據自己的實際需求做選擇即可,
總結
總結一下,一個派生自A的自定義型別B,它的方法集中的方法只有兩個來源:
- 直接定義在B上的那些方法
- 作為嵌入型別包含在B里的其他型別的方法
而A的方法是不存在在B中的,
如果是從一個匿名型別派生的自定義型別B(type B struct {a, b int}),那么B的方法集中的方法只有一個來源:
- 直接定義在B上的那些方法
還有最重要的,如果兩個型別名字不同,即使它們的結構完全相同,也是兩個不同的型別,
這些邊邊角角的知識很容易被遺忘,但還是有機會在作業中遇到的,記牢了可以省很多事,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/499852.html
標籤:其他
上一篇:SpringBoot(15)ORM ( Object Relation Mapping )和JPA—Java持久層API
