当前位置:首页 > 科技  > 软件

Go泛型缺陷?Go Stream是如何解决Go不支持泛型方法的问题的?

来源: 责编: 时间:2023-11-30 09:29:43 156观看
导读大家好,我是Coder哥,最近在用Go语言写项目,也在用泛型解决一些问题,但是也发现了一些问题,今天我们就来聊聊Go语言中泛型函数和泛型方法。起因是这样的,作为java开发,发现Go没有类似于java8 stream一样的流处理框架,导致有些

大家好,我是Coder哥,最近在用Go语言写项目,也在用泛型解决一些问题,但是也发现了一些问题,今天我们就来聊聊Go语言中泛型函数和泛型方法。Ii328资讯网——每日最新资讯28at.com

起因是这样的,作为java开发,发现Go没有类似于java8 stream一样的流处理框架,导致有些逻辑一行能实现的却要写好多行来解决,刚好Go语言也出了泛型,想着用泛型来写应该能和stream一个效果,于是就有了Go-Stream 这个项目,在写Go Stream和用的时候发现了一个关于Golang泛型的一个很有意思的问题,想着拿出来聊一下。咱还是循序渐进的展开分析:Ii328资讯网——每日最新资讯28at.com

  1. go-stream框架的简介
  2. 发现问题的过程。
  3. Go泛型为什么不支持泛型方法?
  4. go-stream框架是怎么解决这个问题的。

go-stream简介

Go-Stream实现了 java8 stream框架常用的操作,包括 过滤(Filter),转换一对一(Map), 转换一对多(FlatMap),转Map(toMap), 聚合(Reduce),数据统计(Statistic), 分组(GroupingBy)已经分组后对各组排序 等功能,基本满足99%的开发需求。Ii328资讯网——每日最新资讯28at.com

【Go-Stream】用Go 泛型实现了个 Java-Stream流处理框架Ii328资讯网——每日最新资讯28at.com

Go-stream代码地址:https://github.com/todocoder/go-streamIi328资讯网——每日最新资讯28at.com

使用可参阅测试类:https://github.com/todocoder/go-stream/blob/master/stream/stream_test.goIi328资讯网——每日最新资讯28at.com

require github.com/todocoder/go-stream v1.1.0Ii328资讯网——每日最新资讯28at.com

图片图片Ii328资讯网——每日最新资讯28at.com

发现问题

科普一下:Ii328资讯网——每日最新资讯28at.com

方法:是一个代码块,由与对象关联的名称调用。Ii328资讯网——每日最新资讯28at.com

函数:函数是按名称调用的代码,不需要与对象关联。Ii328资讯网——每日最新资讯28at.com

写完第一版,基本上能实现一堆花里胡哨的链式调用,看起来也很丝滑,比如我想对一个切片做一系列操作,最后得出结果,代码如下:Ii328资讯网——每日最新资讯28at.com

func TestStream(t *testing.T) {  items := []TestItem{      {itemNum: 7, itemValue: "item7"},{itemNum: 6, itemValue: "item6"},      {itemNum: 1, itemValue: "item1"},{itemNum: 2, itemValue: "item2"},      {itemNum: 3, itemValue: "item3"},{itemNum: 4, itemValue: "item4"},      {itemNum: 5, itemValue: "item5"},{itemNum: 5, itemValue: "item5"},      {itemNum: 5, itemValue: "item5"},{itemNum: 8, itemValue: "item8"},    }    res := Of(items...).Filter(func(item TestItem) bool {      // 过滤掉1的值      return item.itemNum != 4    }).Distinct(func(item TestItem) any {      // 按itemNum 去重      return item.itemNum    }).Sorted(func(a, b TestItem) bool {      // 按itemNum升序排序      return a.itemNum < b.itemNum    }).Skip(1).Limit(6).Reverse().ToSlice()    fmt.Println(res)}
  1. 使用Filter过滤掉1的值
  2. 通过Distinct对itemNum 去重(在第1步的基础上,下面同理在上一步的基础上)
  3. 通过Sorted 按itemNum升序排序
  4. 用Skip 从下标为1的元素开始
  5. 使用Limit截取排在前6位的元素
  6. 使用Reverse 对流中元素进行返转操作
  7. 使用collect终止操作将最终处理后的数据收集到Slice中

看到上面的流程作为一个多年的Javer感觉如此丝滑堪称完美,输出的结果也是原来的类型TestItem。Ii328资讯网——每日最新资讯28at.com

但是我们用stream处理问题仅仅是因为一些简单的单一类型的场景么,那肯定不是了,有人说我想通过这个实现一些类型转换,或者分组,再对各个组的列表按某个字段排列,比如如下的问题:Ii328资讯网——每日最新资讯28at.com

班级有一组学号{1,2,3,....,12},对应12个人的信息在内存里面存着Ii328资讯网——每日最新资讯28at.com

type Student struct { Num   int Score int Age   int Name  string}studentMap := map[int]Student{  1: {Num: 1, Name: "小明", Score: 3, Age: 26},  2: {Num: 2, Name: "小红", Score: 4, Age: 27},  3: {Num: 3, Name: "小李", Score: 5, Age: 24},  4: {Num: 4, Name: "老王", Score: 1, Age: 23},  5: {Num: 5, Name: "小王", Score: 2, Age: 24},  6: {Num: 6, Name: "小绿", Score: 2, Age: 24},  7: {Num: 7, Name: "小蓝", Score: 3, Age: 29},  8: {Num: 8, Name: "小橙", Score: 3, Age: 30},  9: {Num: 9, Name: "小黄", Score: 4, Age: 29},  10: {Num: 10, Name: "小黑", Score: 5, Age: 15},  11: {Num: 11, Name: "小紫", Score: 3, Age: 15},  12: {Num: 12, Name: "小刘", Score: 2, Age: 15},}

我想把这学号转换成具体的**Student** 类,然后过滤掉**Score**为 1的,并且再按评分 Score分组,最后对分好后的各组按照Age 降序排列,按最初v1.0.*版本的代码是这样的:Ii328资讯网——每日最新资讯28at.com

// v1.0.* 的代码这样实现res := Of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12).Map(func(n int) any {  return studentMap[n] }).Filter(func(s any) bool {  // 这里需要强转  tempS := s.(Student)  // 过滤掉1的  return tempS.Score != 1 }).Collect(collectors.GroupingBy(func(t any) int {  return t.(Student).Score }, func(t any) any {  return t }, func(t1 []any) {  sort.Slice(t1, func(i, j int) bool {   return t1[i].(Student).Age < t1[j].(Student).Age  }) })) println(res)

上面这个代码有个问题是 经过Map转换后会丢失类型需要用 any 接收,在用的时候需要强转成目标类型,并且最后得到res 的结果是 any类型的,用的时候也需要转换成目标类型,这样用起来非常麻烦,但是如果按这样的流式处理,这个问题不能避免。因为官方明确说明,目前Go语言不支持泛型方法Ii328资讯网——每日最新资讯28at.com

如果支持泛型方法,按找目前的编译机制,可能需要修改编译器而且会比较复杂Ii328资讯网——每日最新资讯28at.com

为什么Go泛型不好实现泛型方法?

有兴趣的可以查看官方说明:https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#no-parameterized-methodsIi328资讯网——每日最新资讯28at.com

如果支持泛型方法,考虑下面一个例子,一共有四个package:Ii328资讯网——每日最新资讯28at.com

package p1// S 是一个普通的struct,但是包含一个泛型方法Identity.type S struct{}// Identity 一个泛型方法,支持任意类型.func (S) Identity[T any](v T) T { return v }
package p2// HasIdentity 定义了一个接口,支持任意实现了泛型方法Identity的类型.type HasIdentity interface { Identity[T any](T) T}
package p3import "p2"// CheckIdentity 是一个普通函数,检查实参是不是实现了HasIdentity接口,如果是,则调用这个接口的泛型方法Identity.func CheckIdentity(v interface{}) { if vi, ok := v.(p2.HasIdentity); ok {  if got := vi.Identity[int](0); got != 0 {   panic(got)  } }}
package p4import ( "p1" "p3")// CheckSIdentity 传参S给CheckIdentity.func CheckSIdentity() { p3.CheckIdentity(p1.S{})}

作为一个多年用Java的人,一切看起来都没有问题,但是问题是package p3不知道p1.S类型,整个程序中如果也没有其它地方调用p1.S.Identity,依照现在的Go编译器的实现,是没有办法为p1.S.Identity[int]生成对应的代码的。Ii328资讯网——每日最新资讯28at.com

是的,如果go编译器做的比较复杂,在编译的时候这个场景是可以识别出来的,但是它需要遍历整体的程序调用链以便生成全部可能的泛型方法,对编译时间和编译器复杂性带来很大的调整。另外一点,如果代码中通过反射调用的话,编译器可能会遗漏一些泛型方法的实现,这就很要命了。Ii328资讯网——每日最新资讯28at.com

如果在运行时实现呢?就需要JIT或者反射等技术,这会造成运行时性能的下降。Ii328资讯网——每日最新资讯28at.com

很难实现啊?如果规定泛型方法不能实现接口呢?那么这类的泛型方法的存在的意义是什么呢?Ii328资讯网——每日最新资讯28at.com

所以目前没有太好的手段去实现泛型方法,暂时搁置了。Ii328资讯网——每日最新资讯28at.com

期待后面的版本加上。Ii328资讯网——每日最新资讯28at.com

问题是发现,但是要怎么解决这个问题呢,就是我想直接输出可用的类型,而不是any,因为它用起来实在是太麻烦了Ii328资讯网——每日最新资讯28at.com

go-stream框架是怎么处理这样的场景的呢

之前用过python 的 groupby 和map, python是这么做的Ii328资讯网——每日最新资讯28at.com

student_group = groupby(stus, key=lambda s: s['score'])

它是把数组作为groupby的方法传过去,后面是我们的操作,那我们是不是也可以用类似这样的方式来实现呢?刚好Go语言支持泛型函数,就开搞,于是就有了Go-Stream v1.1.0版了,加了几个泛型转换函数,API如下:Ii328资讯网——每日最新资讯28at.com

转换函数

通过这几个函数你可以实现类型转换,分组,flatmap 等处理。Ii328资讯网——每日最新资讯28at.com

注意:这几个函数非常有用,也是最常用的,由于Go语言泛型的局限性,Go语言方法不支持自己独立的泛型,所以导致用Stream中的方法转换只能用 interface{} 代替,这样会有个非常麻烦的问题就是,转换后用的时候必须得强转才能用,所以我把这些写成转换函数,就不会受制于类(struct) 的泛型了。Ii328资讯网——每日最新资讯28at.com

APIIi328资讯网——每日最新资讯28at.com

功能说明Ii328资讯网——每日最新资讯28at.com

Map()Ii328资讯网——每日最新资讯28at.com

类型转换(优点:和上面的Map不一样的是,这里转换后可以直接使用,不需要强转)Ii328资讯网——每日最新资讯28at.com

FlatMap()Ii328资讯网——每日最新资讯28at.com

按照条件将已有元素转换为另一个对象类型,一对多逻辑,即原来一个元素对象可能会转换为1个或者多个新类型的元素,返回新的stream流(优点:同Map)Ii328资讯网——每日最新资讯28at.com

GroupingBy()Ii328资讯网——每日最新资讯28at.com

对元素进行逐个遍历,然后执行给定的处理逻辑Ii328资讯网——每日最新资讯28at.com

Collect()Ii328资讯网——每日最新资讯28at.com

将流转换为指定的类型,通过collectors.Collector进行指定(优点:转换后的类型可以直接使用,无需强转)Ii328资讯网——每日最新资讯28at.com

通过这几个函数实现上面的分组转换功能要怎么操作呢?Ii328资讯网——每日最新资讯28at.com

V1.1.0 版本的实现Ii328资讯网——每日最新资讯28at.com

// v1.1.* 的代码这样实现res := GroupingBy(Map(Of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), func(n int) Student {  // 注意 这里的返回类型可以是目标类型了  return studentMap[n] }).Filter(func(s Student) bool {  // 这里过滤也不需要转换类型  // 过滤掉1的  return s.Score != 1 }), func(t Student) int {   // key  return t.Score }, func(t Student) Student {    // v item  return t }, func(t1 []Student) {  // 按年龄降序排列  sort.Slice(t1, func(i, j int) bool {   return t1[i].Age > t1[j].Age  }) }) println(res)

图片图片Ii328资讯网——每日最新资讯28at.com

可以看到,中间处理的时候不用转换,结果也都是强类似的。Ii328资讯网——每日最新资讯28at.com

res 类型:map[int] []Student  返回值的类型我们可以直接用不用转换。Ii328资讯网——每日最新资讯28at.com

虽然我们不能流式的处理不同的类型,好在用泛型函数也能解决,期待官方后续的版本支持泛型方法,stream处理列表真的非常丝滑,用过的都说好。。哈哈哈。。。Ii328资讯网——每日最新资讯28at.com

最后

作为一个Java开发,用习惯了Stream操作,在网上也没找到合适的轻量的stream框架,也不知道后续官方是否会出,在这之前,就只能先自己实现了,后面遇到复杂的处理流程会持续的更新到上面除了除了仓库首页README里面的功能,还有并行流处理,数据的统计,支持各种分组,转换等等,有兴趣可以自行查看体验测试类:stream_testIi328资讯网——每日最新资讯28at.com

有什么问题可以在github上提issues 留言或者公号搜:todocoder,看到后第一时间回复,感谢大家的支持!Ii328资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-35320-0.htmlGo泛型缺陷?Go Stream是如何解决Go不支持泛型方法的问题的?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 解密 Python 如何调用 Rust 编译生成的动态链接库

下一篇: 用200行代码写一个H5小游戏

标签:
  • 热门焦点
Top