java基础知识系列---泛型

1.实现泛型特性构件pre-java5

面向对象的一个重要目标是对代码重用的支持。支持这个目标的一个重要的机制就是泛型机制:如果除去对象的基本类型之外,实现方法是相同的,那么我们就可以用泛型实现来描述这种基本的功能。

在java1.5版本以前,java并不直接支持泛型实现,泛型编程的实现是通过使用继承的一些基本概念来完成的。

1.1使用Object表示泛型

java中的基本思想就是可以通过使用想Object这样适当的超类来实现泛型。代码段1:

 1 package com.sgl.info;
 2 
 3 public class MemoryCell {
 4     
 5     private Object storeValue;
 6 
 7     public Object read() {
 8         return storeValue;
 9     }
10 
11     public void write(Object x) {
12         this.storeValue = x;
13     }
14 }

代码段2:

package com.sgl.info;

public class TestMemoryCell {
    public static void main(String[] args) {
        MemoryCell m = new MemoryCell();
        m.write("37");
        String value = (String) m.read();
        System.out.println("value:" + value);
    }
}

 

当我们使用这种策略的时候。有俩个细节必须要考虑。

1.在代码段2中,他描述了一个main方法,该方法把37写到TestMemoryCell 对象中,然后又从TestMemoryCell对象中读取出来。为了访问这种对象的一个特定方法,必须要强制转换成正确的类型。(该列子中不必进行类型转换) 

2.不能使用基本数据类型。只有引用数据类型能够与Object相容。

接下来我们来讨论下这个问题的标准。

1.2 基本类型的包装

当我们实现某个程序的时候,常遇到语言定型问题:我们已有一种类型的对象,可是语言的语法却需要一种不同类型的对象。

java中我们会使用包装类这个基本主题。一种典型的用法就是:存储一个基本的类型,并添加一些这种基本类型不支持或者不能正确支持的操作。

在java中,虽然每种引用类型都能与Object类型相容,但是8中基本数据类型却不能。于是,java中为每种基本类型都提供了一个包装类。如下图所示:

每一个包装对象都是不可变的(它的状态决不能改变),它存储一种当该对象被构建时所设置的原值,并提供一种方法可以重新得到该值。

代码段3:

MemoryCell m = new MemoryCell();
m.write(new Integer(37));
Integer IValue=(Integer) m.read();
int value=IValue.intValue();//使用包装类中的静态方法获取原值
System.out.println("value:" + value);

1.3 使用接口类型表示泛型

 只有在使用Object类中已有的那些方法能够表示所执行的操作的时候,才能使用ObJect作为泛型类型来工作。例如:

  考略在由一些项组成的数组中找出最大项的问题。基本的代码是类型无关的,但是它的确需要一种能力来比较任意俩个对象,并且确定哪个是大的,哪个是小的。因此我们不能直接找出Object的数组中的最大元素---我们需要更多的信息。一个简单的想法就是找出Comparable的数组中的最大元。要确定顺序,可以使用compareTo方法,它对所有的Comparable都必然是现成可用的。

  几个忠告:

首先,只有实现Comparable接口的那些对象才能够作为Comparable数组的元素被传递。仅有compareTo方法但并未宣称实现Comparable接口的对象不是Comparable的,它不具有必须的IS-A关系。

第二,如果Comparable数组有俩个不相容的对象(一个String,一个Boolean),那么compareTo方法将会抛出异常ClassCastException,这是我们期望的性质。

第三,基本类型不能作为Comparable传递,但是包装类可以,因为他们实现了Comparable接口。

第四,接口究竟是不是标准的库接口倒不是必须的。

最后,就是用接口类型表示泛型这个方案不是总能够行的通,因为有时宣称一个类实现所需的接口是不可能的。例如:

一个类是库中的类,而接口是用户自定义的接口。

如果一个类是final类,我们就不可能扩展它以创建新的类。

1.4 数组类型的兼容性

 语言设计的苦难之一是你如何处理集合类型的继承的问题。对于Student IS-A Person.那么,是不是也意味着Student[] IS-A Person[]?换句话说,如果一个方法接受Person[]作为参数,那么我们能不能把Student[]作为参数传递呢?

乍一看,这个问题不值得疑问,似乎Person[]就是应该是和Person[]类型兼容的。然而,这个问题却比想像中要复杂。假设除了Student外,还有Employer IS-A Person,并设置Employer []是和Person[]类型兼容的,考略下面俩个赋值语句:

Person[] arr=new Student[5];//编译arr是数组
arr[0] =new Employer();//编译:Employer IS-A Person

俩句都编译,而实际上arr[0]是引用一个Student,可是 Employer IS-NOT-A Student.这样就产生了类型混乱。运行时系统不能抛出ClassCastException异常,因为不存在类型转换。

避免这种问题的最容易的方法是指定这些数组不是类型兼容的。可是,java中数组确是类型兼容的。这叫做:协变数组类型。

2 利用泛性实现泛型特性成分

接下里将描述如何编写泛型类和泛型方法。

2.1 简单的泛型类和接口

断码段4:

 1 package com.sgl.info;
 2 
 3 public class GenericMemoryCell<AnyType> {
 4     private AnyType storeValue;
 5 
 6     public AnyType read() {
 7         return storeValue;
 8     }
 9 
10     public void write(AnyType x) {
11         storeValue = x;
12     }
13 }

上述代码为MemoryCell的泛型实现。

当指定一个泛型时,类的声明则包含一个或者多个类型参数,这些参数被放在类名后面的一对尖括号里面。在这个例子中,对参数没有明显的限制,所以用户可以创建像

 GenericMemoryCell<String>和GenericMemoryCell<Integer>这样的类型,但是不能创建GenericMemoryCell<int>这样的类型。

也可以声明接口是泛型的。在java5中,Comparable接口是泛型的。现在String类实现Comparable<String>并没有一个compareTo方法,这个方法以一个String作为其参数。通过使类变成泛型类,以前只有在运行时才能报告的许多错误现在变成了编译时的错误。

2.2 自动装箱/拆箱

代码段3中代码写的很麻烦,因为使用包装类需要在调用write之前创建Integer对象,然后才能使用intValue方法从Integer中提取int值。在java 5以前这是需要的,但在java 5之后矫正了这种情况。如果int型量被传递到需要一个Integer对象的地方,那么,编译器将在幕后插入一个对Integer=构造方法的调用。这就叫自动装箱。反之就是拆箱。

2.3 带有限制的通配符

代码段5:

 1 public static double totalArea(Shape[] arr)
 2     {
 3         double total =0 ;
 4         for (Shape shape : arr) {
 5             if (shape!=null) {
 6                 total +=shape.area();
 7             }
 8         }
 9         return total;
10     }

上述代码是用来描述计算一个Shape数组的总面积的。假设我们想要你重写这个方法,使得该方法能够使用Collection<Shape>这样的参数。当前唯一重要的是它能够存储一些项,而且这些项可以用一个增强的for循环来处理。由于是增强的for循环,因此代码是相同的,最后的结果如代码段6所示:

 1 public static double totalArea(Collection<Shape> arr)
 2     {
 3         double total =0 ;
 4         for (Shape shape : arr) {
 5             if (shape!=null) {
 6                 total +=shape.area();
 7             }
 8         }
 9         return total;
10     }

如果传递一个Collection<Shape>,那么程序会正常运行。可是,要是传递一个Collection<Circle>会发生什么情况呢?(Circle是继承Shape的类)

答案依赖于是否Collection<Circle> IS-A Collection<Shape>。

之前提到过java中数组是协变的,于是Circle[] IS-A Shape[].一方面这种一致性意味着,如果数组是协变的,那么集合也将是协变的。另一方面,数组的协变性代码得以编译,但会产生一个运行时异常(ArrayStoreException)。因为使用泛型的全部原因就是在于产生编译器错误而不是类型不匹配的运行时异常,所以泛型集合不是协变的。

因此不能将Collection<Circle>作为参数传递到代码段6中的方法中。

问题在于,泛型(以及泛型集合)不是协变的,而数组是协变的。如果没有附加的语法,则用户就会避免使用集合,因为失去协变性使得代码缺少灵活性。

java 5用通配符来弥补这个不足。通配符用来表示参数类型的子类(或者超类)。代码段7描述带有限制的通配符的使用。

 1 public static double totalArea(Collection<? extends Shape> arr)
 2     {
 3         double total =0 ;
 4         for (Shape shape : arr) {
 5             if (shape!=null) {
 6                 total +=shape.area();
 7             }
 8         }
 9         return total;
10     }

代码段中将编写一个将Collection<T>作为参数的方法totalArea,其中T IS-A Shape.因此,Collection<Circle>和Collection<Shape>都是可以接受的参数。关于通配符还有许多其他的语法,此处就不详细说明了。

 

posted @ 2016-08-27 10:34  爪哇小生  阅读(1083)  评论(0编辑  收藏  举报