基本概念
- Java容器类类库的用途:保存对象,划分为两个不同的概念:
–Collection
:一个独立元素的序列,这些元素都服从一条或多条规则。List
必须按照插入的顺序保存元素,Set
不能有重复的元素。Queue
按照排队规则来确定对象产生的顺序。
–Map
:一组成对的“键值对”对象,允许你使用键来查找值。映射表允许我们使用另一个对象来查找某个对象,它也被称为“关联数组”,因为它将某些对象与另外一些对象关联在一起。 - 创建
List
创建一个具体的类,将其转型为对应的接口,,然后其余的代码中都使用这个接口。List<Apple> apples = new ArrayList<>(); // 创建一个ArrayList,向上转型为List
但是,如果要使用该接口额外的方法,就不能将具体的类向上转型为该接口。 - 添加元素
public class SimpleCollection { public static void main(String[] args) { Collection<Integer> c = new ArrayList<Integer>(); for(int i = 0; i < 10; i++) { c.add(i); // 添加元素 } for(Integer i: c) { // foreach遍历 System.out.print(i + ", "); } } }
- 添加一组元素
Collection.addAll()
成员方法只能接受另一个Collection
,用它将自己初始化,因此可以通过Arrays.List()
来为这个构造器输入。但是,Collections.addAll()
方法运行起来快得多,构建一个空的Collection
,然后调用Collections.addAll()
这种方式很方便,因此它是首选方式。
可以直接使用Arrays.adList()
输出,将其当作List
,但这种情况下,底层是数组,没有重写add()
、delete()
方法,因此不能进行修改操作,否则会报异常。public class AddingGroups { public static void main(String[] args) { // Arrays.asList接受一个数组或逗号分隔的元素列表,并将其转换为List对象 // ArrayList构造器将其转换为ArrayList Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5)); // collection的内部方法,将所有元素添加到collection中 Integer[] moreInts = {6, 7, 8, 9, 10}; collection.addAll(Arrays.asList(moreInts)); // 通过工具类Collections,将以逗号分隔的数字,都加入到collection中。或将List所有元素都添加到collection中 Collections.addAll(collection, 11, 12, 13, 14, 15); Collections.addAll(collection, moreInts); // 将一串数字,转换成List List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); List.set(1, 99); // 将index为1的位置,值设为99 } }
List
- 两种类型的
List
–ArrayList
:长于随机访问元素,但是在List
中间插入和移除元素时较慢。
–LinkedList
:在List
中间插入和删除操作代价较低,提供了优化的顺序访问。但是随机访问相对较慢。
– 移除一个元素List<Integer> list = new ArrayList<Integer>(); list.add(1); // 添加元素 list.add(1); // 添加元素 list.add(2); // 添加元素 list.add(3); // 添加元素 list.remove(Integer.valueOf(1)); // 删除和指定对象相等的第一个对象,[1, 2, 3] list.remove(1); // 删除元素,以下标,[1,3] System.out.println(list.contains(3)); // true System.out.println(list.get(0)); // 获取下标为0的元素,但不删除, 1 System.out.println(list.indexOf(1)); // 获取第一个对象为1的下标, 0 list.add(5); // 添加元素 list.add(6); // 添加元素 list.add(7); // 添加元素 list.add(8); // 添加元素 System.out.println(list); // [1, 3, 5, 6, 7, 8] System.out.println(list.subList(1, 4)); // 左闭右开的子List,[3, 5, 6] System.out.println(list.containsAll(Arrays.asList(1, 5, 3))); // 是否包含所有元素,顺序不定 list.add(1); // 添加元素 list.add(-1); // 添加元素 list.add(-7); // 添加元素 list.add(-8); // 添加元素 Collections.sort(list); // 从小到大排列, [-8, -7, -1, 1, 1, 3, 5, 6, 7, 8] //list.sort(Comparator.naturalOrder()); // 从小到大排列,两者等效 list.sort(Comparator.reverseOrder()); // 从大到小排列, [8, 7, 6, 5, 3, 1, 1, -1, -7, -8] Collections.shuffle(list); // 随机打乱, [6, 5, 1, 8, -7, -8, 7, -1, 1, 3] list.removeAll(Arrays.asList(1, 3)); // 删除list里面所有满足条件的对象,[-7, 5, 6, -8, 8, 7, -1] list.set(1, 8); // 下标为1的元素,设为8, [-7, 8, 6, -8, 8, 7, -1] list.addAll(Arrays.asList(1, 2)); // 在尾部添加所有元素,[-7, 8, 6, -8, 8, 7, -1, 1, 2] list.addAll(1, Arrays.asList(4, 5)); // 在下标为1的位置插入所有元素,[-7, 4, 5, 8, 6, -8, 8, 7, -1, 1, 2] list.retainAll(Arrays.asList(1, 2, 3, 4, 6)); // 取交集,[4, 6, 1, 2] Object[] objects = list.toArray(); // list转化为array Integer[] integers = list.toArray(new Integer[0]);// list转化为array,并且将类型也确定了 list.clear(); // 清空, [] System.out.println(list.isEmpty()); // true
remove()
,是用equals
来查找对象的。其他查找的方法,也是使用equals
的
– 当存在很多从中间插入的操作时add(int index, Integer element)
、addAll(int index, Collection<? extends Integer> c)
,考虑使用LinkedList
而不是ArrayList
–subList()
所产生的列表的幕后就是初始列表,因此对返回的列表的修改都会反映到初始列表中,反之亦然。
–retainAll()
是一种有效的“交集”操作,并且会将结果保存在list
中
–toArray()
方法,将任意的Collection()
转换为一个数组。无参数版本返回的是Object
数组,如果传递指定类型的数组,则会返回该类型的数组(内部将创建一个合适尺寸的数组保存结果)
迭代器
- 迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员并不需要关心该序列底层的结构。此外,迭代器通常是轻量级对象:创建它的代价小。
– 使用iterator()
要求容器返回一个Iterator
,Iterator
将准备好返回序列的第一个元素
– 使用next()
获得序列的下一个元素
– 使用hasNext()
检查序列中是否还有元素
– 使用remove()
将新近返回的元素删除(即next()
刚返回的对象)。调用remove()
之前必须调用next()
–display()
方法不包含任何有关它所遍历的序列的类型信息,能够将遍历序列的操作与序列底层的结构分离。因此,迭代器统一了对容器的访问方式。List<Integer> list = new ArrayList<Integer>(); Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9); Iterator<Integer> iterator = list.iterator(); // 获取该容器的迭代器 // 遍历 while (iterator.hasNext()) { System.out.println(iterator.next()); } iterator = list.iterator(); for (int i = 0; i < 3; i++) { System.out.println(iterator.next()); iterator.remove(); } System.out.println(list); // [4, 5, 6, 7, 8, 9]
// 创建一个display()方法,不必知晓容器确切的类型,就可以显示 public void display(Iterator<?> iterator) { while (iterator.hasNext()) { System.out.println(iterator.next()); } }
ListIterator
–ListIterator
是一个更加强大的Iterator
的子类型。可以双向移动还可以指向当前位置的前一个索引和后一个索引。
– 还可以使用set()
方法替换它访问的最后一个元素
– 可以在开始导出iterator()
的时候,加入参数index
,实现一开始就指向第n个元素。List<Integer> list = new ArrayList<Integer>(); Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9); ListIterator<Integer> listIterator = list.listIterator(); while (listIterator.hasNext()) { // 获取下一个值、下一个值的下标、上一个值的下标 System.out.println("next():" + listIterator.next() + " nextIndex():" + listIterator.nextIndex() + " previousIndex():" + listIterator.previousIndex()); } while (listIterator.hasPrevious()) { System.out.println("previous():" + listIterator.previous()); } // 从坐标为3开始的迭代器 listIterator = list.listIterator(3); while (listIterator.hasNext()) { // 获取下一个值、下一个值的下标、上一个值的下标 System.out.println("next():" + listIterator.next() + " nextIndex():" + listIterator.nextIndex() + " previousIndex():" + listIterator.previousIndex()); }
LinkedList
LinkedList
还添加了用作栈、队列和双端队列的方法- 返回列表第一个元素:
getFirst()
和element()
完全一致,当List
为空的时候,抛出NoSuchElementException
异常。peek()
方法也类似,只是当列表为空时返回null
- 删除并返回第一个元素:
removeFirst
和remove()
完全移植,移除并返回第一个元素,当List
为空的时候,抛出NoSuchElementException
异常。poll()
稍有差异,列表为空时返回null
- 在尾部添加元素:
add()
和addFirst()
完全一样,在列表尾部加入元素 LinkedList
的add()
和remove()
有点像队列?先进先出。
Stack
- 栈通常是值后进先出(LIFO)的容器,通常用
LinkedList
实现 - 底层实现
// 入栈 public void push(E e) { addFirst(e); } // 出栈 public E pop() { return removeFirst(); } // 返回栈顶元素 public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } //
Set
Set
不保存重复的元素。Set
最常被使用的是测试归属性,你可以很容易地询问某个对象是否在某个Set
中。正因如此,查找就成为了Set
中最重要的操作。因此你通常都会选择一个HashSet
的实现,它专门对快速查找进行优化。Set
具有与Collection
完全一样的接口,因此没有任何额外的功能。只是行为不同(体现了继承性与多态性)Set<Integer> set = new HashSet<Integer>(); Random rand = new Random(); for (int i = 0; i < 1000; i++) { set.add(rand.nextInt(30)); } System.out.println(set);
- 处于速度原因的考虑,
HashSet
使用了散列。TreeSet
存储在红黑树数据结构中。LinkedHashMap
因为查询速度的原因也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。 - 如果想对结果排序,一种方法是使用
TreeSet
Set<Integer> set = new TreeSet<>(); set.add(53); set.add(1); set.add(4); set.add(-14); set.add(4); set.add(434); set.add(5); System.out.println(set); // [-14, 1, 4, 5, 53, 434]
contains()
测试Set
的归属性Set<String> set = new HashSet<>(); Collections.addAll(set, "4 4 523 54 fsf 3532 54".split(" ")); System.out.println(set); // [4, 523, 3532, fsf, 54] System.out.println(set.contains("4")); // true
Map
- 将对象映射到其他对象
Random rand = new Random(47); // 生成随机数 Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < 10000; i++) { int r = rand.nextInt(20); Integer freq = map.get(r); // 获取目前的频率 map.put(r, freq == null ? 1 : freq + 1); // 如果目前还不存在,就存入1,否则在现在基础上+1 } System.out.println(map);
containsKey()
和containsValue()
Map<String, String> map = new HashMap<>(); map.put("cat", "ketty"); map.put("dog", "honey"); System.out.println(map.containsKey("cat")); // 是否包含键, true System.out.println(map.containsValue("ketty")); // 是否包含值, true
Map
很容易拓展成多维。可以创建一个Map<Person, List<Pet>>
的Map,每个值保存一个List
- 获取所有键、值、键值对
Set<String> strings = map.keySet(); // 获取所有键组成的Set Collection<String> values = map.values(); // 获取所有值组成的Collection Set<Map.Entry<String, String>> entries = map.entrySet(); // 获取所有键值对(Entry)组成的Set System.out.println(strings); System.out.println(values); for (Map.Entry<String, String> entry : entries) { String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + " " + value); }
Queue
- 队列是一个典型的先进先出(FIFO)的容器。LinkedList提供了以支持队列的行为,并且它实现了Queue接口,因此LinkedList用作Queue的一种实现。
offer()
方法,它在允许情况下,将一个元素插入到队尾,或者返回false
。peek()
和element()
都将在不移除的情况下,返回队头,但是peek()
方法在队列为空的时候返回null
,element()
会抛出NoSuchElementException
异常。poll()
和remove()
都会移除队头并返回。在队列为空时,poll()
会返回空,remove()
会抛出NoSuchElementException
异常。Queue<Integer> queue = new LinkedList<>(); // 向上转型 for (int i = 0; i < 10; i++) { queue.offer(i); // 在队尾加入值 } System.out.println(queue.peek()); // 返回队头的值 while (!queue.isEmpty()) { System.out.println(queue.poll()); // 返回队头的值,并移除 }
- 优先级队列
– 优先级队列声明下一个弹出元素时最需要的元素(具有最高优先级)。
– 当你在PriorityQueue
上调用offer()
方法来插入一个对象时,这个对象会在队列中排序,默认的排序将使用对象在队列中的自然顺序(从小到大),但是可以提供自己的Comparator
来修改这个顺序。
–PriorityQueue
可以确保当你调用peek()
、poll()
、remove()
方法时,获取的元素将是队列中优先级最高的元素。List<Integer> input = Arrays.asList(54, 3, 4, 5, 5, 45, 7, 5, 8); // 从小到大 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(); priorityQueue.addAll(input); System.out.println(priorityQueue); // 不一定已经排好序 while (!priorityQueue.isEmpty()) { // 但输出的,一定是排好序的 System.out.println(priorityQueue.poll()); } priorityQueue = new PriorityQueue<>(Collections.reverseOrder()); // 放一个反转自然顺序的Comparator进去,从大到小 priorityQueue.addAll(input); System.out.println(priorityQueue); // 不一定已经排好序 while (!priorityQueue.isEmpty()) { // 但输出的,一定是排好序的 System.out.println(priorityQueue.poll()); } /*out [3, 5, 4, 5, 5, 45, 7, 54, 8] 3 4 5 5 5 7 8 45 54 [54, 8, 45, 5, 5, 4, 7, 3, 5] 54 45 8 7 5 5 5 4 3
// 根据字符串长度排序 PriorityQueue<String> priorityQueue = new PriorityQueue<>(new Comparator<String>(){ @Override public int compare(String o1, String o2) { return o1.length()- o2.length(); // 比较字符串长度 } }); priorityQueue.offer("amy"); priorityQueue.offer("amyfaf"); priorityQueue.offer("fadf"); priorityQueue.offer("fadfa"); while (!priorityQueue.isEmpty()) { // 但输出的,一定是排好序的 System.out.println(priorityQueue.poll()); } /* output amy fadf fadfa amyfaf */
Collection和Iterator
Collection
是描述所有序列容器的共性的根接口。- 使用接口描述的一个理由是它可以使我们能够创建更通用的代码。通过针对接口而非具体实现来编写代码。
Collection
和Iterator
均可以表示容器之间的共性
两个版本的public static <T> void display(Iterator<T> iterator) { while (iterator.hasNext()) { System.out.println(iterator.next()); } } public static <T> void display(Collection<T> collection) { for (T c : collection) { System.out.println(c); } }
display()
方法都可以及那个display()
方法与底层容器的特定实现解耦。- 当你要实现一个不是
Collection
的外部类时,由于让它实现Collection
接口可能非常困难或麻烦,因此使用Iterator
就变得非常吸引人。实现Collection
必须实现所有的Collection
方法,即使不需要使用某些方法。而Iterator
只需要实现两个方法。// Iterator底层 public interface Iterator<E> { boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationException("remove"); } default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); } }
Foreach与迭代器
- 之所以
foreach
能够工作,是因为实现了Iterable
接口,该接口包含一个能够产生Iterator
的iterator()
方法,并且Iterable
接口被foreach
用来在序列中移动 - 因此,创建的任何实现了
Iterable
的类,都可以通过foreach
遍历Collection<String> cs = new ArrayList<>(); cs.addAll(Arrays.asList("faf fae gs hfdh".split(" "))); for(String s : cs) { System.out.println(s); }
public class IterableImpl implements Iterable<String> { String[] s = "faf gdagfa aff gag".split(" "); @Override public Iterator<String> iterator() { return new Iterator<String>() { private int index = 0; @Override public boolean hasNext() { return index < s.length; } @Override public String next() { return s[index++]; } }; } @Test public void test() { IterableImpl strings = new IterableImpl(); for (String string : strings) { System.out.println(string); } } }
Collection
接口继承了Iterable
接口,从而实现了Iterable
接口的几个方法
Arrays.asList()
- 意识到Arrays.asList()产生的List对象会使用底层数组作为物理实现是很重要的。只要你执行的操作会修改这个List,并不想原来的数组被修改,那么你就应该在另一个容器上中创建一个副本。
Random rand = new Random(); Integer[] ia = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; List<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia)); Collections.shuffle(list1, rand); // list1被打乱,但是ia并没有被打乱 List<Integer> list2 = Arrays.asList(ia); Collections.shuffle(list2, rand); // list2被打乱,ia也被打乱了