Java & Go 泛型对比
在当今软件开发领域中,泛型是一种强大的编程特性,它能够在不牺牲类型安全的前提下,实现代码的复用和灵活性。Java 作为一种老牌的面向对象编程语言,在其长期的发展过程中,已经积累了丰富的泛型经验和应用场景。而 Go 语言作为一种相对较新的编程语言,也在不断探索和发展其泛型特性,以满足现代软件开发的需求。本文将对 Java 和 Go 语言的泛型进行比较和介绍,探讨它们的实现方式、语法特点以及适用场景,帮助读者更好地理解和应用泛型编程。
随着 Go 语言 1.18 版本的发布,泛型正式成为了 Go 语言的一部分,填补了原本的短板。通过引入类型参数,使得函数和数据结构可以接受任意类型的参数,从而提升了代码的可复用性和灵活性。这项特性经过长时间的设计和讨论,在新版本中,开发者可以通过type
关键字来定义泛型函数和泛型类型,以及使用泛型约束来限制泛型类型参数的行为。这些新特性的引入,将为 Go 语言的开发者们带来更为丰富和灵活的编程体验。
泛型的引入为 Go 语言带来了一种更为优雅和灵活的编程方式。通过类型参数的引入,函数和数据结构可以接受任意类型的参数,避免了之前通过接口和类型断言等方式实现类似功能的冗余性和复杂性。在新版本中,开发者可以使用type
关键字定义泛型函数和泛型类型,以及使用泛型约束来限制泛型类型参数的行为,从而提升了代码的可读性和可维护性。
Go 语言 1.18 版本的泛型特性经过了长时间的设计和讨论,以确保其能够满足广大开发者的需求,并且与现有的 Go 语言生态无缝衔接。这些新特性的引入,将为 Go 语言的开发者们带来更为丰富和灵活的编程体验,帮助他们更好地应对复杂的编程场景。相信随着更多开发者开始使用泛型特性,Go 语言的生态和社区将会变得更加丰富和多样,为未来的 Go 语言编程带来更多的可能性和机会。
语法
让我们首先看一下 Go
语言的泛型例子:
下面看一下 Java
泛型:
这两个示例展示了在 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
值时,就会报错,例子如下:
报错:
./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
语言泛型类型参数进行约束,如下:
依然得到了如下报错:
./fun_test.go:126:9: cannot use type switch on type parameter value t (variable of type T constrained by int64 | float64)
看来这似乎是 Go
语言特殊的设计,并不希望泛型功能被使用或者泛型本身并不是具有某个类型属性的类型。我们再看一下 Java
是如何处理此类情况:
这段代码如何遇到报错:
请切换 21 及以上 SDK 版本,但其实没有必要,实际编码也用不到这个语法。
类型约束
在 Go 语言中,类型参数约束 T any
表示 T
不受任何特定接口的约束。换句话说,T
实现了 interface{}
(但不完全如此;参考第二章节)。在 Go 语言中,如果一个类型参数被约束为 T any
,则该类型参数 T
不受任何特定接口的限制。也就是说,任何实现了空接口 interface{}
的类型都可以作为类型参数 T
的实际类型。但需要注意的是,并非所有类型参数 T
都实现了 interface{}
接口,具体取决于上下文和类型约束的情况。
在 Go 语言中,我们可以通过指示除 any
之外的东西来进一步约束 T
的类型集,例如:
等价的 Java
代码如下:
在 Go 语言中,类型参数声明可以指定具体类型(如 Java),并且可以内联或引用声明:
当然这段代码会报 此包中重新声明的 'PrintInt64'
检查异常,可以暂时忽略。
联合类型
Go 和 Java 都支持联合类型作为类型参数,但它们的方式非常不同。
Go 只允许具体类型的联合类型。代码如下:
Java 只允许接口类型的联合类型,或者非接口类型和接口类型之间的联合类型。
变异性
Go 的泛型提案不包括对协变性和逆变性的支持。这意味着泛型类型中的类型之间的关系不受类型参数的子类型关系的影响。换句话说,在 Go 的泛型中,如果 T1
是 T2
的子类型,这并不意味着 Foo[T1]
和 Foo[T2]
之间存在任何关系。同样地,即使 T1
是 T2
的超类型,Foo[T2]
和 Foo[T1]
之间也没有任何关系。这种设计决定简化了泛型的实现,并有助于保持 Go 代码的简洁和可读性。然而,这也意味着某些依赖于协变性或逆变性的泛型编程技术可能无法直接应用于 Go 的泛型中。
这种情况在 Java
语言中得到了很好的解决:
版权声明: 本文为 InfoQ 作者【FunTester】的原创文章。
原文链接:【http://xie.infoq.cn/article/553dc7e4696f3ad639d7f8818】。文章转载请联系作者。
评论