• 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)


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

热门文章

    最新讲堂

      • 推荐阅读
      • 换一换
          • 1、引言由于最近Online环境爆出好几个问题,导致开会被各位BOSS连续的盘问,反正不是站着灵魂拷问,也不是坐着喝茶水闲聊。具体咋样,脑补一下。这让我彻夜难眠,辗转反侧,夜不能寝,饭不能吃,都消瘦了好几两肉!!遥想当年,小鱼我什么时候背过Online的锅。这说来也巧了…前晚这Ali的小姐姐又找我撩骚 ,哦不,是聊天。可能是知道我最近辗转反侧;又对我思念心切…我岂能放过这个机会…于是乎牺牲我的睡眠时间,与她夜夜长谈。最后整理出这篇博文《如何排查Online出现的问题》。做过市场调研的,或者参与过主流产品的核心人员,都会了解到,现在市面上的产品,没有谁敢说自己的产品就是0缺陷;之所以没曝出来或者...
            0 0 13924
            分享
          • 相信很多小伙伴想要自学性能测试,但网上的资料查阅出来的都比较干燥,对实际的性能测试经验并没有什么提升,本文从测试前的准备带你了解如何成为一名性能测试工程师。在开始性能测试前,我们需要跟项目组确认的几点内容:性能测试环境与生产环境服务器资源差异,并做差异分析,如测试环境的服务器资源与生产环境的服务器资源差距较大,需反馈给项目组,不然测出来的结果没有太大的参考意义;开始测试前,确认日志级别,日志级别需要与生产保持一致。如生产的日志是Info级别,则在测试环境的日志也应该是Info级别;确认数据库铺底数据(采用的方式为:让运维取生产上的每日成交量的数据库数据量),如数据库都是空表在测试查询交易的时候...
            0 0 1212
            分享
          •   测试面试话题1:敏捷开发与测试  以下是我个人总结的一些经验:  传统开发模式:V模式,瀑布模式。传统开发模式往往循规蹈矩,从需求,概要设计,详细设计,开发,单元测试,集成测试,系统测试,验收测试,上线发布,整个周期往往需要半年到一年,由于周期长,产品在开发过程中会存在需求变化,传统模式不适应需求的变化。为了解决这个问题,当前出现了敏捷模式。  敏捷分为敏捷开发和敏捷测试,特点和特征是:  1. 测试驱动开发和行为驱动测试  测试驱动开发是指开发先写单元测试,再写开发的代码,当单元测试跑通的时候,代码开发就完成了。  行为驱动测试是通过直接写user story,例如Cucumber框架,...
            0 0 930
            分享
          •   Tomcat是常用的应用服务器之一,主要用于开发和测试,也有少量用户用在生产系统中。本文总结了一些Tomcat的常见问题及处理技巧,以供参考。  启动问题  Windows下,在启动Tomcat时经常会出现启动窗口一闪而过,启动失败的情况,而日志文件中没有任何错误提示。  此类问题多半是jvm参数或环境变量设置的问题,解决问题的关键是看到控制台输出的内容,但控制台一闪而过,不知道哪里出了问题。  Tomcat默认安装后,bin目录下的startup.bat文件最下面是这样的。  call "%EXECUTABLE%" start %CMD_LINE_ARGS%,这个命令...
            0 0 558
            分享
          • 基于Appium的Android自动化框架根据多种工具选择的性价比,客户端自动化采Shell+Appium+Selenium+Java+TestNG框架。 工具选择背景以业界主流工具进行对比,优劣如下图所示:由于Selenium工具开源且扩展性较好,所以选定为框架主体框架设计详细根据工具选择,设计自动化框架如下:自动化演示通过命令或定时任务,达到自动执行的效果测试案例2016年初,经优化重组,中软测试团队固定在8人,根据当初接手的实际情况,项目组选用高速迭代开发的规范化流程。这段时期,由于android机型多样,导致软件兼容性问题突出,这对开发测试效率和产品质量都是严峻的考验。开发团...
            12 11 1603
            分享
      • 51testing软件测试圈微信