第十一章:持有对象(容器类源码非常清楚也很强大,像我们这种菜鸟应该多学习)
- 泛型和集合类型安全
- 向上转型可以适用
- 容器类:
- Collection:一个独立元素的序列,这些元素都服从一条或者多条规则
- list:按照插入的顺序保存元素,不关心是否重复
- set:不能有重复元素
- Queue:按照队列规则来确定对象产生的顺序
- Map:键值对
- Collection:一个独立元素的序列,这些元素都服从一条或者多条规则
- Arrays.asList():将一个数组当作list,但是底层然是以数组来实现的,所以当对这个List做增删操作时有可能引起改变数组大小
- 显示类型参数声明:List<T> t = Arrays.<T>asList();底层返回的是一个内部类ArrayList
- 容器的打印:
- Collection打印出来的是方括号括住,每个逗号分隔的
- Map则用大括号,键与值则是用等号联系
- ArrayList和LinkedList都是List类型,都是按照被插入的顺序保存元素
- HashSet,TreeSet,LinkedHashSet都是Set类型,是去重复的
- hashSet的存储顺序非常复杂,待后面说
- TreeSet:按照比较结果的升序存储对象
- LinkedHashSet:按照添加的顺序保存对象
- HashMap,TreeMap,LinkedHashMap,存储的顺序和上面一致,其实Set底层就是基于Map来实现的,后面会细说
- List:将元素维护在特定的序列中,在Collection的接口上添加了大量方法
- ArrayList:易于随机访问,毕竟是依赖数组实现的
- subList():这个函数所产生的列表的幕后就是初始列表,所以对返回的列表的操作会反映到初始列表中
- 像remove(),retainAll()[表示取交集]等函数背后比对使用的都是equals函数,要注意这个函数比对的是引用,除非覆写,比如String类中
- LinkedList:易于插入删除,是依赖链表实现的,这个类中多了很多函数,是为了实现栈和队列提供基础的
- 双向链表实现
- 是无头节点的双向链表
- Queue实在LinkedList基础之上添加了element()/offer()/peek()/poll()/remove()等方法实现的
- element()/getFirst()/peek()都是取第一个对象,但是前两者在list为空时抛出NoSuchElementException异常,peek则返回null
- remove/removeFirst/pull()和上面类似,不过都是取移除并返回头
- add/addFirst()/addLast()
- ArrayList:易于随机访问,毕竟是依赖数组实现的
- 迭代器
- Iterator:单向的
- 使用方法iterator()要求容器返回一个Iterator,Iterator将准备好返回序列的第一个元素
- 使用next()获得序列的下一个元素
- 使用hasNext()检查序列中是否有元素
- 使用remove将迭代器新近返回的元素删除(一般要先调用next(),保证有新近返回的元素,因为根据源码知道,remove里删除的index是在next()方法里设置的)
- ListIterator:只能用于List类的访问,而且是双向的
- 可以通过调用listIterator()方法产生一个指向List开始处的ListIterator,并且还可以通过调用listIterator(n)创建一个一开始就指向列表索引为n的元素的ListIterator
- 通过源代码知道ListIterator是对Iterator的一次扩展,增加了实现双向移动的一些函数
- Iterator:单向的
- 栈
- 依赖LinkedList实现栈,类名后面的<T>告诉编译器这将是一个参数化类型
import java.util.LinkedList; public class StackByLinkedList<T> { private LinkedList<T> storage = new LinkedList<T>(); public void push(T v){ storage.push(v); } public T peek(){ return storage.peek(); } public T pop(){ return storage.pop(); } public boolean empty(){ return storage.isEmpty(); } public String toString(){ return storage.toString(); } }
- 当然在util包中实现了Stack这个类,不过是继承了Vector,依赖数组实现的
- Set:之前说过Set是依赖Map的key来实现的,可以看源码
- 不保存重复的元素,一般利用这个特性来测试归属行
- Set和Collection完全一样的接口,没有任何额外的功能
- HashSet/TreeSet/LinkedHashSet
- TreeSet依赖红黑树,结果是排序的
- HashSet依赖散列函数
- LinkedHashSet也使用了散列函数,但是使用了链表来维护元素的插入顺序
- Map:键值对,可以用于组合别的数据结构,如Map<A,List<? extends B>>();
- Queue:典型的FIFO
- LinkedLIst提供了方法支持队列的行为,而且实现了Queue的接口
- offer():将一个元素插入到队尾
- peek/element都是在不移除的情况下返回对头
- poll/remove都是在移除的情况下返回对头
- PriorityQueue
- 一般情况下会维持一个堆,在offer()插入时会根据一定的规则排序,可以通过Comparator来修改排序规则
- 一般情况下重复是允许的,而且最小的值拥有最高的优先级
- Collection与iterator
- Foreach与迭代器
- foreach适用于所有Collection
- 所有实现了Iterable的接口都可以用foreach实现移动
public class IterableClass implements Iterable<String>{ private String[] words = {"s0","s1","s2","s3","s4"}; public Iterator<String> iterator(){ return new Iterator<String>() { private int index = 0; public String next(){ return words[index++]; } public boolean hasNext(){ return index < words.length; } public void remove(){ } }; } @Test public void test(){ for(String s : new IterableClass()){ System.out.println(s+" "); } } }
- 所有的Collection(除了Map)都实现了Iterable接口,map可以使用Entry来构建成Set集合
import java.util.Map; import org.junit.Test; public class EnviromentVariables { @Test public void test(){ // System.getenv() for(Map.Entry entry : System.getenv().entrySet()){ System.out.println(entry.getKey()+" : "+entry.getValue()); } } }
- 不存在任何数组到Iterable的自动转换,必须手工转换
- Arrays.asList()产生的List对象会使用底层数组作为其物理实现,所以修改这个返回的List会修改底层的数组,如果不希望修改,则需要创建一个副本,如用new ArrayList<>包装一下创建一个引用副本
容器图