Java中集合类对于存入的元素会忘记数据类型,当需要使用时去除的元素变为Object类型。从Java5之后,Java引入参数化类型的概念,允许在创建集合的时候指定集合的元素类型,Java的参数化类型就叫做泛型。
创建这种特殊集合的方法是:在集合接口、类后增加尖括号,尖括号里放一个数据类型,即表明这个集合接口、集合类中只能保存特定类型的对象。
泛型使集合类的使用更加健壮,避免了程序将其他对象放入集合类中;并且程序会更加简洁,集合自动记住所有集合元素的数据类型,从而无需对集合元素进行强制类型转换。
不使用泛型时的代码:
<span style="font-size:14px;">import java.util.ArrayList;
import java.util.List;
class strList {
private List strList = new ArrayList();
public boolean add(String element) {
return strList.add(element);
}
public String get(int index) {
return (String) strList.get(index);
}
public int size() {
return strList.size();
}
}
public class Check {
public static void main(String[] args) {
strList stl = new strList();
//手动实现编译检查
stl.add("haha");
stl.add("xixi");
stl.add("huhu");
System.out.println(stl);
for (int i = 0; i < stl.size(); i++) {
System.out.println(stl.get(i));
}
}
}</span>
Java7之后允许在构造器后不需要带完整的泛型信息,Java可以推断等号右边的尖括号内的泛型信息。
当采用泛型之后的代码:
<span style="font-size:14px;">import java.util.ArrayList;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
List<String> strList = new ArrayList<>();
strList.add("haha");
strList.add("xixi");
strList.add("huhu");
for( int i = 0 ; i < strList.size() ; i++)
{
System.out.println(strList.get(i));
}
}
}</span>
一、深入泛型
需要说明的是,泛型并不是只用于集合类,我们可以为任意类添加泛型声明。
泛型的实质是允许在定义接口、类时声明类型形参,类型形参在整个接口、类体内可以当成类型使用,几乎所有可使用普通类型的地方都可以使用这种类型形参。
<span style="font-size:14px;">public class Shape<T> {
private T dataT;
public Shape() {
}
public Shape(T element) {
dataT = element;
}
public void setData(T element) {
dataT = element;
}
public T getData()
{
return dataT;
}
public static void main(String [] args )
{
Shape<String> sh = new Shape<>("圆形");
System.out.println(sh.getData());
Shape<Integer> sh2 = new Shape<>(2);
System.out.println(sh2.getData());
}
}</span>
程序中定义一个List<E>接口,实际使用是可以产生无数多个List接口,只要E传入的参数不同,系统将会产生一种新的List子接口。需要说明的是系统并没有进行源代码的复制,这是一种动态生成的逻辑上的子类,物理存储上是不存在的。
当创建了具有泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类。但需要指出的是,当使用了这种接口或父类时将不能包含类型形参。
<span style="font-size:14px;">public class cirle extends Shape<String></span>
泛型的类型参数不管是哪一种,对于Java来说,他们依然是一个相同的类,占用一块相同的内存空间。所以说在类的定义中,静态方法、静态初始化块或者静态变量的声明不允许使用泛型进行定义。
二、关于类型通配符
如果说ZI是FU的一个子类型,而Method是一个具有泛型声明的类或者方法,Method(FU)并不是Method(ZI)的父类,但对于FU[]来说它依然是ZI[]的父类。这是比较奇特的一点。
Java泛型的设计原则是,只要代码在编译是没有出现警告,就不会运行时出现异常,泛型的异常检测是Check检测,并不是运行时检测。
为了表示各种泛型的父类,在尖括号内使用类型通配符“?”,它可以匹配任何类型.
<span style="font-size:14px;"> public void text( List<?> list)
{
for( int i = 0 ; i < list.size(); i++ )
{
System.out.println(list.get(i));
}
}</span>
泛型类中实现参数化类型的方法,接受的元素必须是指定参数化类型的类的对象或子类的对象。
A、关于类型通配符的上限
当使用诸如List<?>这种形式时,即表明这个List集合可以是任何泛型List的父类。但是说我们并不想使这个List<?>成为泛型List的父类,只是想让它成为某一类泛型List的父类。
List< ? Extends shape >是一个有通配符上限的泛型集合,此处的?代表一个未知类型,但这个未知类型一定是Shape的子类型。
与类同时继承父类、实现接口类似的是,为类型形参指定多个上限时,所有的接口上限必须位于类上限之后。也就是说,如果需要为类型形参指定类上限,类上限必须位于第一位。
一般用于存储对象的时候。
B、关于类型通配符的下限
Java允许设置通配符下限:< ? Super Type >,这个通配符表示必须是Type本身,或者是Type的父类。
一般用于读取对象时的时候。
三、泛型方法
所谓泛型方法就是在定义方法时定义一个或多个类型形参。
<span style="font-size:14px;">import java.util.ArrayList;
import java.util.Collection;
public class GenericMethod {
static <T> void Array2Collection(T arry[], Collection<T> coll) {
for (T key : arry) {
coll.add(key);
}
}
public static void main(String[] args) {
Object [] oaObjects = new Object[100];
Collection<Object> co = new ArrayList();
Array2Collection(oaObjects, co);
}
}</span>
上面程序中定义的泛型方法中定义了一个T类型形参,这个类型形参可以在该方法中当成普通类型使用,但是这种形参只能在该方法中使用,同理接口、类声明的类型形参只可以在接口、类中使用。
四、类型擦除与转换
在严格的泛型代码里,带泛型参数的类总应该带着类型参数,但Java也允许在私用泛型声明的类时不指定实际的类型参数。如果没有为这个泛型类指定实际的类型参数,则该类型参数将采用原始类型,默认是声明该参数时指定的第一个参数上限。
<span style="font-size:14px;">public class Shape<T extends Number> {
private T dataT;
public Shape() {
}
public Shape(T element) {
dataT = element;
}
public void setData(T element) {
dataT = element;
}
public T getData()
{
return dataT;
}
public static void main(String [] args )
{
Shape<Integer> sh = new Shape<>(2);
int data = sh.getData();
Shape sh2 = sh;
Number num = sh2.getData();
System.out.println("data = " + data);
System.out.println("num = " + num);
}
}
</span>
五、数组与泛型的比较数组是协变的也就是说子类的数组类型也是父类的数组类型。然而泛型中却不存在这种问题,也就说在泛型中子类Type1的Collection<Type1>对象既不是父类Type2的Collection<Type2>的子类型,也不是父类Type2的Collection<Type2>的类型,保证了数据的安全性。这是数组的内在缺陷,是危险的。
数组是具体化的,只有当数组运行的时候才会知道数组的数据类型,数组对其中的数据类型检查相对滞后。而泛型中存在擦除现象,泛型中存储的数据在运行时均被看做是Object类型,在变一阶段进行检测。
尤其需要注意的是反省数组是不存在的,这是一种非法形式。这是一种类型不安全的表现形式,而且数组违反了泛型系统的数据保证。
因此应该优先使用泛型,例如Collection类,通过类型参数而不是Object类型有更好的实用性。当然采用类型参数定义的类会出现警告,但是我们可以自己证明程序的安全性,并在尽量小的范围内禁用这些警告。总而言之,使用泛型比在应用该类的方法时的类型转换更加安全,使用更加容易。
我用数组自己实现的List,代码如下:
<span style="font-size:14px;">import java.util.Arrays;
public class SequenceList<T> {
// 默认数组长度
private int DEFAULT_SIZE = 16;
// 保存数组长度
private int capacity;
// 用于保存线性表的数组
private Object[] elementData;
// 当前存储数据个数
private int size = 0;
// 以默认长度创建存储数组
public SequenceList() {
elementData = new Object[DEFAULT_SIZE];
capacity = DEFAULT_SIZE;
}
// 存储数组长度不足时,数组拓展
private void ensure(int oldsize) {
if (oldsize > capacity) {
while (capacity < oldsize) {
capacity <<= 1;
}
elementData = Arrays.copyOf(elementData, capacity);
}
}
// 以某元素初始化存储数组
public SequenceList(T elemenT) {
this();
elementData[0] = elemenT;
size++;
}
// 以某元素及指定长度初始化存储数组
public SequenceList(T element, int initsize) {
capacity = initsize;
elementData = new Object[initsize];
elementData[0] = element;
size++;
}
// 获取线性表长度
public int length() {
return size;
}
// 获取线性表是否为空
public boolean empty() {
return size == 0;
}
// 获取指定索引的元素
public T get(int i) {
if (i > size - 1 || i < 0) {
throw new RuntimeException("索引越界");
}
return (T)elementData[i];
}
// 查找线性表中的元素位置
public int contains(T key) {
for (int i = 0; i < size; i++) {
if (elementData[i].equals(key)) {
return i;
}
}
return -1;
}
// 向线性表中添加元素
public void add(T element) {
Insert(element, size);
}
// 想存储数组中指定位置插入元素
public void Insert(T element, int index) {
if (index > size || index < 0) {
throw new IndexOutOfBoundsException("不存在该位置");
}
ensure( size + 1);
System.arraycopy(element, index, element, index + 1, size - index);
elementData[index] = element;
size++;
}
// 删除最后一个元素
public T remove() {
return delete(size - 1);
}
// 删除指定位置元素
public T delete(int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("线性表越界");
}
T oldValue = (T)elementData[index];
elementData[index] = null;
int key = size - index - 1;
if (key > 0) {
System.arraycopy(elementData, index + 1, elementData, index, key);
}
return oldValue;
}
// 线性表清空
public void clear() {
Arrays.fill(elementData, null);
}
// 输出
public String toString() {
if (size == 0) {
return null;
} else {
StringBuilder str = new StringBuilder();
str.append("[");
for (int i = 0; i < size; i++) {
str.append(elementData[i] + ",");
}
str.append("]");
return str.toString();
}
}
}
</span>