泛型程序设计不管是在我们作为学生的理论作业中,还是实际应用中,都有着广泛的应用。这主要取决于泛型程序设计的代码具有高度的复用性与可扩展性,我们可以在框架允许的基础上添加一些新的内容。以下是我结合课外资料作出的一些关于泛型的简要总结。
1. 泛型设计的优点
泛型,即代码块可以被很多不同类型的对象所重用的一种设计语法。最典型的例子就是在容器ArrayList中可以添加任意类型的对象与内容(当然ArrayList的每一小块都要求类型相同)。
而作为一种新型的引用,自然就需要同之前的方法进行比较。在Java增加泛型类之前,泛型程序设计是通过继承实现的。以ArrayList类为例:
public class ArrayList
{
private Object[] elementData;
...
public Object get(int i) { ...}
public void add(Object o) {...}
}
可以看出,在泛型类被设计出来之前,ArrayList是通过引用Object型的数组实现的。而这样做的话就会出现一个问题:必须使用强制类型转换,将Object类型修改为其他类型,如:
ArrayList str = new ArrayList();
... //向数组中赋String类型的值
int first = (String)str.get(0);
而这样的话就可以向数组列表中添加其它类型的对象。虽然编译运行不会出错,但是如果在其他地方将get的结果强制转换,就会出错。
为了解决这一问题,泛型类使用“类型参数”来指示ArrayList中元素的类型,即我们现在看到的形式:
ArrayList<String> str = new ArrayList<>();
这样做的好处就是他人可以直观地看出数组中元素的类型,而编译器也可以通过类型参数的声明来确定方法返回值的类型。
总的来说,泛型设计可以提高程序的可读性与编译时的安全性。
2. 泛型类
泛型类即具有一个或多个类型变量的类,用户在使用时可以通过自身需要对类型变量进行明确定义或修改。
泛型类的定义主要利用放置类型变量的<>实现,并在类中定义实例域或者方法时根据需要选择是否使用这一未确定的类型变量作为数据或方法的类型。举例如下:
public class Storage<T>
{
//实例域
private T thing1
private T thing2;
private T thing3;
//构造器
public Storage()
{
thing1 = null;
thing2 = null;
thing3 = null;
}
public Storage(T t1, T t2, T t3)
{
thing1 = t1;
thing2 = t2;
thing3 = t3;
}
//类方法
public T getThing1() { return thing1; }
public T getThing2() { return thing2; }
public T getThing3() { return thing3; }
public void setThing1(T t1) { thing1 = t1; }
public void setThing2(T t2) { thing2 = t2; }
public void setThing3(T t3) { thing3 = t3; }
}
该类引用类型变量T,放在类名的后面,可以表示任意类型
此外,泛型类可以存在多个类型变量,通过“,”隔开.
public class Storage<T, U>
而客户端在使用时,就可以使用具体的类型来替换类型变量,如:
Storage<String>
构造器与类方法的类型变量都可以使用具体的类型进行替换。这样做就可以实现同一代码块的不同用处,即代码的复用性。
3. 泛型方法
除了定义泛型类来使代码可以多出适用之外,还可以定义泛型方法,如:
class ArrayAlg
{
public static <T> T getMiddle(T... a)
{
return a[a.length / 2];
}
}
这个方法实在普通类中定义的,但它是一个泛型方法,在定义泛型方法时,类型变量要求放在修饰符的后面,返回类型的前面。
用户在使用泛型方法时,也是将类型变量替换为具体类型即可:
String middle = ArrayAlg.<String>getMiddle("One", "Two", "Three");
在多数情况下,调用泛型方法可以省略类型变量所表示的类型参数。因为编译器可以通过方法调用的对象类型来判断类型参数,比如“One”,“Two”,“Three”一定是String类型的对象,所以编译器会将T判定为String。
但是,如果传递的形式参数中的对象类型不相同,编译器就会给出错误提示。
泛型方法的设计使得一个类中的方法调用更加灵活,也为用户提供了多种选择。
4. 泛型设计的局限性
泛型设计虽然方便,但也存在些许限制。
- 不能用基本类型实例化类型参数
类型变量不能被基本类型所替换,因此如果想要通过泛型类或泛型方法得到基本类型的数组或对象,就需要对基本类型进行包装。即int->Integer; double->Double - 不能创建参数化类型的数组
如:Storage<String> [] table = new Storage<String>[10];
因为在经过类型擦除后,table的类型可以转换为Object[],数组会记住它的元素类型,如果试图存储其他类型的元素,就会抛出异常。 - 不能实例化类型变量
不能使用像new T(), new T[]或T.class这样的表达式中的类型变量,如:public Storage() { thing1 = new T(); thing2 = new T();}
类型擦除会将T变为Object,这会违反设计类的本意。因此解决方法为调用一个构造器表达式:Storage<String> s = Storage.makeStorage(String::new);
makeStorage方法接受一个函数式接口Supplier<T>
,表示一个无参数且返回值为T的函数。然后通过Class.newInstance方法来构造泛型对象