UTL_MATCH介紹: Oracle的UTL_MATCH包是一個提供字元串匹配和相似度計算功能的工具包。它包含了一系列函數,用於執行字元串比較、相似度計算和模式匹配等操作。 UTL_MATCH包中的函數可以用於以下任務: 字元串相似度計算:UTL_MATCH提供了多個函數來計算字元串之間的相似度, ...
概述
在強類型變成語言中,類型是確定不可變,如函數入參是確定類型、鏈表元素是確定類型,這極大限制了函數功能。也有些解決方案,為每種類型都實現一版函數,會導致大量重覆代碼;使用類型轉換,使用特殊形參(如Object、any),在函數內部判斷並轉換類型後再執行邏輯,導致大量類型轉換的代碼,結構混亂,Java 未支持泛型之前就使用這個套路。最優解決方案是泛型,即類型參數化,編寫函數暫時不確定類型,使用占位符代替,調用時候類型也是通過參數傳遞進去,替換占位符。主流強類型語言都支持泛型,Java、C++、C#等。
千呼萬喚始出來,1.18版本正式增加泛型支持,但是體感貌似一般,使用場景並沒有那麼廣泛,特別是泛型約束能力,相比Java、C++弱了很多。更多是對函數式編程的支持,如slice、map、func等,在面向對象編程支持不夠。不確定後期是否會繼續演變,Go對相容性保持還算可以,後續演變也不用過分擔憂。
基本使用
Go 語言不是主流的面向對象,泛型支持上也有所區別,如Java一切皆為對象,只要確定對象泛型即可統一的泛型模式。Go的每種類型都有區別,可簡單分為兩類,類型泛型:切片泛型、哈希泛型、結構體泛型、基礎類型泛型等;函數泛型:函數泛型、方法泛型等。另外泛型語法不是主流的<>
尖括弧,而使用的[]
中括弧,這不重要,思路都是相似的
類型泛型,風格基本一致,使用type
定義別名,名稱後面緊跟泛型定義,然後是基礎類型。
type MyGen[T any] int
函數泛型,語法也比較類似,函數名稱後面緊跟泛型定義
func MyFun[T any](x T, n int) {
...
}
切片泛型
定義切片泛型。使用type
定了名稱為genSlice
的新類型,底層類型是個切片。緊跟在名稱後面的[T any]
就是泛型定義,T
表示泛型占位符,any
表示泛型約束,可以看到切片的元素類型也使用了T
占位符
type genSlice[T any] []T
any
是標準庫提供的特殊類型,表示任意類型,其本質是個空介面 type any = interface{}
,類似Java中的Object
類型。更多泛型約束信息獨立章節介紹
使用泛型切片,與普通切片幾乎一模一樣。唯一區別,創建時需要傳遞參數類型,這就是所謂的類型參數化。
arr := make(genSlice[int], 0)
arr2 := genSlice[float64]{1.1, 1.2, 2.1}
var arr3 = genSlice[string]{"a", "b", "cd"}
創建了三個切片,相比普通切片多了個參數,使用大括弧傳遞的類型參數,在底層會替換占位符T
,三個切片的元素類型分別是int
、float64
、string
。僅此而已,再無其他區別。訪問切片
arr = append(arr, 10)
arr = append(arr, 20)
for _, i := range arr {
fmt.Printf("%T=%v\n", i, i)
}
輸出如下
int=10
int=20
哈希泛型
也就是map切片,map有兩個參數,可以定一個或兩個泛型,其他方面與切片泛型幾乎一樣
type genMap1[V any] map[string]V // 一個泛型, key 是字元串, value是泛型, 創建時傳入
type genMap2[K string|int, V any] map[K]V // 兩個泛型, key 是泛型且限定為字元串或整數, value是泛型且沒有限定
創建實例
m1 := genMap1[int]{"k1": 1}
m2 := genMap2[string, string]{"k1": "v1"}
管道泛型
泛型定義
type genChan[T any] chan T
創建實例
chan1 := make(genChan[int])
var chan2 genChan[string] // nil, 引用類型需要初始化
基礎類型泛型
相比容器類型有一些區別。基礎類型不能只有類型形參,如下缺乏原始類型。
type CommonType[T int|string|float32] T // err
正確定義,給出原始類型int
type CommonType[T int | string | float32] int
由於原始類型是int
,導致泛型約束無效,原始類型限定更強
var v1 CommonType[int] = 10 // ok
var v2 CommonType[string] = "test" // err
函數泛型
定義泛型函數
func add[T int|string](x, y T) T {
return x+y
}
調用泛型函數,一樣的套路,調用時多個傳遞一個參數,表示類型
n := add[int](1, 2)
fmt.Println(n)
註意,匿名函數不支持泛型,匿名函數不能定義類型參數,以下案例編譯不通過
func[T int | float32](a, b T) T { // err
return a+b
}
閉包函數則可以使用泛型,閉包函數是對外部類型的應用,而不是類型參數。
func MyFunc[T int | float32]() func(a, b T) T {
return func(a, b T) T {
return a + b
}
}
結構體泛型
定義泛型結構體
type genStruct[T int | string] struct {
Name string
Data T
}
創建實例
p1 := genStruct[int]{"xx", 10}
p2 := new(genStruct[string])
註意,匿名結構體目前還不支持泛型,以下代碼編譯不通過。
struct[T int|string] {
caseName string
got T
want T
}[int]{
caseName: "test OK",
got: 100,
want: 100,
}
需要特別註意的是結構體方法,泛型結構體並不代表方法泛型。判斷一個方法是否支持泛型,要看是否有定義類型參數,這與Java特性一樣。如下setName
就是普通方法,因為並沒有定義泛型參數。名稱前面的(p *person[T])
是類型綁定,而不是類型參數。setName
雖然是普通方法,但是內部可以使用結構體中定義的泛型
type person[T string | int] struct {
name T
}
func (p *person[T]) setName(name T) T { // 普通方法
p.name = name
return p.name
}
截止目前go 還不支持泛型方法(1.9版本),如果後續支持,定義方式應該如下,增加了類型參數E
func (p *person[T]) setName[E string](name T, surname E) T {
...
}
泛型也支持相互套用
type WowStruct[T int | float32, S []T] struct {
Data S // S是T類型切片
MaxValue T
MinValue T
}
看起來有點複雜,只要記住一點:任何泛型類型都必須傳入類型實參實例化才可以使用。
var ws WowStruct[int, []int]
介面泛型
介面泛型最為特殊,因為方法不支持泛型,介面定義卻支持泛型,看似好像兩者衝突了。其實用了個雞賊的方式實現,繞開了問題根本,本質是類型轉換。介面泛型定義如下,看起來沒有特別之處
type DataProcessor[T any] interface {
Process(oriData T) (newData T)
Save(data T) error
}
實現泛型介面
type CSVProcessor struct {
}
func (c CSVProcessor) Process(oriData string) (newData string) {
return oriData
}
func (c CSVProcessor) Save(oriData string) error {
return nil
}
註意:與實現非泛型介面有區別,結構體的方法簽名與泛型介面內方法簽名不一樣,按照規範兩者沒有實現關係。討巧就在這裡,給泛型傳入類型參數,然後類型轉換。
func MyFun(E DataProcessor[string]) { // 形參是泛型介面
println(E.Process("name"))
}
var processor DataProcessor[string] = CSVProcessor{} // 類型轉換
MyFun(processor)
包裝泛型
以下方式也都是錯誤定義
// 錯誤, 它認為你要定義一個存放切片的數組,數組長度由 T 乘以 int 計算得到
type NewType [T * int][]T
//✗ 錯誤。和上面一樣,這裡不光*被會認為是乘號,| 還會被認為是按位或操作
type NewType2[T *int|*float64] []T
//✗ 錯誤
type NewType2 [T (int)] []T
解決方法也都一樣,使用介面包裝。在“介面類型約束”章節詳細說明
type NewType interface {
*int | *float64
}
泛型約束
除了類型參數化,泛型有個重要作用:類型約束。簡單理解就是限定泛型占位符的範圍,比如類型參數只能是數組、字元串、數組、介面、函數等。Java的泛型約束可支持上邊界、下邊界、或類型等;Go泛型約束弱一些,沒有上邊界、下邊界。主要原因還是Go的特殊面向對象,沒有Java完整的類型系統。
基本類型約束
泛型約束為int
類型
func MyFunc[T int]() T {
...
}
這種單個基礎數據類型約束沒有意義,直接使用基礎類型效果一樣。更多時候會約束一個範圍,如下約束範圍是所有的整數類型。
func MyFunc[T int|int8|int16|int32|int64|float32|float64]() T {
...
}
介面類型約束
介面是泛型約束中使用最廣泛的類型,這與Java一致。面向介面編程,面向介面約束。
在基本類型約束中有個案例
func MyFunc[T int|int8|int16|int32|int64|float32|float64]() T {
...
}
這種寫法繁瑣且不方便復用,如果其他函數有相同的約束需求,需要再寫一遍,為此Go 提供特殊的介面定義,專門用於泛型約束。
// 整型
type Int interface {
int|int8|int16|int32|int64|float32|float64
}
// 無符號整型
type Uint interface {
uint | uint8 | uint16 | uint32
}
// 浮點
type Float interface {
float32 | float64
}
介面約束之間也繼續組合,約束為所有數值類型
type Number interface {
Int | Uint | Float
}
使用介面約束,與使用其他約束一樣
func MyFunc[T Number]() T {
...
}
上面介面定義各類型使用|
符號分割,是並集關係,也支持交集關係
// 並集
type AllInt interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32
}
// 並集
type Uint interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
// 交集, 介面A代表的類型集是 AllInt 和 Uint 的交集
type A interface {
AllInt
Uint
}
// 交集, 介面B代表的類型集是 AllInt 和 ~int 的交集
type B interface {
AllInt
~int
}
空集合, int
和 float32
沒有相交的類型,所以介面 Bad 代表的類型集為空,沒有任何類型可以匹配,這與any
空介面剛好相反,後者可表示任意類型。
type Bad interface {
int
float32
}
註意:並集和交集只能用於基本類型,如下定義異常
type A interface{
funcA()
}
type B interface{
funcB()
}
type C interface{
A | B // err
}
穿透別名,項目中經常使用type
定義別名,泛型約束無法穿透別名,如下案例編譯不通過
type MyInt int
func MyFunc[T MyInt]() T {
...
}
MyFunc[int]() // err,int != MyInt
Go專門為此提供了特殊語法,在類型前面增加~
符號,可穿透別名約束到底層類型。
type MyInt interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
func MyFunc[T MyInt]() T {
...
}
MyFunc[int]() // ok
MyFunc[MyInt]() // ok
使用~
符號穿透別名約束有一些限制條件:~
後面的類型不能為介面,~
後面的類型必須為基本類型。
type MyInt int
type _ interface {
~int // ok
~[]byte // ok
~MyInt // err,~後的類型必須為基本類型
~error // err,~後的類型不能為介面
}
介面新語法有更深層次的意義,影響深遠,在1.18版本之前介面是一堆函數聲明的集合,稱為方法集(method set)。如下ReadWriter
定義了一個介面 ,包含了 Read()
和 Write()
兩個方法。所有同時定義了這兩種方法的類型被視為實現了這一介面。
type ReadWriter interface {
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}
但是如果換一個角度來重新思考,會發現還能這樣理解:可以把 ReaderWriter
介面看成代表一個“類型的集合”,所有實現了 Read()
Writer()
這兩個方法的類型都在介面代表的類型集合當中。從 1.18 開始介面的定義就從 **方法集(method set)**
變為了 **類型集(type set)**
。
以此配合介面增加了新技能,介面不僅可以定義方法聲明,也定義底層類型。來個複雜點的案例,介面類型 ReadWriter
代表了一個類型集合,所有以 string
或 []rune
為底層類型,並且實現了 Read()
和Write()
這兩個方法的類型都在 ReadWriter
代表的類型集當中
type ReadWriter interface {
~string | ~[]rune
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}
從此介面可分為兩種類型:Basic interface
基本介面,介面內僅包含方法聲明,就是Go1.18 之前的介面。General interface
一般介面,介面內有定義原始類型,以及方法聲明。以下兩個介面都是一般介面,因為內部都有定義原生類型
type Uint interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 // 原始類型
}
type ReadWriter interface {
~string | ~[]rune // 原始類型
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}
容器類型約束
常用的約束
func MyFun[T any](arr []T) {
for i := range arr {
log.Println(i)
}
}
以下兩種寫法和實現的功能其實是差不多的,實例化之後結構體相同
type WowStruct[T int|string] struct {
Name string
Data []T
}
type WowStruct2[T []int|[]string] struct {
Name string
Data T
}
但是像下麵這種情況的時候,使用前一種寫法會更好
type WowStruct3[T int | string] struct {
Data []T
MaxValue T
MinValue T
}
函數類型約束
也比較常規,就是使用函數簽名約束,更多使用介面約束
func MyFunc[T any](arg T) {
...
}
參考