您的当前位置:首页为什么要使用泛型程序

为什么要使用泛型程序

2024-12-11 来源:哗拓教育

一个常用的简单的集合ArrayList引发的思考

  我们现在想往集合中放一些东西,比如一本书的几个属性(当然我们可以直接放这个对象就好了)。包括书名,价格,出版日期。最简单的实现看起来会是这个样子:

ArrayList arr=new ArrayList();
arr.add("java in action");
arr.add(89.5);
arr.add(new Date());

  写完了。存到了一个列表。可能在程序经过一系列复杂的函数调用后,现在要做的事是逐个取出这个列表的3个元素。这个时候我们可能已经忘了当时存放第一个或后面的元素的类型是什么类型了。

  也许你会说没关系,Object object = arr.get(0);先得到一个Object 再强转。没问题,但是你难道不用去看原来每个元素中存的什么类型吗?万一你记错了,看错了呢?对不起,等待你的是一堆的classCastException。也许你经过一系列的调整,都解决了。这种强制类型转换一大堆的代码,技术leader能放行,这个段代码运行在生产环境吗?要是以后有业务调整,你走了,新来的员工接着继承你"伟大的事业”吗?

  但是如果我们稍作改动,将这个list做一些限制。只让它存String类型的元素。这个看起来会比较完美。经过规范后,看起来会是这样的:

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

  我们再也不用担心类型的问题了,放心大胆的存就好了,一旦存的不是String类型的,编译器会自动提示我们。有的人会说了,我这几个属性不是String类型的,你让我怎么存?实在要这样存,转成String也不是不可以,更多是我们发现我们这样存的方式不合理,应该存一个书的对象,获取选取别的数据结构什么?哈哈。。。绕回去了。但总之,说明了泛型给我们至少带来的2点好处。

1使程序具有更好的安全性
2可读性更高了

  接下来我们要是思考的是如何运用?如何能够成为一个优秀的泛型程序员,以至于我们既能够在没有过多的限制以及混乱的错误下做所有的事,并且能够预测出所用的类和方法未来可能的用途?

泛型设计分为三个级别:
1,泛型类(大多数程序员的级别)
2,泛型方法
3,类型变量的限定

泛型类

public class Pair <T> {
    private T first;
    private T second;
    public Pair() { first=null, second=null; }
    public Pair(T first ,T second){ this.first=first; this.second =second; }
    public T getFirst(){ return first; }
    public T getSecond(){ return second; }
    public void setFirst(T newValue ){ first=newValue; }
    public void setSecond(T newValue){ second =newValue; }
}

  以上例子,引入了一个类型变量T,泛型可以有多个类型变量。如:public class Pair<T,U>{ ... }
第一个域是T,第二个域是U,类定义的类型变量指定了方法的返回类型,域,局部变量的类型。

类型变量的表示:
E:表示集合
K,V:表示键和值
T(U,S):表示任意类型

  具体的类型替换类型变量就可以实例化泛型类型,例如:Pair<String>实例化的构造函数就成为了 :

public Pair(String first ,String  second){ this.first=first; this.second =second; }

  换句话说,泛型类型可看作普通类的工厂。

泛型方法

class ArrayAlg {
    public static <T> T getMiddle(T... a){
        return a[a.length/2; ]
    }
}

  类型变量放在修饰符的后面,返回类型的前面。调用一个泛型方法时,在方法名前的<>中放入具体的类型:

String middle = ArrayAlg.<String>getMiddle("john","dog","cat");

  方法调用大部分情况可以省略类型变量。编译器可以推断出调用的方法,如:

String middle = ArrayAlg.getMiddle("john","dog","cat");

  但是形如这样的调用,编译器也会提示错误,如:

double middle =ArrayAlg.getMiddle(3.14,12,55);

  编译器会自动打包参数为1个Double和2个Interger对象,然后寻找这些类共同的超类。事实上,找到2个这样的超类型:Number和Comparable接口,其本身也是一个泛型类型。在这种情况下,可以采取补救措施是将所有的参数写为double值。

类型变量的限定

  有时,类或者方法需要对类型变量加以约束。我们来看一下一下这个例子:

class ArrayAlg{
    public statis <T> T min(T[] a){
        if(a==null || a.length==0 ) return null;
        T smallest=a[0];
        for(int i=1;i<a.length;i++){
             smallest =a[i];
        return smallest;
        }
    }
}

  现在该泛型方法使用了一个类型变量T,也就是说,任意类型都可以。但是怎么确定T所属的类有compareTo方法呢?答案是将T限制为实现了Comparable接口的类。形如:

public static <T extends Comparable> T min(T[] a)...

  也许你现在会感到好奇——不是说实现吗?为什关键字使用extends 而不是implements?毕竟,Compareble是一个接口。T可以是类,也可以是接口。选择关键字extends的原因是更接近子类的概念,并且java的设计者也不打算在语音中再添加一个新的关键字。

  一个类型变量或通配符可以有多个限定,例如:

T extends Comparable&Serializable

  限定类型用&分割,而逗号用来分隔类型变量。在Java的继承中,可以根据需要拥有多个接口超类型,但限定中至多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个。

显示全文