欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java教程——泛型(一)

程序员文章站 2024-03-14 18:18:46
...

在讲解泛型之前,我想先提一提 ArrayList,因为他在我们编程中经常出现。大家有没有想过,他为什么 啥类型的数据都能装?聪明的人都知道是因为泛型。

好,我换个问法:假如没有泛型,你猜会变成什么样子?下面我们通过代码带你领略这一现象。

代码认识

假如没有泛型,ArrayList的源码应该是这样的:

public class ArrayList {
    private Object[] array;
    private int size;
    public void add(Object e) {...}
    public void remove(int index) {...}
    public Object get(int index) {...}
}

我们可以看到,里面装的 Object ,我们都知道java中一切对象都基于 Object ,这倒是个解决能装所有引用数据类型的好方法,可是,它没有对引用数据类型进行逻辑上的校验,什么意思呢?请看下面代码

ArrayList list = new ArrayList();
list.add("Hello");
// 获取到Object,必须强制转型为String:
String first = (String) list.get(0);

这段到时没啥毛病,下面一段毛病可大了:会报错:ClassCastException

list.add(new Integer(123));
// ERROR: ClassCastException:
String second = (String) list.get(1);

编程本身是一种让人身心愉悦的事,你这倒好,为了避免报 ClassCastException 这个错,得让编程人员记住每个位置的存放引用数据类型,这样很不友好,于是有人就想出了这么个办法:为每个数据类型创建属于自己的 ArrayList,什么意思呢?见下面代码:
 

public class StringArrayList {
    private String[] array;
    private int size;
    public void add(String e) {...}
    public void remove(int index) {...}
    public String get(int index) {...}
}

从这段代码中 我们可以看到 StringArrayList 存入和取出的对象都是 String,的确解决了 ”误转型“ 问题。

实际上,还需要为其他所有class单独编写一种ArrayList

  • LongArrayList

  • DoubleArrayList

  • PersonArrayList

  • ...

这是不可能的,JDK的class就有上千个,而且它还不知道其他人编写的class。

为了解决新的问题,我们引入了泛型,即编写模板代码来适应任意类型

我们把ArrayList变成一种模板:ArrayList<T>,代码如下:

public class ArrayList<T> {
    private T[] array;
    private int size;
    public void add(T e) {...}
    public void remove(int index) {...}
    public T get(int index) {...}
}

从上面代码可以看出,它装的类型和取出来的类型与用户创建该ArrayList时传入的类型有关。

T可以是任何class。这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList

// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 创建可以存储Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();

因此,泛型就是定义一种模板,例如ArrayList<T>,然后在代码中为用到的类创建对应的ArrayList<类型>

ArrayList<String> strList = new ArrayList<String>();

由编译器针对类型作检查:

strList.add("hello"); // OK
String s = strList.get(0); // OK
strList.add(new Integer(123)); // compile error!
Integer n = strList.get(0); // compile error!

这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。

向上转型

在Java标准库中的ArrayList<T>实现了List<T>接口,它可以向上转型为List<T>

public class ArrayList<T> implements List<T> {
    ...
}

List<String> list = new ArrayList<String>();

即类型ArrayList<T>可以向上转型为List<T>

特别注意:不能把ArrayList<Integer>向上转型为ArrayList<Number>List<Number>

这是为什么呢?假设ArrayList<Integer>可以向上转型为ArrayList<Number>,观察一下代码:

// 创建ArrayList<Integer>类型:
ArrayList<Integer> integerList = new ArrayList<Integer>();
// 添加一个Integer:
integerList.add(new Integer(123));
// “向上转型”为ArrayList<Number>:
ArrayList<Number> numberList = integerList;
// 添加一个Float,因为Float也是Number:
numberList.add(new Float(12.34));
// 从ArrayList<Integer>获取索引为1的元素(即添加的Float):
Integer n = integerList.get(1); // ClassCastException!

我们把一个ArrayList<Integer>转型为ArrayList<Number>类型后,这个ArrayList<Number>就可以接受Float类型,因为FloatNumber的子类。但是,ArrayList<Number>实际上和ArrayList<Integer>是同一个对象,也就是ArrayList<Integer>类型,它不可能接受Float类型, 所以在获取Integer的时候将产生ClassCastException

实际上,编译器为了避免这种错误,根本就不允许把ArrayList<Integer>转型为ArrayList<Number>