一、泛型上限

1、迭代并打印集合中的元素

(1)集合既可能是List,也可能是Set,用Collection提高扩展性

(2)当容器中存放的元素不确定,且里面不准备使用具体类型的情况下,使用通配符

注:

(1)通配符:?,未知类型。不明确类型时,可以用?来表示,意味着什么类型都可以传入

(2)运行时,参数中的泛型<>只要有一个类型具备了,里面就都是统一类型

    /**
     * 迭代并打印集合中的元素
     * (1)使用Collection提高扩展性
     * (2)容器中存放的元素不确定,且里面不准备使用具体的类型,使用通配符?,意味着什么类型都可以传入
     */
    public static void printCollection(Collection<?> coll){
        for (Iterator<?> it = coll.iterator(); it.hasNext(); ){
//            String str = it.next();   //不使用具体的类型
            //不能明确类型
            System.out.println(it.next());
        }
    }

2、<T>与<?>的区别

(1)<T>:如果有指定<T>,就意味着<T>能代表一个具体类型,并能被操作(<T>可以被接收)

(2)<?>:仅在不明确类型,且不对返回值类型进行操作时,用?来表示。调用的是Object中的方法 -- 用得较多

    /**
     * 返回值类型:T
     */
    public static <T> T method(Collection<T> coll){
        Iterator<T> it = coll.iterator();
        T t = it.next();    //返回值为T,T这个类型是可以被操作的
        return t;
    } 

3、学生类继承自Person类(class Student extends Person),但在Collection中,Person是一个单独的类型,Student也是一个单独的类型。Collection<>只能存一个具体的类型。如果Collection<Person>类型的参数接收的是存储Student类型元素的集合,即Collection<Person> coll = new ArrayList<Student>();,左右两边泛型不匹配(一般情况下,写泛型要具备的特点是:左右两边泛型要一致),编译报错。本来容器想装Person,结果new的只能装Student,其他的Person子类装不了,不合适

    //Collection<Person>:只能接收存储Person对象的集合
    public static void method(Collection<Person> coll){
        Iterator<Person> it = coll.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
    }

    public static void main(String[] args) {
        ArrayList<Student> list = new ArrayList<Student>();
        list.add(new Student("zhangsan"));
        list.add(new Student("xiaoqi"));
//        method(list);   //编译报错。method()方法的参数 Collection<Person>:Person是一个单独的类型,Student也是一个单独的类型
    }

      想要接收Person的子类对象,Collection<? extends Person>,即 ?全都是来自Person的子类

    //Collection<? extends Person>:只能接收存储Person或Person子类的集合 -- 泛型的限定
    public static void method(Collection<? extends Person> coll){
        Iterator<? extends Person> it = coll.iterator();
        while (it.hasNext()){
            Person p = it.next();   //存入的都是Person或Person子类,可以用Person接收(此处是多态)
            System.out.println(p.getName());    //多态,方法 编译看左边,运行看右边
//            System.out.println(it.next());
        }
    }

    public static void main(String[] args) {
        ArrayList<Student> list = new ArrayList<Student>();
        list.add(new Student("zhangsan"));
        list.add(new Student("xiaoqi"));
        method(list);

        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc1");
        list1.add("abc2");
//        method(list1);    //编译报错,只能接收存储Person或Person子类的集合
    }

4、Collection<? extends Xxx>:只接收Xxx类型或者Xxx类型的子类 -- 泛型的限定(上限)。取出时,用Xxx类型接收,此时是多态(调用方法:编译看左边,运行看右边)

注:Collection<?>  <==>  Collection<? extends Object>

二、泛型下限

1、对类型进行限定

(1)? extends E:接收E类型或者E的子类型对象 -- 上限(扩展性较强,使用较多)

(2)? super E:接收E类型或者E的父类型对象 -- 下限(此种做法不是很多)

2、迭代器的泛型一定和获取迭代器对象集合的泛型一致

    //Collection<? super Student>:只能接收存储Student或Student父类(Person)的集合,不能接收存储Worker的集合 -- 下限
    //此种做法不多(应用不明显)
    public static void method(Collection<? super Student> coll) {
        //迭代器的泛型一定和获取迭代器对象集合的泛型一致
        Iterator<? super Student> it = coll.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }

三、上限的体现

1、boolean addAll(Collection<? extends E> c):将指定collection中的所有元素都添加到此Collection中(扩展性很强)

class MyCollection<E> {

    //在明确类型E的情况下,添加的就是已知类型E。但一次添加一个 比较慢
    public void add(E e) {
    }

    //集合中装什么类型,在添加新集合的时候,新集合也是什么类型
//    public void add(MyCollection<E> e) {
//    }

    //Collection中addAll()方法的实现原理
    //存元素时使用上限(<? extends E>),扩展类型。取出时,不存在类型安全隐患
    public void addAll(MyCollection<? extends E> e) {
    }

}

2、一般在存储元素时使用上限(? extends E)。因为一旦确定好类型,存入的就都是E或E的子类,取出时都按照上限类型E来运算,不会出现类型安全隐患

注:上限使用较多(可以扩展类型)

        /*
        ArrayList list1 = new ArrayList();
        list1.add(new Person("zhangsan"));
        ArrayList list2 = new ArrayList();
        list1.add("abc");
        //没有指定泛型,是Object,什么都可以存入。取出时,类型会存在安全隐患(类型不匹配)
        list1.add(list2);
        */

        ArrayList<Person> list1 = new ArrayList<Person>();
        list1.add(new Person("zhangsan"));

        ArrayList<Student> list2 = new ArrayList<Student>();
        list2.add(new Student("xiaoqi"));

        ArrayList<String> list3 = new ArrayList<String>();
        list3.add("abc");

        //addAll(Collection<? extends E> c):取出时,按Person类型来取,Person可以接收学生等子类,不存在类型安全隐患
        list1.addAll(list2);
//        list1.addAll(list3);    //错误。类型不匹配

四、下限的体现

1、TreeSet的部分构造函数

(1)TreeSet(Collection<? extends E> c)

(2)TreeSet(Comparator<? super E> comparator)

2、Student extends Person,若想按照姓名排序,需自定义比较器。无论是Student还是Person,用的都是同一段代码,只是泛型不同而已,且用的都是父类型Person的方法

class CompByName implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        int temp = p1.getName().compareTo(p2.getName());
        return temp == 0 ? p1.getAge() - p2.getAge() : temp;
    }
}

class CompByStuName implements Comparator<Student> {
    @Override
    public int compare(Student p1, Student p2) {
        int temp = p1.getName().compareTo(p2.getName());
        return temp == 0 ? p1.getAge() - p2.getAge() : temp;
    }
}

      若想对Student、Worker统一地进行共性类型排序,怎么排?

      Student、Worker的排序方式依赖于Person的排序方式。比较有一个特点,要把集合中的元素取出来比较存放位置。要把集合中的元素取出来进行比较,就要把取出来的元素进行接收。接收时,存放的是什么类型不重要,最重要的是接收进来的类型要大一些。就意味着Student、Worker都可以用Person的比较器

class Xxx implements Comparator<? super Student> {}

3、何时使用下限?

      通常对集合中的元素进行取出操作时,可以使用下限。存什么类型,可以用 这个类型或这个类型的父类型 来接收 -- 较为少用

eg:比较器就是取集合中的元素,用一个父类型来接收,保证取出的全都可以被接收到

说明:一个容器TreeSet<Person>中既能添加Person对象,又能addAll()添加子类对象,意味着有Person又有Student、Worker。想统一对这些对象进行排序,排的时候要把它们取出来比较,拿什么来接收?此时应该拿Person来接收。意味着如果存储的全部都是Student、Worker,也都能用Person接收。只要用的是Person的方法来进行比较,全用此方法即可

五、通配符的体现

1、boolean containsAll(Collection<?> c):contains()方法的原理是在用equals()做判断,而equals()方法任何对象都具备,且其参数是Object

原因:

(1)任何对象都有equals()方法

(2)equals()可以接收任意对象(比的是地址值)

        ArrayList<Person> list1 = new ArrayList<Person>();
        list1.addAll(new Person("zhangsan"));
        
        ArrayList<Person> list2 = new ArrayList<Person>();
        list2.addAll(new Person("xiaoqi"));
        
        ArrayList<String> list3 = new ArrayList<String>();
        list3.add("abc");
        
        //不报错。因为 boolean containsAll(Collection<?> c)
        list1.containsAll(list2);
        list1.containsAll(list3);

2、何时使用<?> ?

(1)只要里面用的全都是Object的方法,就用<?>

(2)有的方法返回一个集合,但不知道返回的集合是什么类型,用<?> (返回<?>就用<?>接收即可)

六、集合查阅的技巧

1、需要唯一吗?

      需要:Set

              需要指定顺序吗?

                      需要:TreeSet

                      不需要:HashSet

                      不需要,但想要有一个和存储一致的顺序(有序):LinkedHashSet

      不需要:List

              需要频繁增删吗?

                      需要:LinkedList

                      不需要:ArrayList

              需要同步(效率低)吗?

                      需要:Vector

2、怎样记住每一个容器的结构和所属体系呢?

      看名字

(1)后缀名就是该集合所属的体系(通过体系可以判断其特点)

        |-- List :有角标、有序、可重复

               |-- ArrayList

               |-- LinkedList

        |-- Set :元素唯一,无序

               |-- HashSet

               |-- TreeSet

(2) 前缀名就是该集合的数据结构

        看到 array:就要想到数组,就要想到查询快(有角标)

        看到 link:就要想到链表,就要想到增删快,就要想到 add、get、remove + first/last 的方法

        看到 hash:就要想到哈希表,就要想到唯一性,就要想到元素需要覆盖hashCode()方法和equals()方法

        看到 tree:就要想到二叉树,就要想到排序,就要想到两个接口Comparable、Comparator

注:通常,这些常用的集合容器都是不同步的。Vector是同步的

3、看到排序,想到元素需要具备比较性,就要使用Comparable或Comparator

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐