泛型编程
泛型编程
在支持泛型的编程语言中,泛型常用于对不受具体类型影响的可复用的业务逻辑的实现。譬如对于排序的逻辑:
public class ArraySortViaComparable {
public <E extends Comparable> void insertionSort(E[] a) {
for (int i = 1; i < a.length; i = i + 1) {
Comparable itemToInsert = a[i];
int j = i;
while (j != 0 && greaterThan(a[j-1], itemToInsert)) {
a[j] = a[j-1]
j = j - 1
};
a[j] = itemToInsert;
}
}
private static boolean greaterThan(E left, Object right) { return left.compareTo(right) == 1; }
}
这段
package sort
func Float64s(a []float64)
func Strings(a []string)
func Ints(a []int)
...
上述函数都是
泛型困境
泛型和其他特性一样不是只有好处,为编程语言加入泛型会遇到需要权衡的两难问题。语言的设计者需要在编程效率、编译速度和运行速度三者进行权衡和选择
我们以
-
C 语言是系统级的编程语言,它没有支持泛型,本身提供的抽象能力非常有限。这样做的结果是牺牲了程序员的开发效率,与Go 语言目前的做法一样,它们都需要手动实现不同类型的相同逻辑。但是不引入泛型的好处也显而易见:降低了编译器实现的复杂度,也能保证源代码的编译速度; -
C++ 与C 语言的选择完全不同,它使用编译期间类型特化实现泛型,提供了非常强大的抽象能力。虽然提高了程序员的开发效率,不再需要手写同一逻辑的相似实现,但是编译器的实现变得非常复杂,泛型展开会生成的大量重复代码也会导致最终的二进制文件膨胀和编译缓慢,我们往往需要链接器来解决代码重复的问题; -
Java 在1.5 版本引入了泛型,它的泛型是用类型擦除实现的。Java 的泛型只是在编译期间用于检查类型的正确,为了保证与旧版本JVM 的兼容,类型擦除会删除泛型的相关信息,导致其在运行时不可用。编译器会插入额外的类型转换指令,与C 语言和C++ 在运行前就已经实现或者生成代码相比,Java 类型的装箱和拆箱会降低程序的执行效率;
当我们面对是否应该支持泛型时,实际上需要考虑的问题是:我们应该牺牲工程师的开发效率、牺牲编译速度和更大的编译产物还是牺牲运行速度。泛型的引入一定会影响编译速度和运行速度,同时也会增加编译器的复杂度,所以社区在考虑泛型时也非常谨慎。