Golang拾遗之自定义类型和方法集详解
积累知识,胜过积蓄金银!毕竟在##column_title##开发的过程中,会遇到各种各样的问题,往往都是一些细节知识点还没有掌握好而导致的,因此基础知识点的积累是很重要的。下面本文《Golang拾遗之自定义类型和方法集详解》,就带大家讲解一下类型、方法集、自定义知识点,若是你对本文感兴趣,或者是想搞懂其中某个知识点,就请你继续往下看吧~
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))
}
请问上述代码会输出什么:
1.编译错误
2.运行时panic
3.{}
4."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上的那些方法
还有最重要的,如果两个类型名字不同,即使它们的结构完全相同,也是两个不同的类型。
这些边边角角的知识很容易被遗忘,但还是有机会在工作中遇到的,记牢了可以省很多事。
今天关于《Golang拾遗之自定义类型和方法集详解》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于golang的内容请关注golang学习网公众号!
数据库设计工具MySQL Workbench使用教程(超级详细!)
- 上一篇
- 数据库设计工具MySQL Workbench使用教程(超级详细!)
- 下一篇
- Navicat连接不上MySQL的问题解决
-
- 勤奋的小鸭子
- 真优秀,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢作者分享技术贴!
- 2023-03-03 00:14:02
-
- 可爱的飞鸟
- 这篇博文太及时了,太全面了,很好,收藏了,关注作者大大了!希望作者大大能多写Golang相关的文章。
- 2023-03-02 09:55:05
-
- Golang · Go教程 | 4小时前 |
- Go HTML 模板怎么复用页头页脚和基础布局
- 193浏览 收藏
-
- Golang · Go教程 | 5小时前 |
- Go 怎么解析源码并列出函数名和参数
- 119浏览 收藏
-
- Golang · Go教程 | 5小时前 | go · 工程实践 · go generate · Go 代码生成 go generate
- Go 怎么用 go generate 自动生成重复代码
- 136浏览 收藏
-
- Golang · Go教程 | 5小时前 | go · Go Modules · 构建部署 · go mod vendor Go 离线构建 vendor/modules.txt
- Go 项目怎么打包依赖以支持离线构建
- 258浏览 收藏
-
- Golang · Go教程 | 6小时前 |
- Go 怎么生成测试覆盖率文件并定位未覆盖函数
- 113浏览 收藏
-
- Golang · Go教程 | 6小时前 |
- Go 单元测试怎么替换 HTTP 客户端而不访问网络
- 165浏览 收藏
-
- Golang · Go教程 | 6小时前 |
- Go 多个测试怎么共享一次初始化并统一清理
- 398浏览 收藏
-
- Golang · Go教程 | 6小时前 |
- Go 文件系统代码怎么用内存文件做单元测试
- 278浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 164次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 88次使用
-
- LangGPT
- LangGPT是一种受编程语言启发的结构化提示词设计工具,提供双层框架、模块化模板及变量功能,帮助用户高效编写高质量Prompt。该项目已在GitHub免费开源,适用于内容创作、编程辅助等多场景。
- 26次使用
-
- ClickPrompt
- ClickPrompt是一款专为AI提示词编写者设计的开源在线工具,支持Stable Diffusion绘图、ChatGPT对话及GitHub Copilot代码辅助。提供Prompt自动生成、一键运行、社区分享及可视化优化功能,帮助用户高效获取精准AI输出。
- 53次使用
-
- PromptHero
- PromptHero是专业的AI提示词搜索引擎与优化平台,支持Stable Diffusion、Midjourney等主流模型。提供海量提示词库、分类搜索、在线课程及社区互动,助力用户高效生成高质量AI图像与文本。
- 38次使用
-
- 一文带你了解Go语言中的类型断言和类型转换
- 2022-12-28 196浏览
-
- goalng 结构体 方法集 接口实例详解
- 2022-12-30 250浏览
-
- Golang中map数据类型的使用方法
- 2022-12-30 443浏览
-
- 一文详解Golang中的切片数据类型
- 2022-12-31 171浏览
-
- Go 编程复杂数据类型 Map
- 2023-01-01 306浏览

