泛型
#泛型的概念
Java泛型(generics)是JDK 5中引入的一个新特性,泛型的本质是参数化类型,也就是说在定义类、接口和方法时,其所操作的数据类型被指定为一个参数,分别称为泛型类、泛型接口、泛型方法。声明的类型参数在使用时用具体的类型来替换,这就像方法中的形参和实参之间的关系。
假设有下面三个类的定义:
class Fruit{}
class Apple extends Fruit{}
class Orange extends Fruit{}
某段代码如下:
//List是一个接口,用来表示某种有序的集合。可以对列表中每个元素的插入位置进行精确地控制。
//而且可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
//ArrayList是大小可变的数组对List接口的实现
//我们创建一个box用来专门存放Apple
List box = new ArrayList();//使用add在Box中所添加的任何对象都会转换为Object对象而存放
box.add(new Apple());//在Box中插入一个Apple对象
box.add(new Orange());//在Box中是可以插入一个Orange对象的
Apple apple = box.get(0);//get方法获取到的对象为Object对象,因此,本条语句会出错,如下图
apple = (Apple)box.get(0);
apple = (Apple)box.get(1);
代码经过如下修正:
Apple apple = (Apple)box.get(0);//get方法获取到的对象为Object对象,这里经过了强制转换,如下图:
apple = (Apple)box.get(0);
apple = (Apple)box.get(1);//在第1个位置,我们前面代码中放的是一个Orange对象,在运行时会出错
运行结果如下:
我们从运行结果可以看到上述的代码在编译的时候是无法防止box中放入其他种类的水果的,只能在运行的时候才能发现类型的转换问题,此时是不是已经晚了?!
如果我们把上面的代码用泛型来实现,如下:
//List接口的定义原型为List<E>,其中E就是一个形式上类型参数
//List<Apple>中的Apple为实际类型
List<Apple> box = new ArrayList<Apple>();
box.add(new Apple());
box.add(new Orange());//此时再加入Orange对象则会在编译的时候就能发现错误,见下图:
Apple apple = box.get(0);//因为box的类型为List<Apple>,所以get方法的返回值类型为Apple类型,而不是Object类型
从上面我们看到,使用泛型以后,我们再在box中加入其他非Apple的对象就在编译的时候就得到了检查。
如果上例改成如下的形式呢?
List<Fruit> box = new ArrayList<Fruit>();
box.add(new Apple());
box.add(new Orange());
通过编译及运行,我们看到完美通过。那若再改成下面的代码呢:
List<Fruit> fruitBox;
List<Apple> appleBox = new ArrayList<Apple>();
fruitBox = appleBox;//此句会产生错误,
上面的代码再编译时会产生下面的错误: 我们可以了解到
List<Fruit>
和List<Apple>
是完全不同的两个类型。
#泛型的定义
类型参数的命名风格为:用简练的名字作为形式类型参数的名字(如果可能,单个字符),最好避免小写字母,比如:T、E等。类型参数表用尖括号括起来,多个类型参数之间用逗号隔开。泛型的定义可以应用在接口、类、方法。 示例如下:
interface A<T,E>{//定义泛型接口、类,类型参数表在接口名、类名之后
public <T,E> T getT(T t,E e);//类型参数表在存取权限之后
}
class B<T,E> implements A<T,E>{
public <T,E> T getT(T t,E e){
return t;
}
}
class TestGenerics{
public static void main(String[] args) {
B<String,Integer> b = new B<String,Integer>();//
System.out.println(b.getT("abc",1));
}
}
#泛型的子类
在前面的例子中,我们已经看到了List<Fruit>
和List<Apple>
是完全不同的两个类型。再通用些,如果类型A是类型B的子类型,那C<A>
和 C<B>
之间没有任何关系。有没有可能将C<A>
类型的实例赋给一个C<B>
类型的变量呢?答案是可以,只不过需要改一改C<B>
的形式。我们定义类型C<? extends B>
,其中?
表示某个类继承自(实现)B
。我们将前面的代码经过改造如下:
List<? extends Fruit> fruitBox;
List<Apple> appleBox = new ArrayList<Apple>();
fruitBox = appleBox;
则代码无错通过,其中List<? extends Fruit>
可以看作是List<Apple>
的父类。
有没有可能将C<B>
类型的实例赋给一个C<A>
类型的变量呢?答案也是可以的,我们仍然需要改一下C<A>
的形式。我们定义类型C<? super A>
,其中?
表示A继承(实现)自它。
List<Fruit> fruitBox = new ArrayList<Fruit>();
List<? super Apple> appleBox = fruitBox;
代码无错通过,其中List<? super Apple>
可以看作是List<Fruit>
的子类。
#通配符
通配符?
除了可以用在上述的泛型子类的场合,它还可以表示任意实际类型参数,示例:
class Info< T> {
private T var ; // 定义泛型变量
public void setVar(T var) {
this.var = var ;
}
public T getVar() {
return this.var ;
}
public String toString() { // 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo {
public static void main(String args[]) {
Info< String> i = new Info< String>() ; // 使用String为泛型类型
i.setVar("it") ; // 设置内容
fun(i) ;
}
public static void fun(Info< ?> temp) { // 可以接收任意的泛型对象
System.out.println("内容:" + temp) ;
}
};
#泛型数组
示例
public class GenericsDemo1<T> {
public static void main(String args[]) {
Integer integer[] = fun1(1, 2, 3, 4, 5, 6) ; // 返回泛型数组
fun2(integer) ;
}
@SafeVarargs//如果把这个标记去掉,会产生如下图所示的警告
public static <T> T[] fun1(T...arg) { //这种定义形式可以将实参传递过来的参数组成数组
return arg ; // 返回泛型数组
}
public static <T> void fun2(T param[]) { // 输出
System.out.print("接收泛型数组:") ;
for (T t : param) {
System.out.print(t + "、") ;
}
}
};
#泛型示范
#Comparator
前述的Arrays
中的普通的sort
方法为升序排序,但也提供了一个可以使用Comparator
的排序,利用Comparator
既可以升序也可以降序排序。
从public interface Comparator<T>
可以看到,它是一个接口,它在java.util
包中,其中有两个方法,分别为:
int compare(T o1, T o2)
boolean equals(Object obj)
第二个方法在实现Comparator
的类中,一般不需要实现,这是因为所有类都继承自Object
,而Object
中有equals
方法,可以视同对Comparator
中equals
的实现。 我们需要实现的是compare
方法,如果参数的类型不允许此 Comparator 对它们进行比较,则会抛出ClassCastException
。
该方法的实现比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。 设符号
sgn(expression)
表示符号数学函数,根据expression
的值为负数、0 还是正数,该函数分别返回 -1、0 或 1。实现程序必须确保对于所有的 x 和 y 而言,都存在sgn(compare(x, y)) == -sgn(compare(y, x))
。(这意味着当且仅当 compare(y, x) 抛出异常时 compare(x, y) 才必须抛出异常。)
实现程序还必须确保关系是可传递的:((compare(x, y)>0) && (compare(y, z)>0)) 意味着 compare(x, z)>0。
最后,实现程序必须确保 compare(x, y)==0 意味着对于所有的 z 而言,都存在 sgn(compare(x, z))==sgn(compare(y, z))。
虽然这种情况很普遍,但并不 严格要求
(compare(x, y)==0) == (x.equals(y))
。一般说来,任何违背这个条件的Comparator
都应该清楚地指出这一事实。推荐的说法是“注意:此 Comparator 强行进行与 equals 不一致的排序。”
示例:
import java.util.Random;
import java.util.Arrays;
import java.util.Comparator;
class TestComparator{
//顶一个Integer的数组
static Integer[] intArray = new Integer[10];
//对该数组进行初始化
static{
Random random = new Random();
for(int i = 0; i < intArray.length; i++){
intArray[i] = random.nextInt(10);
}
}
public static void main(String[] args) {
//先输出未排序的数组状态
for(int x:intArray){
System.out.print(x + " ");
}
//通过使用Comparator对数组进行降序排序
Arrays.sort(intArray, new Comparator<Integer>(){
public int compare(Integer t1, Integer t2){
return t2 - t1;//t1>t2,返回负值,反之。它的返回值就使得排序的结果为降序
}
});
System.out.println();
//输出排序后的数组状态
for(int x:intArray){
System.out.print(x + " ");
}
}
}