1.实现泛型构件pre-Java5
面向对象的一个重要的目标是对代码重用的支持。支持这个目标的一个重要的机制就是泛型机制(generic mechanism):如果除去对象的基本类型外,实现方法是相同的,那么我们就可以用泛型实现(generic implementation)来描述这种基本的功能。例如,可以编写一个方法,将由一些项组成的数组与被排序的对象的类型无光,此时可以使用泛型方法。
1.1使用Object表示泛型
java中的基本思想就是可以通过使用Object这样适当的超类来实现泛型类。
如下1-5 MemoryCell类就是一个例子。
public class MemoryCell{
public Object read( ){ return storedVaule;}
public Void write( Object x ){ storedValue = x; }
private Object storedValue;
}
1-5 泛型MemoryCell类
当我们使用这种策略时,有两个细节必须要考虑。第一个细节在1-6中阐释,他描述一个main方法,该方法吧串“37”写到MemoryCell对象中,然后又从MemoryCell对象读出。为了访问这种对象的一个特定方法,必须要强制转换成正确的类型(当然,这个例子中可以不必进行强制转换,因为在程序的第9行可以调用toString()方法,这种调用对任意对象都是能够做到的)
public Class TestMemoryCell{
public static void main( String [ ] args ){
MemoryCell m = new MemoryCell( );
m.write("37");
String val = (String) m.read();
System.out.println("Contents are:" + val);
}
}
1-6 使用泛型MemoryCell类
1.2基本类型的包装
当我们实现算法的时候,常常遇到语言定型的问题:我们已有一种类型的对象,可是语言的语法却需要一种不同类型的对象,可是语言的语法却需要一种不同类型的对象。
这种技巧阐释了包装类(wrapper class)的基本主题。一种典型的用法是存储一个基本的类型,并添加一些这种基本类型不支持或不能正确支持的操作。
在Java中我们已经看到虽然每一个引用类型都和Object相容,但是,8种基本类型中却不能。于是,Java为这8种基本类型中的每一种都提供了一个包装类。例如:int类型的包装类是Integer。每一个包装对象都是不可变的(就是说它的状态不能改变),它存储一种当前对象被构建时所设置的原值,并提供一种方法以重新得到该值。包装类也包含不少静态实用方法。
public class WrapperDemo{
public static void main( String [ ] args ){
MemoryCell m = new MemoryCell( );
m.write( new Integer(37));
Integer wrapperVal = (Integer)m.read();
int val = wrapperVal.intValue();
System.out.println( "Contents are:" + val );
}
}
1-7 Integer 包装类的一种演示
1.3 使用接口类型表示泛型
只有在使用Object类中已有的那些方法能够表示执行的操作的时候,才能使用Object作为泛型来工作。
Class FindMaxDemo{
/**
* Return max item in arr.
* Precondition: arr.length > 0
* /
public static Comparble findMax( Comparable [ ] arr){
int maxIndex = 0;
for ( int i = 1; i < arr.length; i++)
if( arr[ i ].compareTo( arr[ maxIndex ] ) > 0 )
maxIndex = i;
return arr [ maxIndex ];
}
/**
*Test findMax on Shape and String objects.
*/
public static void main( String [ ] args ){
//Circle,Square,Rectangle.都是Shap的子类。
Shap [ ] sh1 = { new Circle( 2.0 ), new Square( 3.0 ), new Rectangle( 3.0,4.0 ) };
String [ ] st1 = { "Joe","Bob","Bill","Zeke" };
System.out.println( findMax( sh1) );
System.out.println( findMax( st1 ) );
}
}
1-8 泛型 findMax 例程,使用Shape 和 String 演示(pre-Java 5)
最后,这个方案不是总能行得通,因为有时宣称一个类实现所需的接口是不可能的。例如,一个类可能是库存中的类,而接口却是用户定义的接口。如果一个类是final类,那么我们就不能扩展它以创建一个新的类。
1.4 数组类型的兼容性
语言设计中的困难之一是如何处理集合类型的继承问题。设Employee IS-A Person(IS-A的意思:如果A类 IS-A B类。则B类是A类的父类)。那么是不是也意味着Employee[ ] IS-A Person[ ]呢,如果一个例程接受Person[ ]作为参数,那么我们能不能把Employee[ ]作为参数,那么我们能不能把Employee[ ]作为参数来传递呢?
乍一看,该问题不值得一问,似乎Employee[ ] 就应该是和Person[ ] 类型兼容的。然而,这个问题却要比想象的复杂。假设Employee外,我们还有Student IS-A Person,并设Employee[ ]是和Person[ ]类型兼容的。此时考虑下面两条赋值语句:
Person[ ] arr = new Employee[ 5 ]; //编译:arrays are compatible
arr[ 0 ] = new Student(...); //编译:Student IS-A Person
两句都编译,而arr[ 0 ] 实际上引用一个Employee,可是Student IS-NOT-A Employee。这样就产生了类型混乱。运行系统(runtime system)(Java 虚拟机)不能抛出ClassCastException异常,因为不存在类型转换。
避免这种问题的最容易的方法是指定这些数组不是类型兼容的。可是,在Java中数组确实类型兼容的。这叫作xxie协变数组类型(covariant arraay type)。每个数组都表明了他所允许储存对线的类型。如果将一个不兼容的类型插入到数组中,那么虚拟机将会抛出一个ArrayStoreException异常
1.5 利用Java5泛型特性实现泛型构件
Java5支持泛型类,这些类很容易使用。然而,编写泛型类却需要多一些工作。本节将叙述编写泛型类和泛型方法的基础。
1.5.1 简单的泛型类和接口
1 public class GenricMemoryCell<AnyType>{
2 public AnyType read( ){
3 return storedValue;
4 }
5 public void write( AnyType x ){
6 storedValue = x;
7 }
8
9 private AnyType storedValue;
10 }
1-9 Java5 版本的Comparable接口,他是泛型接口
当指定一个泛型类时,类的声明则包含一个或多个类型参数,这些参数被放在类名后面的一对尖括号内。第一行指出,Generic-MemoryCell有一个类型参数。在这个例子中对类型参数没有明显的限制,所以用户可以创建像GenericMemoryCell< String >和GenericMemoryCell< Integer >这样的类型,但是不能创建GenericMemoryCell< int >这样的类型(不能用基本类型)。在GenericMemoryCell类声明累不,我们可以声明泛型类型的域和使用泛型类型作为参数或返回类型的方法
例如在1-9的第五行,类GenericMemoryCell< String >的write方法需要一个String类型的参数。如果传递其他参数那将产生一个错误的编译。
public interface Comparable< AnyType >{
public int compareTo( AnyType other);
}
1-10 Java5版本的Comparable接口,他是泛型接口
也可以声明接口泛型的,例如在Java5以前,Comparable接口不是泛型的,而他的compareTo方法需要一个Object作为参数。于是,传递到compareTo方法的任何引用变量即使不是一个合理类型也都会编译,而只是在运行时报告ClassCastException错误。
1.5.2 自动装箱/拆箱
如果一个int型量被传递到需要一二Integer对象的地方,那么,编译器将在幕后插入一个对Integer构造方法的调用。这叫自动装箱。如果一个Integer对象被放入到需要int型量的地方,则编译器将在幕后插入一个对intValue方法调用,这就叫作自动装箱。对于其他7对基本类型/包装类型,同样会发生类似的情形。
class BoxingDemo{
public static void main( String [ ] args ){
GenericMemoryCell< Integer > m = new GenericMemoryCell< Integer >( );
m.write(37);
int val = m.read();
System.out.println( "Contents are: " + val );
}
}
1-11a 自动装箱和拆箱(Java5)
1.5.3 菱形运算符
在1-11a中,第五行有些烦人,因为既然m是GenericMemoryCell< Integer >类型的,显然创建的对象也必须是GenericMemoryCell< Integer > 类型的,任何其他类型的参数都会产生编译错误。Java7增加了一种新的语言特效,称为菱形运算符,使得第5行可以改写为:
GenericMemoryCell < Integer > m = new GenericMemoryCell<>();
菱形运算符在不增加开发者负担的情况下简化了代码,我们通篇都会使用到他,1-11b给出了带菱形运算符的Java7版代码。
class BoxingDemo{
public static void main( String [ ] args ){
GenericMemoryCell< Integer > m = new GenericMemoryCell< >( );
m.write(37);
int val = m.read();
System.out.println( "Contents are: " + val );
}
}
1-11b 自动装箱和拆箱(Java7,使用菱形运算符)