读后笔记 -- Java核心技术(第11版 卷I ) Chapter9 集合
9.1 Java 集合框架
9.1.2 Collection 接口
// 查看 9.2 的Java接口类型图,Collection 是其中的一个基本接口
public interface Collection<E> { // 其有两个基本方法:
boolean add(E element); // 添加元素后,集合发生改变返回 true,没有改变返回 false。其中,set 无重复元素 Iterator<E> iterator(); // iterator() 返回一个实现了 Iterator 接口的对象,该迭代器对象依次访问集合的元素 ... }
// Java 集合类库也是将 接口(interface) 和 实现(implementation) 进行分离。
9.1.3 迭代器
// 1. Iterator接口有4个方法 public interface Iterator<E> { E next(); boolean hasNext(); void remove(); default void forEachRemaining(Consumer<? super E> action); }
1.1 访问集合中的元素的几种方式 Collection<String> c = ...; Iterator<String> iter = c.iterator(); // 通过调用 iterator() 向 Collection 要一个 Iterator,然后再循环中使用。迭代器访问一遍后,数据清空 # 1) while 循环 while (iter.hasNext()) { // 必须判断,否则到末尾时调用 next,将抛出 NoSuchElementException 异常
String element = iter.next(); do sth with element } # 2) for each 循环 // 编译器会将 "for each" 循环转换为带有迭代器的循环。"for each"循环可以处理任何实现了 Iterable 接口的对象(Collection 接口扩展了 Iterable,看 section 9.2 接口图); for (String element : c) { do sth with element } # 3) lambda 表达式 // 这里使用的 iter 必须要重新声明一个,每次迭代器对象访问完就消耗掉了
iter.forEachRemaining(element -> do sth with element);
1.2 Iterator 接口的删除。可以认为 迭代器位于两个元素之间,删除操作会删除刚刚跳过的元素。 Iterator<String> it = c.iterator(); it.next(); // skip over the first element it.remove(); // now remove it,remove 前必须通过 next 访问。连续两次调用 remove() 将会异常
9.1.4 泛型实用方法
// Collection 与 Iterator 都是泛型接口, // 如果类继承 Collection,则可以用下面可以检测任意集合是否包含指定元素的泛型方法(Collection 接口默认的 contains 方法是抽象方法,没有具体实现): public static <E> boolean contains(Collection<E> c, Object obj) { for (E element : c) { if (element.equals(obj) return true; } return false; } // 当前 Java 提供了 AbstractColleciton 类,它保持基础方法 size 和 iterator 仍为抽象方法,但为实现者实现了其它例行方法。如此, 具体集合类可以扩展 AbstractCollection 类。现在由具体的集合类提供 iterator 方法,而 AbstractCollection 提供 contains 方法。 // 更好的做法:通过 Collection 的默认方法实现(Java 17 还未实现) // 注意集合实际的数据类型,如下面的 c对象,其实际是 HashSet,HashSet 类已经实现了 contains() Collection<Integer> c = new HashSet<>();
9.2 集合框架中的接口
1. List 是一个有序集合,元素会增加到容器中的特定位置。
访问方式:1)迭代器顺序访问; 2)索引随机访问;
List 随机访问的方法: add、remove、get、set;
--- 这些方法中都带有 index,可完成随机访问
List 接口的两个实现:ArrayList 和 LinkedList(参照 section 9.3 的图): 1)迭代器访问:2 个类都支持; 2)普通 index 随机访问:ArrayList -- 可以很方便地到达位置;
LinkedList -- 随机访问很慢; 3)高效随机访问:ArrayList 支持,但 LinkedList 不支持(通过查看 ArrayList 和 LinkedList 类的源码,ArrayList 有 implements RandomAccess 而 LinkedList 没有)。可通过标记接口 RandomAccess 判断;
ListIterator 是 Iterator 的一个子接口,定义了一个方法用于在迭代器位置前面增加一个元素(add 方法)。Iterator 接口是没有 add 方法的; // 通过查看 ListIterator 源码可看出,其扩展了 Iterator,并实现了 add()
ListIterator 提供了正反双向遍历链表(listIterator.next()、listIterator.hasNext() / listIterator.previous()、listIterator.hasPrevious());
2. Set 是一个无序的 Collection
- equals() 方法的逻辑:只要两个 set 包含同样的元素则相等,不要求具有相同的顺序;
3. Sorted Set 是 Set 的子接口,按迭代器遍历时,这个遍历会按有序的顺序进行
4. Map 访问数据: put(写)、get(读)
5. 特点总结:
- List:有序,可重复(不唯一)
- Set:无序,不可重复(唯一)
- Collection:无序,可重复(不唯一)
- Map:存放键值对(key-value)
9.3 具体集合(Concrete Collections)
// 该图的 AbstractXXX 类是实现了 section 9.2 接口的抽象类。抽象类下面具体的类(如 LinkedList)又扩展了抽象类。
9.3.1/2 LinkedList/ArrayList
1. Two ordered collection implementations: array lists and linked lists. 2. array lists: 1) manage an array that can grow or shrink; 2) inserting and removing in the middle is slow; 3. linked lists: 1) easy to remove in the middle;
// Linked list iterators can detect concurrent modifications: List<String> list = ...; ListIterator<String> iter1 = list.listIterator(); ListIterator<String> iter2 = list.listIterator(); iter1.next(); iter1.remove(); iter2.next(); // throws ConcurrentModificationException
大多数情况下,使用频率: ArrayList > LinkedList
9.3.3 HashSet
-- If you don't care about element ordering, you can use more efficient collection. -- Sets can add, remove, and find elements quickly. -- Hash set uses hash codes to group elements into buckets.
-- Elements are visited in seemingly random order.
-- Java 中,Hash set 通过链表数组实现。
9.3.4 TreeSet
-- Tree sets visit elements in sorted order. -- In practice, they are a bit slower than hash sets. -- But performance is guaranteed, whereas hash sets can perform poorly when the hash function does not scramble vlaues well. -- Tree set needs total ordering - not always easy to find. -- Use tree sets when your elements are comparable and you need traversal in sorted order.
// 要使用树集,元素必须实现 Comparable 接口,或构造集时必须提供一个 Comparator
案例 treeset\TreeSetTest 中,var parts = new TreeSet<Item>(); // Item 类需要实现 Comparable 接口
9.3.5/9.3.6 Queues 队列, Deques 双端队列, and Priority Queues 优先队列
Queues let you efficiently add at the tail and remove from the head. Useful for adding work assignments to the tail and removing them from the head. Common tool in concurrent programs to even out workload. Deque can add/remove on both ends. Priority Queue isn't a queue. The priority queue doesn't remember in which order elements were added. When removing, the "highest priority" element is removed. Useful for work scheduling. !!! 优先队列使用的内部结构是为快速删除优化的,而不是为迭代和有序顺序而优化。
9.4 映射
9.4.1 基本映射操作
A map stores key/value associations. map 不收集元素,只是存储 key/value 关联。 Map 接口的2种实现:HashMap hashes the keys, TreeMap organizes them in sorted order. 1. Add an association to a map: Map<String, Employee> staff = new HashMap<>(); // HashMap implements Map。Map 和 HashMap 都是泛型 Employee harry = new Employee(...); staff.put("987-98-9966", harry); // 无键时添加,有键时覆盖 // 应用场景:Retrive a value with a given key: // 方案1) String id = "987-98-9966"; e = staff.get(id); // gets harry,最好使用 getOrElse(),因为有可能无指定的键,直接 get() 可能获取 null // 方案2) The get method returns null if the key is absent. Better approach: Map<String, Integer> scores = ...; int score = scores.getOrElse(id, 0); // Gets 0 if the id is not present 2. Easiest way to iterate over a map: scores.forEach(k, v) -> System.out.println("key=" + k + ", value=" + v));
9.4.2 更新映射条目
// 应用场景: Updating a map entry is tricky b/c the first time is special, will return NullPointerException if key is null.
var counts = new HashMap<String, Integer>();
// 1. Consider updating a word count: counts.put(word, counts.get(word) + 1); // 最好使用 getOrDefault() 方法,will return NullPointerException if key is null counts.put(word, counts.getOrDefault(word, 0) + 1); // for word wasn't present // 2. Another approach: counts.putIfAbsent(word, 0); counts.put(word, counts.get(word) + 1); // work with above sentence. Now, we know that get will succeed // 3. Best: counts.merge(word, 1, Integer::sum); // If the word wasn't present, put 1. Otherwise, put the sum of 1 and the previous value.
9.4.3 映射视图
In the java collections framework, a map isn't a collection. Note (用例: map\MapEntryTest): 1. keySet(), values() and entrySet() 生成的集合并不是映射中集合的副本,它们是真正的集合,所以这时视图是实时的,在键集上调用 remove 时,它会真正从映射删除那个键和关联的值; 2. 不能向键集视图中添加元素; // 1. Can get collections of keys, values, and key/value pairs: Set<K> keySet() Collection<V> values() Set<Map.Entry<K, V>> entrySet() // 应用场景:visit all keys and values, can use: // 方式1: Set<String> keys = staff.keySet(); for (String k : keys) {do sth with k and map.get(k)} keys.remove(k); // the element with key k is removed too,当然,删除一个不存在的 key 也不会报错 // 方式2:More efficiently: for (var entry : map.entrySet()) { // 原始写法: for (Map.entry<String, Employee> entry : map.entrySet()) String k = entry.getKey(); Employee v = entry.getValue(); do sth with k, v } // 方式3:Efficient and elegant: map.forEach((k, v) -> {do sth with k, v});
9.4.5 链接散列集与映射
A linked hash map traverses entries in the order in which they were added. // 应用场景: In the Java ServerFaces framework, you can specify a map of displayed and submitted values in an HTML select tag: options.put("Monday", 1); options.put("Tuesday", 1); options.put("Wednesday", 1); .... You don't want them to show up in random or sorted order. => Solution: Use a LinkedHashMap
9.5 视图与包装器
Collections 类的一些方法:
--- 可以生成可修改的视图(如 9.5.1 和 9.5.2)
--- 还可生成不可修改的视图(如 9.5.3)
// 用例: view\ViewTest
1. A view implements a collection interface without actually storing the elements. Example: Collection<String> greetings = Collections.nCopies(10, "Hello"); // Collection: 接口 // Collections: 类,其方法的参数和返回值都是集合 2. Useful to pass small collections to a method: Collection<String> greetings = Collections.singleton("Hello"); // 提供一个元素来创建数组列表 Set<String> deepThoughts = Collections.emptySet(); // provide empty set 3. Subranges give a view into an existing collection: List<Employee> group2 = staff.subList(10, 20); // the view is live: staff reduction,elements will be removed from staff list and group2 will be empty group2.clear(); 4. For sorted sets and maps, can view intervals: SortedSet<E> subSet(E from, E to) SortedSet<E> headSet(E to) SortedSet<E> tailSet(E from) SortedMap<K, V> subMap(K from, K to) SortedMap<K, V> headMap(K to) SortedMap<K, V> tailMap(K from)
9.5.1 小集合
// 1. 接口的静态方法:List.of, Set.of, Map.of/ofEntries,即创建完的元素不可更改(add(),remove() 都会 unsupported operation) (用例: list\CollectionLiterals) // 1.1) List 和 Set 接口有11个方法,0 ~ 10 个参数,this is workable after Java 9 List<String> names = List.of("Peter", "Paul", "Mary");
Set<Integer> numbers = Set.of(2, 3, 5); // 1.2)创建 Map 的两种方式: // 方式一:键值交织,Map.of(key1, value1, key2, value2, ...)(此方式的参数 <= 10 个) Map<String, Integer> scores = Map.of("Peter", 2, "Paul", 3, "Mary", 5); // 方式二: 可变参数的 map import static java.util.Map.*; Map<String, Integer> scores2 = ofEnteries(entry("Peter", 2), entry("Paul", 3), entry("Mary", 5)); // 2. Arrays.asList() 方法,元素可更改但大小不可改(即可调用 set() 修改,但不能调用 remove()/add())
// 该方法的2个作用:1)快速建立一个列表; 2)将一个数组转换为一个列表 List<String> variableList = Arrays.asList("Amy", "Bruce", "Carl");
9.5.2 子范围
// 1. 为 集合 建立 子范围(subrange)视图: List<Employee> group2 = staff.subList(10, 20); // 获取 staff 列表的 第 10- 19 个的元素 group2.clear(); // staff reduction // 2. 对有序集和映射,可使用排序而不是元素位置建立子范围: SortedSet<E> subSet(E from, E to) SortedSet<E> headSet(E to) SortedSet<E> tailSet(E from) SortedMap<K, V> subMap(K from, K to) SortedMap<K, V> headMap(K to) SortedMap<K, V> tailMap(K from) NavigableSet<E> subSet(E from, boolean fromInclusive, E to, boolean toInclusive) NavigableSet<E> headSet(E to, boolean toInclusive) NavigableSet<E> tailSet(E from, boolean fromInclusive)
9.5.3 不可修改的视图
1. Useful methods for producing unmodifiable views: Collections.unmodifiableCollection Collections.unmodifiableList // 处理 ArrayList, LinkedList 或实现了 List 接口的其他类 Collections.unmodifiableSet Collections.unmodifiableSortedSet Collections.unmodifiableNavigableSet Collections.unmodifiableMap
2. Good idea whenever you want to give a "look, don't touch" copy of a collection. 3. Sychronized views for safe concurrent access. But you should instead use a threadsafe collection. 4. Checked views for debugging class cast exceptions: List<String> safeStrings = Collections.checkedList(string, String.class);
9.5.4 同步视图
// 应用场景:多个线程访问集合,确保集合不会被意外破坏。 // 如:一个线程试图将元素添加到散列表,同时另一个线程正对元素进行再散列 // 解决方案:使用 Collections 的 静态方法 synchronizedMap var map = Collections.synchronizedMap(new HashMap<String, Employee>()); // 效果:现在可从多线程访问这个 map 对象了,类似 get 和 put 等方法都是同步的,即每个方法调用必须完全结束,另一个线程才能调用另一个方法。
9.5.5 检查型视图
检查型视图:用来对泛型类型可能出现的问题提供调试支持。
// 场景:将错误类型的元素混入泛型集合 var strings = new ArrayList<String>(); ArrayList rawList = strings; // warning only, not an error, for compatibility with legacy code rawList.add(new Date()); // now strings contains a Date obj // 问题: rawList.add() 这个Date obj,在运行时检测不到,当代码调用 get 方法,将其结果强制转换为 String 时,才会出现强转异常。 // 其中的解决方案:使用检查型视图定义一个安全列表 List<String> safeStrings = Collections.checkedList(strings, String.class); ArrayList rawList = safeStrings; rawList.add(new Date()); // checked list throws a ClassCastException
9.6 算法
1. 常用算法
1. Collections.sort sorts a list of Comparable, or any list with a Comparator instance. 2. Collections.binarySearch finds an element in a sorted list. 3. Collections.min and Collections.max find the smallest and largest element. 4. Collections.fill fills a list with a specified element. 5. Collections.shuffle randomly shuffles a list. 6. More useful examples, it's a lot nicer than an explicit loop. Collections.replaceAll(words, "C++", "Java"); words.replaceAll(String::toLowerCase); // with lambda expression words.removeIf(w -> w.length() <= 3); // with lambda expression
9.6.5 批操作
1. Can copy or remove elements in bulk: coll1.addAll(coll2); // add coll2 to coll1 coll1.removeAll(coll2); // remove all elements of coll2 from coll1 coll1.retainAll(coll2); // 从 coll1 中删除所有不在 coll2 中出现的元素
// When working with sets, addAll is set union(并集), retainAll is set intersection(交集), removeAll is set difference(差集). 2. Can combine with veiws: staffMap.keySet().removeAll(terminatedIDs); relocated.addAll(staff.subList(0, 10));
9.6.6 集合与数组的转换
1. To turn an array into a collection, use the Arrays.asList method: String[] names = ...; List<String> list = Arrays.asList(names); // 方法一,数组 转 list Set<String> set = new HashSet<>(List.of(names)); // 方法二,数组 转 set
2. To turn a collection into an array,
// 2.1 you can call coll.toArray(), but only get a useless Object[] array. // 2.2 To get an array of the right type, supply an array of length 0: Collection<String> coll = ... ; String[] names = coll.toArray(new String[0]); // 提供一个指定类型且长度为 0 的数组 String[] names = coll.toArray(new String[coll.size()]); // more efficiently,构造一个大小正确的数组
9.7 遗留的集合(Legacy Collection)
Some APIs still use ancient collections;
1. Vector => ArrayList, Hashtable => HashMap;
2. Enumeration => Iterator;
3. Properties are used when specifiying program configuraitons. Type are similiar to Map<String, String>;
4. Stack => ArrayList;
5. A BitSet is a sequence of bits. It can be used to represent a set of integers. Operations include:
int bit = bucketOfBits.get(i); bucketOfBits.set(i); bucketOfBits.clear(i);