Java笔记 - 泛型

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类型来进行接收以保证所有取出来的类型都能接收到。

一般情况下,通常对集合中的元素进行取出操作时,可以使用下限。这样可以保证所有取出来的类型都能进行接收

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值