一、泛型上限
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