Hello,大家好,我是程序员Karos,作为一位从Java语言转向Go语言学习与应用的程序员,我深切地体会到了这两种编程语言的设计哲学、特性与实践方式之间的差异,本期给大家带来的是:如何从Java快速转型到Golang,Go泛型的Instantiation Cycle坑点,更多内容可以参考我的上一篇文章: https://cloud.tencent.com/developer/article/2465550。
分为如下几个部分:
// 这里是当时出错的地方,这个函数是不能用的,当时算法设计有点问题。
type Option[T any] struct {
opt T
}
type Options[T any] []Option[T]
type Stream[T any, Slice ~[]T] struct {
options *Options[T]
source *Slice
parallel bool
}
func NewOptionsStream[Opt any, Opts Options[Opt]](source *[]Opts) *Stream[Options[Opt], []Options[Opt]] {
resOptions := make(Options[Options[Opt]], 0)
sourceList := make([]Options[Opt], 0)
for i := 0; i < len(*source); i++ {
t := Options[Opt]((*source)[i])
resOptions = append(resOptions, Option[Options[Opt]]{opt: t})
sourceList = append(sourceList, t)
}
return &Stream[Options[Opt], []Options[Opt]]{
options: (*Options[Options[Opt]])(&resOptions),
source: &sourceList,
}
}
// 这下面的代码略看即可,具体的代码已经修改了,这是当时找到报错时候的版本,部分逻辑有问题,下一期出Stream流的复刻!
func (s *Stream[T, Slice]) OrderBy(desc bool, orderBy algorithm.HashComputeFunction) *Stream[T, Slice] {
size := len(*s.options)
data := make([]Options[T], 0)
// opt opt opt opt -> opts opts
lists.Partition(*s.options, optional.IsTrue(s.parallel, size<<4^0x1, 1)).ForEach(func(pos int, options []Option[T]) error {
data = append(data, options)
return nil
}, false, nil)
res := NewOptionsStream[T, Options[T]](&data).Map(func(options Options[T]) any {
sort.SliceStable(options, func(i, j int) bool {
if desc {
return orderBy((options)[i].opt) > orderBy((options)[j].opt)
} else {
return orderBy((options)[i].opt) < orderBy((options)[j].opt)
}
})
return options
}).Map(func(v any) any {
i := v.([]Option[T])
ress := NewOptionStream(&i).Map(func(item Option[T]) any {
return item.opt
}).ToList()
return ress
}).ToList()
re := make([]T, len(res))
result := ToStream(&res).Reduce(re, func(cntValue any, nxt any) any {
ts := cntValue.([]T)
nxts := nxt.([]T)
if len(ts) == 0 {
return nxts
}
if len(nxts) == 0 {
return ts
}
lenRe := len(re)
lenNxt := len(nxts)
for i := 0; i < min(lenRe, lenNxt); i++ {
if orderBy(ts[i]) > orderBy(nxts[i]) {
if desc {
re = append(re, ts[i])
} else {
re = append(re, nxts[i])
}
} else {
if desc {
re = append(re, ts[i])
} else {
re = append(re, nxts[i])
}
}
}
return re
}, nil).(Slice)
stream := ToStream(&result)
return stream
}
../../container/stream/stream.go:14:13: instantiation cycle:
../../container/stream/stream.go:30:66: T instantiated as T
../../container/stream/stream.go:219:17: T instantiated as []Option[T]
这里的具体问题就出在NewOptionsStream``函数
// 传入source的实际类型是 *[]Options[T],这个时候Opt是T
// 这里 Opt 是 Options[Opt] 的类型参数
// Stream 的类型参数又是 Options[Opt]
// 造成了类型参数的互相依赖
func NewOptionsStream[Opt any, Opts Options[Opt]](source *[]Opts) *Stream[Options[Opt], []Options[Opt]] {
resOptions := make(Options[Options[Opt]], 0)
sourceList := make([]Options[Opt], 0)
for i := 0; i < len(*source); i++ {
t := Options[Opt]((*source)[i])
resOptions = append(resOptions, Option[Options[Opt]]{opt: t})
sourceList = append(sourceList, t)
}
return &Stream[Options[Opt], []Options[Opt]]{
options: (*Options[Options[Opt]])(&resOptions),
source: &sourceList,
}
}
其实还是很难看,拿来看个简洁版本的:
type slice[E any] struct{ elems []E }
func (s slice[E]) chunk(n int) slice[[]E] {
return slice[[]E]{elems: [][]E{}}
}
func Test_123(t *testing.T) {
chunks := slice[int]{
elems: []int{1, 2, 3},
}.chunk(2)
for _, chunk := range chunks.elems {
t.Log(chunk)
}
}
# github.com/karosown/katool/test [github.com/karosown/katool/test.test]
./other_test.go:7:12: instantiation cycle:
./other_test.go:9:38: E instantiated as []E
slice 结构体包含一个 elems 字段,其类型是 []E,也就是一个元素类型为 E 的切片。
chunk 方法的返回值类型是 slice[[]E],也就是一个元素类型为 []E 的切片。
这就造成了一个循环:slice 依赖 []E,而 []E 又被用作 slice 的元素类型。
(具体的原因就是因为泛型采用混合方法单态化,每一个类型都要生存一个chunk函数,我们对slice[int]生成后,还需要对slice[[]int]生成,然后要对slice[[][]int]生成)
可以看看github:https://github.com/golang/go/issues/65366
随后,当你对Go有了更深入的掌握,我推荐你尝试去实现一个简单的高并发服务,比如一个具有用户登录功能的聊天室,这个过程中可以更加全面地应用到协程、Channel以及Go的一些内置库,更能发现语言层面的实用性与不足。
学习Go的过程中,我们会发现,它的"简洁"哲学不仅体现在语言语法上,还体现在工具链的设计里。Go原生提供了一整套完整的工具链,包括编码规范检查、单元测试框架、性能分析工具和文档生成等,这些工具大大简化了开发人员任务链的复杂性。例如,在编写代码时,我们无需再引入第三方的lint工具,因为Go命令行自带go fmt
,可以保证团队内代码风格的统一性。而go test
不仅可以跑单元测试,还可以经过额外配置,进行性能基准测试,甚至生成性能剖析文件。
在团队协作中,Go语言的这些特性优势会更加明显,特别是在项目的迭代速度及代码质量上。通过Go的工具链,团队可以轻松实现从编码、测试到部署的整个CI/CD流程。举个例子,通过go build
和go run
命令快速得到编译后的执行文件,再配合Docker打包和kubernetes部署,整个过程在Go的简洁操作下会让初学者感受到前所未有的流畅性。而实际的性能分析工具如pprof
,也让开发者对程序中的性能瓶颈一目了然,帮助团队做出精确的优化。
在学习完基础语法及工具链后,可以尝试进一步了解Go的中间件生态,例如常见的Gin、Echo等Web框架,这些框架在轻量级和高性能的设计上展现了Go语言的特点。尤其是Gin框架,不仅覆盖了丰富的业务场景,还支持中间件扩展机制,能够高效实现请求处理和路由管理,适合初学者快速上手开发Web服务。
当然,Java有Nacos、Zk等一系列的微服务组件,最后Go一样要学etcd、grpc等等。
好了,本期就到这里,咱们下期再见。
下一期:Java转Go的A型路,让你从弯道超车,早日抓住Golang这一杀器 —— 带你熟悉Go之 Java杀器复刻—Go-Stream
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。