java泛型特例_Go Internals: Go 反射 vs Java 泛型 vs cpp 模板

go反射 vs java范型 vs cpp模板

0 简述

Go语言并不支持范型编程(某些内置函数是支持范型的,但是用户自定义函数不支持范型),但是可以借助reflect来一定程度上弥补这部分能力的缺失,因为要靠运行时计算所以有运行时开销,性能比不上真正的范型实现。

Java支持真正的“范型”,泛型编程的好处是,编译时对类型安全性进行检查,并且模板参数可以是任意类型不用做类型转换,既安全又方便。由于是在编译时进行类型检查,并且Java编译器会对类、方法、变量级别的模板参数进行类型擦除(Type Erasure,简单理解就是将模板参数替换成Object类型或者第一个Bound的类型),无运行时开销,比Go借助反射模拟范型性能好,也不用像C++一样拷贝代码引起编译速度下降或者代码尺寸膨胀。点击查看:Java-Type-Erasure。

C++通过“模板”来支持“范型”编程,之所以加引号,是因为C++不是真正的支持范型编程,模板特例化时编译器其实是生成了一个新类的代码。C++模板是通过Macro来进行处理的,相当于复制、粘贴了类模板的代码,并替换了模板参数类型。简言之,就是一个高级的Macro处理系统。但是因为拷贝了代码,代码膨胀导致了编译速度下降、文件尺寸增加。

网上有很多相关的讨论,这里举个示例简单总结一下。

1 C++ 类模板

#include

using namespace std;

template

class Calc{

T t1;

T t2;

public:

T Add(T t1, T t2) {

return t1 + t2;

}

};

int main() {

Calc calc_int;

auto sum_int = calc_int.Add(1, 2);

cout << "1 + 2 = " << sum_int << endl;

Calc calc_flt;

auto sum_flt = calc_flt.Add(1.1, 2.2);

cout << "1.1 + 2.2 = " << sum_flt << endl;

return 0;

}

这里其实是创建了两个不同的类,objdump -dS可以很清晰地看到至少创建了两个不同的方法Add(T, T),可能会有人认为这是函数重载中的name mangling,其实不是,确实是生成了两个不同的类型,这个可以通过DWARF相关信息看出来,首先g++ -s main.cpp得到汇编后文件main.s,然后查看该文件内容并搜索Calc,下面两个分别表示Calc模板实例以及Calc模板实例,二者确实属于两个不同的类型,一个是用Ltypes95来标识,一个是用Ltypes47来标识。

105558a163343deb9183e8a9a180b968.png

2 Java范型

Java中范型的实现依赖于Java中的类继承机制、类型擦除、类型转换来实现,最终只会有一个类的示例。

编译时,编译器会对模板参数T进行类型擦除,这里有两种处理的情形:

模板参数T,没有绑定一个类型(如T extends Comparable),那么类型擦除后,模板参数T会用Object进行替代,同时生成对应的类型转换的代码;

模板参数T,有限制类型(如T extends Comparable限定了模板实参必须实现Comparable接口),那么类型擦除后,模板参数T就用这第一个bound的类型Comparable代替,同时生成对应的类型转换代码。

需要注意的是,编译时类型擦除虽然会对源码做一定的调整,某些信息看似丢失了,比如List lst被擦除后变为了List lst,在运行时我们依然可以通过反射机制来获取lst的元素类型为String,则是为什么呢?这是因为类型擦除并不是删除所有类型信息,模板实参的信息会以某种形式保存下来,以便反射时使用。

// 类型擦除前代码

List lst = new ArrayList();

lst.Add("hello");

lst.Add("world");

Iterator it = lst.iterator();

for ; it.hasNext(); {

String el = it.Next();

}

// 类型擦除后代码

List lst = new ArrayList(); // 模板实参String,擦除为Object

lst.Add("hello"); // hello为String,IS-A Object关系成立

lst.Add("world"); // ...

Iterator it = lst.iterator();

for ; it.hasNext(); {

String el = (String) it.Next(); // 编译器自动插入类型转换的代码

}

由此可见,Java的范型实现,既不会像C++那样多创建类导致代码体积膨胀,也不会带来运行时开销,也没有破坏反射依赖的信息。

3 go反射

Go1不支持范型,但是它可以结合interface{}以及reflection来模拟范型。反射的性能大约有几百ns级别的性能损耗,和范型实现比,还是存在一定的性能差距。

Go2已经中已经计划支持泛型了,拭目以待。

总结

对“泛型”这个宽泛的术语,对比了c++、java、go的一些支持和实现上的差异。

本作品采用《CC 协议》,转载必须注明作者和本文链接

hitzhangjie

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值