• 0
  • 0
分享

在当今软件开发领域中,泛型是一种强大的编程特性,它能够在不牺牲类型安全的前提下,实现代码的复用和灵活性。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)


  • 【留下美好印记】
    赞赏支持
登录 后发表评论
+ 关注

热门文章

    最新讲堂

      • 推荐阅读
      • 换一换
          • 背景知识Python 是一种解释型、面向对象、动态数据类型的高级程序设计语言。 Python 由 Guido van Rossum 于 1989年底发明,第一个 公开发行版发行于 1991 年。 Python 源代码遵循 GPL(GNU GeneralPublic License) 协议。 Python 不是大蟒蛇,而是个 BBC 电视剧(命名的来源) Python 是 解释型语言&nb...
            0 1 1054
            分享
          •   一、写在前面  本文讨论的基础,是基于Robotframework(简称RF)+Selenium测试框架的Web前端自动化测试。针对Robotframework和Selenium的安装、使用等基础知识不做介绍,只讨论在进行自动化脚本编写过程中遇到的元素定位失败问题。  二、但是你不得不了解的事  RF框架在做前端自动化时,支持的元素定位方式有:css定位、id定位、name定位、xpath定位和js定位。在介绍如何使用这几种方法之前,你不得不认识并熟悉我们Web测试的基础工具(客户端)——浏览器。  以Chrome浏览器为例,Chrome的开发者工具栏的Elements面板,提供了用户捕捉...
            13 14 2261
            分享
          •   前言  写本篇的原因很简单,2023年还有3个月就结束了,要给自己及其他小伙伴做下总结;  以前呢,都是自己做总结,围绕的无非就是对团队的贡献,个人成长;  但是现在不一样,需要帮小伙伴做总结,也需要为测试团队做总结,突然觉得压力山大,而且也要给优秀同学提名奖项;  因此,就有了本篇的内容,目的很简单,测试岗在评绩效时,到底有哪些维度?  业务测试  测试岗位的分工,粗略分为业务测试跟测试开发,两者因岗位的不同,而要求自然也会有区别,这里就先聊聊业务测试;  从结论而言,业务测试肯定是第一位的,是产品的基础,因此围绕业务会有很多衍生品,比如性能、兼容性、稳定性、安全等等,尽管如此,业务测试...
            0 0 1262
            分享
          • 测试驱动开发可以在很多环节里体现,同样,我们也可以主观地把TDD的思维运用到各个环节当中去。相信对敏捷熟悉的朋友对测试驱动开发(TDD)的概念都不会陌生。测试驱动开发强调通过预定义的测试标准驱动开发写出符合标准的代码。不过现在越来越多人会把TDD等同于单元测试驱动开发,即UTDD。我并不否认UTDD的价值,不过我更想强调应该把TDD当作一种思维。TDD的思维其实非常合理,做任何事情都应该有一个预期的目标和标准,如果目标和标准不清晰,就很难确保产出的价值。“The original description of TDD was in an ancient book about programmi...
            0 0 1000
            分享
          •   苹果公司日文版网站的代码多次提到"Apple Pencil Pro",预计新机型将于 5 月 7 日发布。据了解,2024 年 5 月 7 日的苹果"Let Loose"发布会将集中展示新款iPad Air和iPad Pro,但即使仅从其标识来看,新款Apple Pencil也有望亮相。现在,苹果日本官方网站上的代码显示,"Apple Pencil Pro"这个名字被多次提及。  提到的内容都在网站的页面源代码和开发人员视图中。目前还没有公开可见的引用。  在苹果公司日本网站的代码中,可以看到多次提到"Apple Pen...
            0 0 929
            分享
      • 51testing软件测试圈微信