Golang泛型

冰岩作坊 March 28, 2022

Golang泛型

泛型使用

泛型函数

1
func Add[T any](a, b T) T

其中T为类型,any为类型约束,相当于没有约束(底层实现为空接口).

泛型结构体

1
type array[T any] struct 

约束

1
type Inter interface func AddInInt[T Inter](ab TT//也可以这样写func AddInInt[T ~int | ~int8 | ~int16 | ~int32| ~int64](ab TT

go使用interface作为约束,其约束了T泛型的实际类型。

其中 “|” 操作符就是 “或” 的意思,“~”操作符代表粗略匹配(底层实现相同即可),如

1
func AddInInt1[T ~int](ab T) Tfunc AddInInt2[T int](ab T) Ttype MyInt intfunc main() 

当然,约束的内容除了基础类型之外,约束的内容是方法也可以。

1
func Print[S Stringer](s S) type Stringer interface 

当约束中既有基础类型约束,又有方法约束时,其必须二者都满足,如

1
type Stringer interface type MyInt intfunc (a MyIntString() string //MyInt满足Stringer约束type onlyInt inttype onlyString stringfunc (a onlyStringString() string //onlyIntonlyString都不满足

约束可以嵌套

1
type Stringer interface type Inter interface type SS interface //其等价为//type Stringer interface -----------------------------------------------------------------------------------------type Stringer interface type Inter interface //其等价为//type Stringer interface -----------------------------------------------------------------------------------------type Stringer interface type Inter interface //其虽然不会报错但显然没有任何一种类型满足约束,因为不可能有任何一种类型同时满足:1、底层为int162、实现了方法String(),3、实现了Inter约束,因为Inter约束要求底层为int,与要求的第一点(底层为int16)不可能同时实现//}

泛型约束

定义

1
type LockInter[T any] interface 

使用

1
func test[T LockInter](t T)T{}//或者func test[T LockInter[T1],T1 any](t T)T{}

泛型原理

Go

Go泛型实现原理与C++模板相同,对于每一种泛型实例生成对应的代码进行调用。

java

类型擦除

Java的泛型采用了类型擦除来实现,在编译期间,所有的泛型信息都会被擦掉(被擦为原始类型,无约束则为Object),如在代码中定义List和List等类型,在编译后都会变成List,JVM看到的只是List。

1
public class Test }

在上面这个例子中,ArrayList与ArrayList为两种ArrayList数组,但最终打印结果为true,因为泛型类型String和Integer都被擦除掉了,只剩下原始类型Object。

1
public class Test //输出1 asd    }}

这个例子中直接调用add只能存储Integer,因为泛型类型实例为Integer(编译器会检查代码中泛型的类型,然后进行类型擦除,再进行编译),但通过运行时利用反射调用add()方法,可以存储字符串,说明ArrayList实例在编译之后Integer被擦除成原始类型Object

原始类型

原始类型指擦除去泛型信息,在字节码中的类型变量的真正类型。定义一个泛型时,相应的原始类型会被自动提供,类型变量擦除,并使用其限定类型(无限定用Object)替换。

1
class V<T>       public void setValue(T  value)   } //原始类型为//class V   //    public void setValue(Object  value)   //}public class V1<T extends Comparable> //原始类型为Comparable

类型擦除引起的问题

自动类型转换

因为类型擦除,所有的泛型类型变量最后都会被替换为原始类型,既然都被替换为原始类型,那么为什么在获取的时候,不需要进行强制类型转换?

ArrayList.get()方法:

1
public E get(int index

可以看到,在return之前,会根据泛型变量进行强制类型转换。假设泛型类型变量为Date,虽然泛型信息会被擦除掉,但是会将(E) elementData[index],编译为(Date)elementData[index]。所以不用自己进行强制类型转换。当存取一个泛型域时也会自动插入强制类型转换。假设V类的value域是public的,那么表达式:

1
Date date = V.value;

也会自动地在结果字节码中插入强制类型转换。

泛型类型无法当做真实的类型使用
1
public  void Method(T t)
泛型类型无法用作方法重载
1
public void printList(List list)public void printList(List list)

因为在编译时泛型被擦除,被擦除后这两种方法就没有任何区,所以这种写法是错误的。

与多态的冲突(重写&重载)
1
class V<T>       public void setValue(T value)   }class DateV extends V<Date>       @Override      public Date getValue()   }

我们本意是在子类DateV中对父类V的两个方法进行重写,但是若进行了类型擦除后,父类就会变成:

1
class V       public void setValue(Object  value)   }  

而子类中两个重写的方法:

1
@Override  public void setValue(Date value)   @Override  public Date getValue() 

对setValue方法,父类类型是Object,而子类是Date,参数类型不同,这看起来不会是重写,而是重载。

我们进行测试一下:

1
public static void main(String[] args) throws ClassNotFoundException 

如果是重载,那么子类中有两个setValue方法,一个是Object类型,一个是Date类型,但通过编译失败信息可以看出,没有参数为Object类型的setValue方法,可见其确实是重写而不是重载。JVM解决这个问题的方法是桥方法:编译器会在DataV子类中自动生成两个桥方法来重写父类的方法:

1
public Object getValue() //虚拟机通过参数类型和返回类型来确定一个方法,所以两个getValue方法可以同时存在(但自己编写java代码时不允许这样)public void setValue(Object value)