关于泛型的一点想法

455 阅读3分钟

泛型出现的背景

  在java语言刚刚诞生的时候, 泛型还不是语言的一部分。所以java的泛型是建立在之前版本的一项附加功能。也就是说很多情况下, 泛型并不是一种独立的方法, 同样的功能用以前的语法规则也能轻松实现, 只不过泛型能让整个过程更安全。

一个简单的类

  现在我想写一个SortedArray类, 让其中的元素能够有序排列, 如果没有泛型, 我会这么写:

public class SortedArray {
    public void add(Comparable e) {}
}

  很明显, 这个类中的元素必须要实现Comparable接口。但是这个真的能达到理想的效果吗?答案是否定的, 因为Comparable接口的方法是:

    int compareTo(Object o)

  一般的类都是同类型进行比较, 所以实现这个方法的第一步就是将参数强制转换成和自己一样的类型, 而如果类型不一致方法就会抛出异常。同样的, 如果一个这样的SortedArray对象, 它本应该包含一系列的相同类型元素。 如果我们用的时候不小心将一些不同类型的对象放入同一个SortedArray对象, 编译完全正常, 可是程序一旦运行就会抛出异常。

简单的泛型类

  泛型能够很大程度上的将这类运行时异常变成编译错误, 毕竟编译错误要更加容易解决。 可以说泛型就是一个更严格的编译语法检测系统, 这个系统在很大程度上能够确保你的程序是类型安全的。
  先来一个泛型的简单应用, 假如现在有一个Array类, 它要求其中的每个元素类型一致, 除此之外没有别的要求, 我用泛型可以这样写:

public class Array<T> {}

  然后我用的时候只需要这样:

Array<String> array = new Array<String>()

  由于java语言的不断改进, 在java7及以后的版本可以写成这样:

Array<String> array = new Array<>()

运用泛型改进

  现在可以用泛型来改进SortedArray类了, 我们对这个类中的元素有两种要求, 其一是类型相同, 其二是实现了Comparable接口, 可能你会这样写:

public class SortedArray<T extends Comparable> {}

  这样写大部分时候没问题, 不过我们还要确定类型T具有的compareTo的比较对象也是同类型的, 而且Comparable本身就是泛型接口, 在有泛型的情况下用原始类型编译器会给出警告。进一步的改进是这样的:

public class SortedArray<T extends Comparable<T>> {}

  嗯, 现在看起来不错了, 不过假如有下面这几个类:

class Shape implements Comparable<T> {
    ...
    int compareTo(T o) {
        return getArea() - o.getArea();
    }
}
class Circle implements Shape {}

  现在我想要声明一个这样的SortedArray:

SortedArray<Circle> array = new SortedArray<>();

  很容易发现, 这个语句是无法通过编译的, 因为Circle并没有实现Comparable<Circle>这个接口。 而且我们知道, java里面同一个接口不能被继承两次。Comparable的各种泛型接口在继承方面被看成是同一个接口, 所以子类Circle就不能再实现Comparable<Circle>了。
  将圆用面积进行大小比较的想法是很正常的, 可能这样写:

SortedArray<Shape> array = new SortedArray<>();

  这没问题, 但是不够好。 我现在已经知道里面的元素都是Circle为什么不能直接将其确定下来呢?其实可以这样写:

public class SortedArray<T extends Comparable<? super T>> {}

  好了, 现在这个问号(通配符)表示Shape类, Circle类实现了Comparable<Shape>, 而Shape类是Circle的父类, 所以T可以表示Circle, 完美。