在自定义泛型世界中,类型可以声明为Java泛型类型,函数可以声明为泛型函数。另外,泛型类型是定义类型,所以它们可能有方法。
泛型类型、泛型函数或泛型方法的声明包含类型参数列表部分,这是与普通类型、函数或方法声明的主要区别。
首先,让我们看一个例子来展示泛型类型的样子。它可能不是一个完美的例子,但它确实展示了自定义泛型类型的有用性。
package main
import "sync"
type Lockable[T any] struct {
sync.Mutex
Data T
}
func main() {
var n Lockable[uint32]
n.Lock()
n.Data++
n.Unlock()
var f Lockable[float64]
f.Lock()
f.Data += 1.23
f.Unlock()
var b Lockable[bool]
b.Lock()
b.Data = !b.Data
b.Unlock()
var bs Lockable[[]byte]
bs.Lock()
bs.Data = append(bs.Data, "Go"...)
bs.Unlock()
}
在上面的例子中,Lockable是一个通用类型。与非泛型类型相比,泛型类型的声明(更准确地说是规范)多了一个部分,即类型参数列表。Lockable这里,泛型类型的类型参数列表是[T any]。
类型参数列表可以包含一个或多个类型参数声明,这些声明用方括号括起来并用逗号分隔。每个参数声明由类型参数名称和(类型)约束组成。对于上面的例子,T是类型参数名,any是 的约束T。
请注意,这any是 Go 1.18 中引入的一个新的预声明标识符。它是空白接口类型的别名interface{}。我们应该知道所有类型都实现了空白接口类型。
我们可以将约束视为(类型参数)类型的类型。所有类型约束实际上都是接口类型。约束是自定义泛型的核心,将在下一章详细讲解。
T表示类型参数类型。它的范围从声明的泛型类型的名称开始,到泛型类型规范的末尾结束。这里它被用作Data字段的类型。
从 Go 1.18 开始,值类型分为两类:
类型参数类型;
普通类型。
在 Go 1.18 之前,所有值类型都是普通类型。
通用类型是定义的类型。它必须被实例化才能用作值类型。该表示法Lockable[uint32]称为实例化类型(泛型类型Lockable)。在表示法中,[uint32]称为类型参数列表,uint32称为类型参数,它被传递给相应的T类型参数。这意味着Data实例化类型的字段类型Lockable[uint32]是uint32.
类型参数必须实现其对应类型参数的约束。该约束any是最宽松的约束,任何值类型都可以传递给T类型参数。上例中使用的其他类型参数是:float64,bool和[]byte。
每个实例化类型都是命名类型和普通类型。例如,Lockable[uint32]和Lockable[[]byte]都是命名类型。
上面的例子展示了自定义泛型如何避免类型声明的代码重复。如果没有自定义泛型,则需要声明多个结构类型,如以下代码所示。
package main
import "sync"
type LockableUint32 struct {
sync.Mutex
Data uint32
}
type LockableFloat64 struct {
sync.Mutex
Data float64
}
type LockableBool struct {
sync.Mutex
Data bool
}
type LockableBytes struct {
sync.Mutex
Data []byte
}
func main() {
var n LockableUint32
n.Lock()
n.Data++
n.Unlock()
var f LockableFloat64
f.Lock()
f.Data += 1.23
f.Unlock()
var b LockableBool
b.Lock()
b.Data = !b.Data
b.Unlock()
var bs LockableBytes
bs.Lock()
bs.Data = append(bs.Data, "Go"...)
bs.Unlock()
}
非泛型代码包含许多代码重复,可以通过使用上面演示的泛型类型来避免。
有些人可能不喜欢上面泛型的实现。相反,他们更喜欢使用不同的实现,如下面的代码所示。与Lockable上一节中的实现相比,新实现对通用类型的外部包用户隐藏了结构字段。
package main
import "sync"
type Lockable[T any] struct {
mu sync.Mutex
data T
}
func (l *Lockable[T]) Do(f func(*T)) {
l.mu.Lock()
defer l.mu.Unlock()
f(&l.data)
}
func main() {
var n Lockable[uint32]
n.Do(func(v *uint32) {
*v++
})
var f Lockable[float64]
f.Do(func(v *float64) {
*v += 1.23
})
var b Lockable[bool]
b.Do(func(v *bool) {
*v = !*v
})
var bs Lockable[[]byte]
bs.Do(func(v *[]byte) {
*v = append(*v, "Go"...)
})
}
在上面的代码中,Do为泛型基类型声明了一个方法Lockable。这里,接收者类型是指针类型,其基类型是泛型类型Lockable。与普通基类型的方法声明不同,接收者部分在接收者泛型类型名之后有一个类型参数列表Lockable部分。这里,类型参数列表是[T].
泛型基类型的方法声明中的类型参数列表实际上是泛型接收器基类型规范中指定的类型参数列表的副本。为了使代码干净,类型参数约束被(并且必须)从列表中省略。这就是为什么这里的类型参数列表是[T], 而不是[T any].
此处,T用于值参数类型,func(*T).
Do其实例化类型的方法类型Lockable[uint32]为func(f func(*uint32)).
Do其实例化类型的方法类型Lockable[float64]为func(f func(*float64)).
Do其实例化类型的方法类型Lockable[bool]为func(f func(*bool)).
Do其实例化类型的方法类型Lockable[[]byte]为func(f func(*[]byte)).
请注意,泛型基类型的方法声明中使用的类型参数名称不需要与泛型类型规范中使用的相应名称相同。例如,上面的方法声明等同于下面重写的一个。
func (l *Lockable[Foo]) Do(f func(*Foo)) {
...
}
但是,不保持相应的类型参数名称相同是一种不好的做法。
顺便说一句,如果不使用类型参数的名称,它甚至可能是空白标识符_ (泛型类型和函数声明中的类型参数也是如此)。例如
func (l *Lockable[_]) DoNothing() {
}
以上就是关于“自定义泛型的示例”介绍,大家如果想了解更多相关知识,不妨来关注一下本站的Java基础教程技术文档,里面还有更丰富的知识等着大家去学习,希望对大家能够有所帮助哦。
你适合学Java吗?4大专业测评方法
代码逻辑 吸收能力 技术学习能力 综合素质
先测评确定适合在学习