在当今软件开发领域中,泛型是一种强大的编程特性,它能够在不牺牲类型安全的前提下,实现代码的复用和灵活性。Java作为一种老牌的面向对象编程语言,在其长期的发展过程中,已经积累了丰富的泛型经验和应用场景。而Go语言作为一种相对较新的编程语言,也在不断探索和发展其泛型特性,以满足现代软件开发的需求。本文将对Java和Go语言的泛型进行比较和介绍,探讨它们的实现方式、语法特点以及适用场景,帮助读者更好地理解和应用泛型编程。
随着Go语言1.18版本的发布,泛型正式成为了Go语言的一部分,填补了原本的短板。通过引入类型参数,使得函数和数据结构可以接受任意类型的参数,从而提升了代码的可复用性和灵活性。这项特性经过长时间的设计和讨论,在新版本中,开发者可以通过`type`关键字来定义泛型函数和泛型类型,以及使用泛型约束来限制泛型类型参数的行为。这些新特性的引入,将为Go语言的开发者们带来更为丰富和灵活的编程体验。
泛型的引入为Go语言带来了一种更为优雅和灵活的编程方式。通过类型参数的引入,函数和数据结构可以接受任意类型的参数,避免了之前通过接口和类型断言等方式实现类似功能的冗余性和复杂性。在新版本中,开发者可以使用`type`关键字定义泛型函数和泛型类型,以及使用泛型约束来限制泛型类型参数的行为,从而提升了代码的可读性和可维护性。
Go语言1.18版本的泛型特性经过了长时间的设计和讨论,以确保其能够满足广大开发者的需求,并且与现有的Go语言生态无缝衔接。这些新特性的引入,将为Go语言的开发者们带来更为丰富和灵活的编程体验,帮助他们更好地应对复杂的编程场景。相信随着更多开发者开始使用泛型特性,Go语言的生态和社区将会变得更加丰富和多样,为未来的Go语言编程带来更多的可能性和机会。
## 语法
让我们首先看一下 `Go` 语言的泛型例子:
```Go
// Print[T any] // @Description: 打印类型
// @param t 任意类型
//
func Print[T any](t T) {
fmt.Printf("printing type: %T\n", t)
}
//
// Tree[Tany]
// @Description: 树结构
//
type Tree[T any] struct {
left, right *Tree[T]
data T
}
```
下面看一下 `Java` 泛型:
```Java
/** 打印任意类型
* @param t 任意类型
* @param <T> 任意类型
*/
public static <T> void print(T t) {
System.out.println("printing type: " + t.getClass().getName());
}
/**
* @param <T> 树的数据类型
*/
class Tree<T> {
private Tree<T> left, right;
private T data;
}
```
这两个示例展示了在Go语言和Java中实现泛型的方式。虽然两者都可以实现泛型,但它们的语法和实现方式有所不同。
在Go语言中,泛型是通过在函数或类型上使用类型参数来实现的。在函数 `Print[T any](t T)` 中,`[T any]` 表示类型参数,`any` 表示类型约束,即可以接受任意类型的参数。在类型 `Tree[T any]` 中,`[T any]` 表示类型参数,`any` 同样表示类型约束,表示可以是任意类型的参数。
而在Java中,泛型是通过使用尖括号 `<T>` 来定义类型参数,并在函数或类声明中使用这些类型参数。在函数 `print(T t)` 中,`<T>` 表示类型参数,表示该函数可以接受任意类型的参数。在类 `Tree<T>` 中,`<T>` 同样表示类型参数,表示该类可以是任意类型的数据类型。
总的来说,虽然Go语言和Java都支持泛型,但它们的语法和实现方式略有不同。Go语言的泛型实现相对简洁和直观,而Java的泛型实现更加灵活和强大。
一个区别:Go需要类型参数被类型显式约束(例如: `T any` ),而Java则没有( `T` 本身被隐式地推断为 `java.lang.Object` )。如果在Go中没有提供约束,将导致类似于下面的错误:
`syntax error: missing type constraint`
我怀疑差异在于Java的统一类型层次结构(每个对象都是java.lang.Object)。而Go语言则没有这样的模型。
## 类型开关
当我在 `Go` 语言中试图获取一个泛型的 `type` 值时,就会报错,例子如下:
```Go
func print[T any](t T) {
switch t.(type) {
case string:
fmt.Println("printing a string: ", t)
}
}
```
报错:
`./fun_test.go:126:9: cannot use type switch on type parameter value t (variable of type T constrained by any)`
但是当我把泛型替换成 `interface{}` 时,编译通过了。当然这是 `Go` 语言的特殊设计,并不像 `Java` 那样,所以对象均是 `java.lang.Object` 子类。怀着这样的疑问,我们将 `Go` 语言泛型类型参数进行约束,如下:
```Go
func print[T int64|float64](t T) {
switch t.(type) {
case int64: fmt.Println("printing an int64: ", t)
case float64: fmt.Println("printing a float64: ", t)
}
}
```
依然得到了如下报错:
`./fun_test.go:126:9: cannot use type switch on type parameter value t (variable of type T constrained by int64 | float64)`
看来这似乎是 `Go` 语言特殊的设计,并不希望泛型功能被使用或者泛型本身并不是具有某个类型属性的类型。我们再看一下 `Java` 是如何处理此类情况:
```Java
/** 打印任意类型
* @param t 任意类型
* @param <T>
*/
public static <T> void print(T t) {
switch(t) {
case String s:
System.out.println("字符串类型: " + s);
default :
System.out.println("非字符串类型: " + t.getClass().getName());
}
}
```
这段代码如何遇到报错:
```
java: -source 17 中不支持 switch 语句中的模式
(请使用 -source 21 或更高版本以启用 switch 语句中的模式)
```
请切换21及以上SDK版本,但其实没有必要,实际编码也用不到这个语法。
## 类型约束
在Go语言中,类型参数约束 `T any` 表示 `T` 不受任何特定接口的约束。换句话说,`T` 实现了 `interface{}`(但不完全如此;参考第二章节)。在Go语言中,如果一个类型参数被约束为 `T any`,则该类型参数 `T` 不受任何特定接口的限制。也就是说,任何实现了空接口 `interface{}` 的类型都可以作为类型参数 `T` 的实际类型。但需要注意的是,并非所有类型参数 `T` 都实现了 `interface{}` 接口,具体取决于上下文和类型约束的情况。
在Go语言中,我们可以通过指示除 `any` 之外的东西来进一步约束 `T` 的类型集,例如:
```Go
// Tree[Tany]
// @Description: 树结构
type Tree[T any] struct {
left, right *Tree[T]
data T
}
```
等价的 `Java` 代码如下:
```Java
class Tree<T extends Integer> {
private Tree<T> left, right;
private T data;
}
```
在Go语言中,类型参数声明可以指定具体类型(如Java),并且可以内联或引用声明:
```Go
// PrintInt64[T int64] // @Description: 打印64位整数
// @param t
//
func PrintInt64[T int64](t T) {
fmt.Printf("%v\n", t)
}
// PrintInt64[T Int64Type] // @Description: 打印64位整数
// @param t
//
func PrintInt64[T Int64Type](t T) {
fmt.Printf("%v\n", t)
}
//
// Bit64Type 64位整数类型
// @Description: 64位整数类型
//
type Bit64Type interface {
int64
}
```
当然这段代码会报 `此包中重新声明的 'PrintInt64'` 检查异常,可以暂时忽略。
## 联合类型
Go和Java都支持联合类型作为类型参数,但它们的方式非常不同。
Go只允许具体类型的联合类型。代码如下:
```Go
// GOOD
func PrintInt64OrFloat64[T int64|float64](t T) {
fmt.Printf("%v\n", t)
}
type someStruct {}
// GOOD
func PrintInt64OrSomeStruct[T int64|*someStruct](t T) {
fmt.Printf("t: %v\n", t)
}
// BAD
func handle[T io.Closer | Flusher](t T) { // 在联合中不能将接口与方法结合使用
err := t.Flush()
if err != nil {
fmt.Println("failed to flush: ", err.Error())
}
err = t.Close()
if err != nil {
fmt.Println("failed to close: ", err.Error())
}
}
type Flusher interface {
Flush() error
}
```
Java只允许接口类型的联合类型,或者非接口类型和接口类型之间的联合类型。
```Java
// GOOD
public static class Tree<T extends Closeable & Flushable> {
private Tree<T> left, right;
private T data;
}
// GOOD
public static <T extends Number & Closeable> void printNumberAndClose(T t) {
System.out.println(t.intValue());
try {
t.close();
} catch (IOException e) {
System.out.println("io exception: " + e.getMessage());
}
}
// BAD
public static <T extends Integer & Float> void printIntegerOrFloat(T t) {
System.out.println(t.toString()); // 模糊的调用
System.out.println(t.isNaN());
}
```
## 变异性
Go的泛型提案不包括对协变性和逆变性的支持。这意味着泛型类型中的类型之间的关系不受类型参数的子类型关系的影响。换句话说,在Go的泛型中,如果 `T1` 是 `T2` 的子类型,这并不意味着 `Foo[T1]` 和 `Foo[T2]` 之间存在任何关系。同样地,即使 `T1` 是 `T2` 的超类型,`Foo[T2]` 和 `Foo[T1]` 之间也没有任何关系。这种设计决定简化了泛型的实现,并有助于保持Go代码的简洁和可读性。然而,这也意味着某些依赖于协变性或逆变性的泛型编程技术可能无法直接应用于Go的泛型中。
这种情况在 `Java` 语言中得到了很好的解决:
```Java
// 协变性
private static void sort(List<? extends Number> list) {
}
// 逆变性
private static void reverse(List<? super Number> list) {
}
```
> - [2021年原创合集](https://mp.weixin.qq.com/s/ziuOuueP6tN2U7dF06axkw)
> - [2022年原创合集](https://mp.weixin.qq.com/s/Ztet8pky58B8RBLWrqdrpg)
> - [2023年原创合集](https://mp.weixin.qq.com/s/HI-AllIWhql0hiDEineglg)
> - [接口功能测试专题](https://mp.weixin.qq.com/s/1wHGwVmRYgGImTb6MN0EMg)
> - [性能测试专题](https://mp.weixin.qq.com/s/YOXJ3xw4NWGaverwkQPB0Q)
> - [Java、Groovy、Go、Python](https://mp.weixin.qq.com/s/LrcAe7M6dUigysM01RpvkQ)
> - [单元&白盒&工具合集](https://mp.weixin.qq.com/s/QSG8mq22DuNAUsxI7xukJw)
> - [测试方案&BUG&爬虫&UI自动化](https://mp.weixin.qq.com/s/H0QYf7QOir2QxD2O-Y44qQ)
> - [测试理论鸡汤](https://mp.weixin.qq.com/s/67jXEMIJZQZ_EMW2tSJsBA)
> - [社区风采&视频合集](https://mp.weixin.qq.com/s/0NZ88x3yc3ntR8kLZh3row)