返回

泛型篇章,一个永恒的JAVA主题

后端

观察以下代码:

List<Integer> lt1 = new ArrayList<Integer>();
List<String> lt2 = new ArrayList<String>();

我们会发现当使用Arraylist实现List接口的时候,我们并没有定义数据类型,但是list能够同时保存Integer和String类型的数据。并且在我们定义了lt1和lt2的

lt1.add(10);
lt2.add("hello");

此时,系统编译器不会报错,且lt1和lt2可以分别持有Integer和String对象,变量type能够分别得到Integer和String,这样的行为完全不符合Java的静态安全检查原则,可是系统编译器居然默许了这种操作。

通向Java泛型的道路

由于代码存在异常情况,且编译器不会报错,很有可能在运行期间出现异常。

lt1.add("hello");

尽管编译器编译正常,但是因为编译器发现数据类型不匹配,所以代码运行期间会抛出异常。当在代码中加入以下代码时:

Integer n = lt1.get(0);

编译器同样不会报错,此时,系统编译器又一次违反了Java的静态安全检查原则。编译器应当发出警告,并且不允许开发者通过Integer变量n来接受String类型数据。

代码编译的背后逻辑

接下来,我们来揭秘这段代码是如何编译的,因为编译器在某些时候的运行机制与我们的预期有差异。

List<Integer> lt1 = new ArrayList<Integer>();

当编译器编译这段代码时,实际上,它会使用Class文件来代替数据类型Integer。

List<Class> lt1 = new ArrayList<Class>();

所以,编译器其实做了这么一件事:用类Class来代替数据类型,并不会像我们想象的那样用类似于int.class的方式替换。接下来,我们来看看第二部分代码是怎么编译的。

lt1.add(10);

当编译器编译这段代码时,实际上会先将数字10转换成一个Integer对象,然后再调用ArrayList类的add()方法。

lt1.add(new Integer(10));

尽管这种做法与我们的期望一致,但仍然存在一些隐患。比如,当我们调用Collection类的add()方法时,会发生以下情况:

public boolean add(Object o);

当调用Collection类的size()方法时,会发生以下情况:

public int size();

这些方法都是使用Object类型的参数。这会带来一个问题,即我们可以使用Collection类的add()方法来向Collection对象添加任何类型的对象,因为该方法使用的是Object类型的参数。而当我们使用size()方法时,却无法得知Collection对象中到底有多少个元素,因为size()方法返回的是一个int类型的值。

限制添加到Collection对象中的元素类型

如果我们希望限制可以添加到Collection对象中的元素类型,那么我们可以使用Java泛型。Java泛型允许我们为Collection对象指定一个类型参数,该类型参数可以是任何类型。

public class MyCollection<T> {

    private List<T> list;

    public void add(T t) {
        list.add(t);
    }

    public int size() {
        return list.size();
    }
}

现在,我们可以像这样使用MyCollection类:

MyCollection<Integer> myCollection = new MyCollection<Integer>();
myCollection.add(10);
myCollection.add(20);
int size = myCollection.size();

现在,当我们试图向myCollection对象中添加一个String类型的值时,编译器就会报错。

myCollection.add("hello");

这是因为,我们已经将MyCollection类的类型参数指定为Integer,因此,只能向myCollection对象中添加Integer类型的值。

结束语

泛型是Java中一个强大的特性,它可以帮助我们编写出更加安全、可靠的代码。泛型可以用于限制添加到Collection对象中的元素类型,也可以用于创建泛型方法和泛型类。