前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >透彻!Java快速转型到Go—Go泛型的Instantiation Cycle坑点

透彻!Java快速转型到Go—Go泛型的Instantiation Cycle坑点

原创
作者头像
Karos
修改2024-12-18 02:50:54
修改2024-12-18 02:50:54
21300
代码可运行
举报
运行总次数:0
代码可运行

Hello,大家好,我是程序员Karos,作为一位从Java语言转向Go语言学习与应用的程序员,我深切地体会到了这两种编程语言的设计哲学、特性与实践方式之间的差异,本期给大家带来的是:如何从Java快速转型到Golang,Go泛型的Instantiation Cycle坑点,更多内容可以参考我的上一篇文章: https://cloud.tencent.com/developer/article/2465550

分为如下几个部分:

  • 第一步,自然是学习Golang的基础语法和特性,这部分内容主要是熟悉Go的内置数据类型、语法规则、控制结构和一些常用的库。其实从Java到Go可能会让人有些落差,因为Go对一些高级特性支持得并没有Java那么好,不过这恰好也能让我们有机会重新审视编程的本质。 例如:
    • 基础内容大家可以看菜鸟教程来看,这个大多数同学都没问题,但是还是有些坑点,并且上手比较慢,给大家推荐一个我在今年年初入门Go的时候(当时为了应付面试)快速学习的视频 https://www.bilibili.com/video/BV15G4y127QN/?spm_id_from=333.337.search-card.all.click&vd_source=39ab311d30f9c989c01184bc337556cf,当时学完之后,自己也是搓了一下默克尔树: https://cloud.tencent.com/developer/article/2397122
    • 在学习Go时,了解它的并发编程模型是关键一步。Go比较亮眼的特点就是Goroutine和Channel,在实际项目中,这两者的组合可以极大提升开发性能。同时学习Go内置的sync包(这个包中有一些线程安全的集合类),对于高并发场景下的变量同步至关重要。
    • 除此之外,还有泛型,在Java中,泛型是通过编译时擦除和运行时动态代理技术来实现的,有效的提高了代码编写的灵活性,而Golang的泛型,类似静态代理,对于每一种实际使用的类型,都会在编译的时候生成对应的方法,当然,在使用过程中,也遇到了一个坑点(这里介绍坑比较大的地方,其他坑点可以参考我的上一篇文章 https://cloud.tencent.com/developer/article/2465550
代码语言:javascript
代码运行次数:0
复制
// 这里是当时出错的地方,这个函数是不能用的,当时算法设计有点问题。
​
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
}
代码语言:javascript
代码运行次数:0
复制
../../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``函数

代码语言:javascript
代码运行次数:0
复制
// 传入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,
    }
}

其实还是很难看,拿来看个简洁版本的:

代码语言:javascript
代码运行次数:0
复制
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)
  }
}
代码语言:javascript
代码运行次数:0
复制
# 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的简洁和高效特点,快速开发出一个轻量级的服务端程序。从数据库读写到接口设计,用Go去实现每一个环节,在实践中体会它所宣扬的“简单”哲学以及偏底层的语言风格。

随后,当你对Go有了更深入的掌握,我推荐你尝试去实现一个简单的高并发服务,比如一个具有用户登录功能的聊天室,这个过程中可以更加全面地应用到协程、Channel以及Go的一些内置库,更能发现语言层面的实用性与不足。

学习Go的过程中,我们会发现,它的"简洁"哲学不仅体现在语言语法上,还体现在工具链的设计里。Go原生提供了一整套完整的工具链,包括编码规范检查、单元测试框架、性能分析工具和文档生成等,这些工具大大简化了开发人员任务链的复杂性。例如,在编写代码时,我们无需再引入第三方的lint工具,因为Go命令行自带go fmt,可以保证团队内代码风格的统一性。而go test不仅可以跑单元测试,还可以经过额外配置,进行性能基准测试,甚至生成性能剖析文件。

在团队协作中,Go语言的这些特性优势会更加明显,特别是在项目的迭代速度及代码质量上。通过Go的工具链,团队可以轻松实现从编码、测试到部署的整个CI/CD流程。举个例子,通过go buildgo 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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档