1.泛型的提出
先来看一段代码
//创建ArrayList集合,向其中添加字符串
ArrayList al = new ArrayList();
al.add("abc");
al.add("def");
al.add(new Integer(123));//①
Iterator it = al.iterator();
while(it.hasNext()){
String str = (String)it.next();
System.out.println(str);
}
编译通过,但是运行发现在①处报错了,因为在添加Integer对象后,输出时转成String对象就会出现类型转换错误。这样就会造成安全隐患,我们希望能在编译的时候就提示我们错误。所以可以对ArrayList进行类型定义。
ArrayList<String> al = new ArrayList<String>();
al.add("abc");
al.add("def");
//al.add(new Integer(123));编译报错,类型错误,String类型的ArrayList中无法添加Integer类型的对象
Iterator<String> it = al.iterator();//取出对象也定义String类型,就不需要再做强转了
while(it.hasNext()){
String str = it.next();
System.out.println(str);
}
这样就把运行时错误转移到编译时错误,因为我们已经对ArrayList集合中的元素类型做了定义,所以就无法向其中加入Integer类型的元素。
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
//ArrayList接口定义:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
//...省略掉其他具体的定义过程
}
在上述例子中,String就是类型实参,而ArrayList接口定义中的E就是类型形参,当给形参传入String的时候,就确定其具体类型。
2.泛型类、接口、方法
interface Inter<Q> {//泛型接口
public void show1(Q q);
}
public class ClassDemo<T, Q> implements Inter<Q>{//泛型类
private T t;
public void set(T t){
this.t = t;
}
public T get(){
return t;
}
public <W> void show(W w){//泛型方法
System.out.println("show:"+w.toString());
}
public void show1(Q q) {
// TODO Auto-generated method stub
System.out.println("show1:"+q);
}
}
public class Demo {
public static void main(String[] args) {
// TODO Auto-generated method stub
ClassDemo<String,Double> cd = new ClassDemo<String,Double>();
cd.set("abc");
//cd.set(new Integer(12));//类型不匹配
System.out.println(cd.get());
cd.show(new Integer(12));
cd.show1(new Double(123.123));
}
}
输出结果:
show:12
abc
show1:123.123
3.类型通配符
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<String>();
al.add("abc");
al.add("hehe");
ArrayList<String> al2 = new ArrayList<String>();
al2.add("abc2");
al2.add("hehe2");
printCollection(al);
printCollection(al2);
}
public static void printCollection(Collection<?> al){//使用Collection,LinkedList,HashSet,TreeSet...所有的集合都可以接收
Iterator<String> it = al.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
类型通配符?可以用来表示未知的引用类型
4.泛型的上限和下限
上限
public static void main(String[] args) {
ArrayList<Person> al1 = new ArrayList<Person>();
al1.add(new Person("abc",30));
al1.add(new Person("abc4",34));
ArrayList<Student> al2 = new ArrayList<Student>();
al2.add(new Student("stu1",11));
al2.add(new Student("stu2",22));
ArrayList<Worker> al3 = new ArrayList<Worker>();
al3.add(new Worker("stu1",11));
al3.add(new Worker("stu2",22));
ArrayList<String> al4 = new ArrayList<String>();
al4.add("abcdeef");
//al1.addAll(al4);//错误,类型不匹配。
al1.addAll(al2);
al1.addAll(al3);
System.out.println(al1.size());
}
查阅API文档,添加集合的泛型为boolean addAll(Collection< ? extends E> c),< ? extends E>表示可以接收E类型或者E的子类型对象,E是类型的上限。因为即使接收的类型是Student或Worker,但是addAll后都会类型提升为Person,而Person是可以用来接收Student和Worker的,并且在取出的时候也是Person类型的。
一般情况下,在存储元素的时候都是用上限,因为这样取出都是按照上限类型来运算的。不会出现类型安全隐患。
下限
public static void main(String[] args) {
TreeSet<Person> al1 = new TreeSet<Person>(new CompByName());
al1.add(new Person("abc4",34));
al1.add(new Person("abc1",30));
al1.add(new Person("abc2",38));
TreeSet<Student> al2 = new TreeSet<Student>(new CompByName());
al2.add(new Student("stu1",11));
al2.add(new Student("stu7",20));
al2.add(new Student("stu2",22));
TreeSet<Worker> al3 = new TreeSet<Worker>();
al3.add(new Worker("stu1",11));
al3.add(new Worker("stu2",22));
}
/*
* class TreeSet<Worker>
* {
* Tree(Comparator<? super Worker> comp);
* }
*/
class CompByName implements Comparator<Person>{
public int compare(Person o1, Person o2) {
int temp = o1.getName().compareTo(o2.getName());
return temp==0? o1.getAge()-o2.getAge():temp;
}
}
class CompByStuName implements Comparator<Student>{
public int compare(Student o1, Student o2) {
int temp = o1.getName().compareTo(o2.getName());
return temp==0? o1.getAge()-o2.getAge():temp;
}
}
class CompByWorkerName implements Comparator<Worker>{
public int compare(Worker o1, Worker o2) {
int temp = o1.getName().compareTo(o2.getName());
return temp==0? o1.getAge()-o2.getAge():temp;
}
}
在上面的例子中,我们创建了三个TreeSet集合,并分别向其中存入Person、Student、Worker类型的元素。查看API文档中关于TreeSet构造函数的说明:public TreeSet(Comparator< ? super E> comparator)构造一个新的空 TreeSet,它根据指定比较器进行排序。所以为他们分别创建比较器并作为构造函数的参数进行传递。但是观察发现其实Student和Worker的比较器都是依赖于Person的比较器。而如果我们想要把集合中的元素取出来比较以确定其位置,就要对取出来的元素进行接收,这里就是由比较器接收。不管我们存放的是什么类型的元素,我们在接收的时候,可以用该类型来接收,也可以用该类型的父类型来接收。就是说如果我们在al1集合中存入Person和Student类型的元素,在取出来进行比较的时候,接收的时候需要用Person类型来进行接收以保证所有取出来的类型都能接收到。
一般情况下,通常对集合中的元素进行取出操作时,可以使用下限。这样可以保证所有取出来的类型都能进行接收