自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go泛型缺陷?Go Stream是如何解決Go不支持泛型方法的問(wèn)題的?

開(kāi)發(fā) 前端
Go-Stream實(shí)現(xiàn)了 java8 stream框架常用的操作,包括 過(guò)濾(Filter),轉(zhuǎn)換一對(duì)一(Map), 轉(zhuǎn)換一對(duì)多(FlatMap),轉(zhuǎn)Map(toMap), 聚合(Reduce),數(shù)據(jù)統(tǒng)計(jì)(Statistic), 分組(GroupingBy)已經(jīng)分組后對(duì)各組排序 等功能,基本滿(mǎn)足99%的開(kāi)發(fā)需求。

大家好,我是Coder哥,最近在用Go語(yǔ)言寫(xiě)項(xiàng)目,也在用泛型解決一些問(wèn)題,但是也發(fā)現(xiàn)了一些問(wèn)題,今天我們就來(lái)聊聊Go語(yǔ)言中泛型函數(shù)和泛型方法。

起因是這樣的,作為java開(kāi)發(fā),發(fā)現(xiàn)Go沒(méi)有類(lèi)似于java8 stream一樣的流處理框架,導(dǎo)致有些邏輯一行能實(shí)現(xiàn)的卻要寫(xiě)好多行來(lái)解決,剛好Go語(yǔ)言也出了泛型,想著用泛型來(lái)寫(xiě)應(yīng)該能和stream一個(gè)效果,于是就有了Go-Stream 這個(gè)項(xiàng)目,在寫(xiě)Go Stream和用的時(shí)候發(fā)現(xiàn)了一個(gè)關(guān)于Golang泛型的一個(gè)很有意思的問(wèn)題,想著拿出來(lái)聊一下。咱還是循序漸進(jìn)的展開(kāi)分析:

  1. go-stream框架的簡(jiǎn)介
  2. 發(fā)現(xiàn)問(wèn)題的過(guò)程。
  3. Go泛型為什么不支持泛型方法?
  4. go-stream框架是怎么解決這個(gè)問(wèn)題的。

go-stream簡(jiǎn)介

Go-Stream實(shí)現(xiàn)了 java8 stream框架常用的操作,包括 過(guò)濾(Filter),轉(zhuǎn)換一對(duì)一(Map), 轉(zhuǎn)換一對(duì)多(FlatMap),轉(zhuǎn)Map(toMap), 聚合(Reduce),數(shù)據(jù)統(tǒng)計(jì)(Statistic), 分組(GroupingBy)已經(jīng)分組后對(duì)各組排序 等功能,基本滿(mǎn)足99%的開(kāi)發(fā)需求。

【Go-Stream】用Go 泛型實(shí)現(xiàn)了個(gè) Java-Stream流處理框架

Go-stream代碼地址:https://github.com/todocoder/go-stream

使用可參閱測(cè)試類(lèi):https://github.com/todocoder/go-stream/blob/master/stream/stream_test.go

require github.com/todocoder/go-stream v1.1.0

圖片圖片

發(fā)現(xiàn)問(wèn)題

科普一下:

方法:是一個(gè)代碼塊,由與對(duì)象關(guān)聯(lián)的名稱(chēng)調(diào)用。

函數(shù):函數(shù)是按名稱(chēng)調(diào)用的代碼,不需要與對(duì)象關(guān)聯(lián)。

寫(xiě)完第一版,基本上能實(shí)現(xiàn)一堆花里胡哨的鏈?zhǔn)秸{(diào)用,看起來(lái)也很絲滑,比如我想對(duì)一個(gè)切片做一系列操作,最后得出結(jié)果,代碼如下:

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 {
      // 過(guò)濾掉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過(guò)濾掉1的值
  2. 通過(guò)Distinct對(duì)itemNum 去重(在第1步的基礎(chǔ)上,下面同理在上一步的基礎(chǔ)上)
  3. 通過(guò)Sorted 按itemNum升序排序
  4. 用Skip 從下標(biāo)為1的元素開(kāi)始
  5. 使用Limit截取排在前6位的元素
  6. 使用Reverse 對(duì)流中元素進(jìn)行返轉(zhuǎn)操作
  7. 使用collect終止操作將最終處理后的數(shù)據(jù)收集到Slice中

看到上面的流程作為一個(gè)多年的Javer感覺(jué)如此絲滑堪稱(chēng)完美,輸出的結(jié)果也是原來(lái)的類(lèi)型TestItem。

但是我們用stream處理問(wèn)題僅僅是因?yàn)橐恍┖?jiǎn)單的單一類(lèi)型的場(chǎng)景么,那肯定不是了,有人說(shuō)我想通過(guò)這個(gè)實(shí)現(xiàn)一些類(lèi)型轉(zhuǎn)換,或者分組,再對(duì)各個(gè)組的列表按某個(gè)字段排列,比如如下的問(wèn)題:

班級(jí)有一組學(xué)號(hào){1,2,3,....,12},對(duì)應(yīng)12個(gè)人的信息在內(nèi)存里面存著

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: "小藍(lán)", 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},
}

我想把這學(xué)號(hào)轉(zhuǎn)換成具體的**Student** 類(lèi),然后過(guò)濾掉**Score**為 1的,并且再按評(píng)分 Score分組,最后對(duì)分好后的各組按照Age 降序排列,按最初v1.0.*版本的代碼是這樣的:

// v1.0.* 的代碼這樣實(shí)現(xiàn)
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 {
  // 這里需要強(qiáng)轉(zhuǎn)
  tempS := s.(Student)
  // 過(guò)濾掉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)

上面這個(gè)代碼有個(gè)問(wèn)題是 經(jīng)過(guò)Map轉(zhuǎn)換后會(huì)丟失類(lèi)型需要用 any 接收,在用的時(shí)候需要強(qiáng)轉(zhuǎn)成目標(biāo)類(lèi)型,并且最后得到res 的結(jié)果是 any類(lèi)型的,用的時(shí)候也需要轉(zhuǎn)換成目標(biāo)類(lèi)型,這樣用起來(lái)非常麻煩,但是如果按這樣的流式處理,這個(gè)問(wèn)題不能避免。因?yàn)楣俜矫鞔_說(shuō)明,目前Go語(yǔ)言不支持泛型方法

如果支持泛型方法,按找目前的編譯機(jī)制,可能需要修改編譯器而且會(huì)比較復(fù)雜

為什么Go泛型不好實(shí)現(xiàn)泛型方法?

有興趣的可以查看官方說(shuō)明:https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#no-parameterized-methods

如果支持泛型方法,考慮下面一個(gè)例子,一共有四個(gè)package:

package p1
// S 是一個(gè)普通的struct,但是包含一個(gè)泛型方法Identity.
type S struct{}
// Identity 一個(gè)泛型方法,支持任意類(lèi)型.
func (S) Identity[T any](v T) T { return v }
package p2
// HasIdentity 定義了一個(gè)接口,支持任意實(shí)現(xiàn)了泛型方法Identity的類(lèi)型.
type HasIdentity interface {
 Identity[T any](T) T
}
package p3
import "p2"
// CheckIdentity 是一個(gè)普通函數(shù),檢查實(shí)參是不是實(shí)現(xiàn)了HasIdentity接口,如果是,則調(diào)用這個(gè)接口的泛型方法Identity.
func CheckIdentity(v interface{}) {
 if vi, ok := v.(p2.HasIdentity); ok {
  if got := vi.Identity[int](0); got != 0 {
   panic(got)
  }
 }
}
package p4
import (
 "p1"
 "p3"
)
// CheckSIdentity 傳參S給CheckIdentity.
func CheckSIdentity() {
 p3.CheckIdentity(p1.S{})
}

作為一個(gè)多年用Java的人,一切看起來(lái)都沒(méi)有問(wèn)題,但是問(wèn)題是package p3不知道p1.S類(lèi)型,整個(gè)程序中如果也沒(méi)有其它地方調(diào)用p1.S.Identity,依照現(xiàn)在的Go編譯器的實(shí)現(xiàn),是沒(méi)有辦法為p1.S.Identity[int]生成對(duì)應(yīng)的代碼的。

是的,如果go編譯器做的比較復(fù)雜,在編譯的時(shí)候這個(gè)場(chǎng)景是可以識(shí)別出來(lái)的,但是它需要遍歷整體的程序調(diào)用鏈以便生成全部可能的泛型方法,對(duì)編譯時(shí)間和編譯器復(fù)雜性帶來(lái)很大的調(diào)整。另外一點(diǎn),如果代碼中通過(guò)反射調(diào)用的話(huà),編譯器可能會(huì)遺漏一些泛型方法的實(shí)現(xiàn),這就很要命了。

如果在運(yùn)行時(shí)實(shí)現(xiàn)呢?就需要JIT或者反射等技術(shù),這會(huì)造成運(yùn)行時(shí)性能的下降。

很難實(shí)現(xiàn)?。咳绻?guī)定泛型方法不能實(shí)現(xiàn)接口呢?那么這類(lèi)的泛型方法的存在的意義是什么呢?

所以目前沒(méi)有太好的手段去實(shí)現(xiàn)泛型方法,暫時(shí)擱置了。

期待后面的版本加上。

問(wèn)題是發(fā)現(xiàn),但是要怎么解決這個(gè)問(wèn)題呢,就是我想直接輸出可用的類(lèi)型,而不是any,因?yàn)樗闷饋?lái)實(shí)在是太麻煩了

go-stream框架是怎么處理這樣的場(chǎng)景的呢

之前用過(guò)python 的 groupby 和map, python是這么做的

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

它是把數(shù)組作為groupby的方法傳過(guò)去,后面是我們的操作,那我們是不是也可以用類(lèi)似這樣的方式來(lái)實(shí)現(xiàn)呢?剛好Go語(yǔ)言支持泛型函數(shù),就開(kāi)搞,于是就有了Go-Stream v1.1.0版了,加了幾個(gè)泛型轉(zhuǎn)換函數(shù),API如下:

轉(zhuǎn)換函數(shù)

通過(guò)這幾個(gè)函數(shù)你可以實(shí)現(xiàn)類(lèi)型轉(zhuǎn)換,分組,flatmap 等處理。

注意:這幾個(gè)函數(shù)非常有用,也是最常用的,由于Go語(yǔ)言泛型的局限性,Go語(yǔ)言方法不支持自己獨(dú)立的泛型,所以導(dǎo)致用Stream中的方法轉(zhuǎn)換只能用 interface{} 代替,這樣會(huì)有個(gè)非常麻煩的問(wèn)題就是,轉(zhuǎn)換后用的時(shí)候必須得強(qiáng)轉(zhuǎn)才能用,所以我把這些寫(xiě)成轉(zhuǎn)換函數(shù),就不會(huì)受制于類(lèi)(struct) 的泛型了。

API

功能說(shuō)明

Map()

類(lèi)型轉(zhuǎn)換(優(yōu)點(diǎn):和上面的Map不一樣的是,這里轉(zhuǎn)換后可以直接使用,不需要強(qiáng)轉(zhuǎn))

FlatMap()

按照條件將已有元素轉(zhuǎn)換為另一個(gè)對(duì)象類(lèi)型,一對(duì)多邏輯,即原來(lái)一個(gè)元素對(duì)象可能會(huì)轉(zhuǎn)換為1個(gè)或者多個(gè)新類(lèi)型的元素,返回新的stream流(優(yōu)點(diǎn):同Map)

GroupingBy()

對(duì)元素進(jìn)行逐個(gè)遍歷,然后執(zhí)行給定的處理邏輯

Collect()

將流轉(zhuǎn)換為指定的類(lèi)型,通過(guò)collectors.Collector進(jìn)行指定(優(yōu)點(diǎn):轉(zhuǎn)換后的類(lèi)型可以直接使用,無(wú)需強(qiáng)轉(zhuǎn))

通過(guò)這幾個(gè)函數(shù)實(shí)現(xiàn)上面的分組轉(zhuǎn)換功能要怎么操作呢?

V1.1.0 版本的實(shí)現(xiàn)

// v1.1.* 的代碼這樣實(shí)現(xiàn)
res := GroupingBy(Map(Of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), func(n int) Student {
  // 注意 這里的返回類(lèi)型可以是目標(biāo)類(lèi)型了
  return studentMap[n]
 }).Filter(func(s Student) bool {
  // 這里過(guò)濾也不需要轉(zhuǎn)換類(lèi)型
  // 過(guò)濾掉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)

圖片圖片

可以看到,中間處理的時(shí)候不用轉(zhuǎn)換,結(jié)果也都是強(qiáng)類(lèi)似的。

res 類(lèi)型:map[int] []Student  返回值的類(lèi)型我們可以直接用不用轉(zhuǎn)換。

雖然我們不能流式的處理不同的類(lèi)型,好在用泛型函數(shù)也能解決,期待官方后續(xù)的版本支持泛型方法,stream處理列表真的非常絲滑,用過(guò)的都說(shuō)好。。哈哈哈。。。

最后

作為一個(gè)Java開(kāi)發(fā),用習(xí)慣了Stream操作,在網(wǎng)上也沒(méi)找到合適的輕量的stream框架,也不知道后續(xù)官方是否會(huì)出,在這之前,就只能先自己實(shí)現(xiàn)了,后面遇到復(fù)雜的處理流程會(huì)持續(xù)的更新到上面除了除了倉(cāng)庫(kù)首頁(yè)README里面的功能,還有并行流處理,數(shù)據(jù)的統(tǒng)計(jì),支持各種分組,轉(zhuǎn)換等等,有興趣可以自行查看體驗(yàn)測(cè)試類(lèi):stream_test

有什么問(wèn)題可以在github上提issues 留言或者公號(hào)搜:todocoder,看到后第一時(shí)間回復(fù),感謝大家的支持!

責(zé)任編輯:武曉燕 來(lái)源: TodoCoder
相關(guān)推薦

2021-09-29 18:17:30

Go泛型語(yǔ)言

2024-10-28 00:40:49

Go語(yǔ)法版本

2021-11-27 22:20:13

SlicesGo泛型

2022-03-28 13:34:26

Go泛型部署泛型

2023-11-03 14:02:04

Go切片泛型庫(kù)

2022-04-15 09:55:59

Go 泛型Go 程序函數(shù)

2021-12-05 23:45:23

Go泛型Maps

2021-12-15 10:23:56

Go 1.18 Bet語(yǔ)言泛型

2021-12-28 07:20:44

泛型Go場(chǎng)景

2021-12-30 18:34:29

緩存GoSinglefligh

2021-11-01 12:41:39

Go

2022-03-18 18:00:00

編程語(yǔ)言泛型支持模糊測(cè)試

2021-12-01 08:29:17

Go泛型Maps

2022-03-29 11:48:40

Go泛型測(cè)試

2021-10-18 10:53:26

Go 代碼技術(shù)

2021-08-22 17:18:58

Go代碼泛型代碼

2022-07-12 06:17:43

GoogleGolang開(kāi)發(fā)工作

2022-05-06 09:22:25

Go泛型

2021-12-13 08:52:42

Go 泛型

2021-01-14 05:20:48

Go語(yǔ)言泛型
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)