集合简介

Java的集合类定义在java.util包中,支持泛型,主要提供了3种集合类,包括List,Set和Map。Java集合使用统一的Iterator遍历,尽量不要使用遗留接口。

List 集合

List是按索引顺序访问的长度可变的有序表,优先使用ArrayList而不是LinkedList;
可以直接使用for each遍历List;
List可以和Array相互转换。
List变为Array:

  1. 这种方法会丢失类型信息,所以实际应用很少。
  2. public class Main {
  3. public static void main(String[] args) {
  4. List<String> list = List.of("apple", "pear", "banana");
  5. Object[] array = list.toArray();
  6. for (Object s : array) {
  7. System.out.println(s);
  8. }
  9. }
  10. }
  1. 第二种方式是给toArray(T[])传入一个类型相同的ArrayList内部自动把元素复制到传入的Array中:
  2. 注意到这个toArray(T[])方法的泛型参数<T>并不是List接口定义的泛型参数<E>,所以,我们实际上可以传入其他类型的数组,
  3. 例如我们传入Number类型的数组,返回的仍然是Number类型
  4. public class Main {
  5. public static void main(String[] args) {
  6. List<Integer> list = List.of(12, 34, 56);
  7. Integer[] array = list.toArray(new Integer[3]);
  8. for (Integer n : array) {
  9. System.out.println(n);
  10. }
  11. }
  12. }
  1. 最后一种更简洁的写法是通过List接口定义的T[] toArray(IntFunction<T[]> generator)方法:
  2. Integer[] array = list.toArray(Integer[]::new);

Array变为List:

  1. Integer[] array = { 1, 2, 3 };
  2. List<Integer> list = List.of(array);

对于JDK 11之前的版本,可以使用Arrays.asList(T…)方法把数组转换成List。

在List中使用indexOf 方法和contain方法时覆写equals方法

List还提供了boolean contains(Object o)方法来判断List是否包含某个指定元素。此外,int indexOf(Object o)方法可以返回某个元素的索引,如果元素不存在,就返回-1

  1. public static void main(String[] args) {
  2. List<String> list = List.of("A", "B", "C");
  3. System.out.println(list.contains("C")); // true
  4. System.out.println(list.contains("X")); // false
  5. System.out.println(list.indexOf("C")); // 2
  6. System.out.println(list.indexOf("X")); // -1
  7. }

在List中查找元素时,List的实现类通过元素的equals()方法比较两个元素是否相等,因此,放入的元素必须正确覆写equals()方法,Java标准库提供的String、Integer等已经覆写了equals()方法;
编写equals()方法可借助Objects.equals()判断。Objects.equals()静态方法可以不用判断要判断的两个对象是否为空
如果不在List中查找元素,就不必覆写equals()方法。

Map 集合

如果只是想查询某个key是否存在,可以调用boolean containsKey(K key)方法

遍历map:

  1. Map来说,要遍历key可以使用for each循环遍历Map实例的keySet()方法返回的Set集合,它包含不重复的key的集合:
  2. public class Main {
  3. public static void main(String[] args) {
  4. Map<String, Integer> map = new HashMap<>();
  5. map.put("apple", 123);
  6. map.put("pear", 456);
  7. map.put("banana", 789);
  8. for (String key : map.keySet()) {
  9. Integer value = map.get(key);
  10. System.out.println(key + " = " + value);
  11. }
  12. }
  13. }
  1. 同时遍历keyvalue可以使用for each循环遍历Map对象的entrySet()集合,它包含每一个key-value映射:
  2. public class Main {
  3. public static void main(String[] args) {
  4. Map<String, Integer> map = new HashMap<>();
  5. map.put("apple", 123);
  6. map.put("pear", 456);
  7. map.put("banana", 789);
  8. for (Map.Entry<String, Integer> entry : map.entrySet()) {
  9. String key = entry.getKey();
  10. Integer value = entry.getValue();
  11. System.out.println(key + " = " + value);
  12. }
  13. }
  14. }

注意: 遍历Map时,不可假设输出的key是有序的!

小结

Map是一种映射表,可以通过key快速查找value。
可以通过for each遍历keySet(),也可以通过for each遍历entrySet(),直接获取key-value。
最常用的一种Map实现是HashMap。

编写equals和hashcode

一、我们经常使用String作为key,因为String已经正确覆写了equals()方法。但如果我们放入的key是一个自己写的类,就必须保证正确覆写了equals()方法。

二、正确使用Map必须保证:

  1. 作为key的对象必须正确覆写equals()方法,相等的两个key实例调用equals()必须返回true;
  2. 作为key的对象还必须正确覆写hashCode()方法,且hashCode()方法要严格遵循以下规范:
  • 如果两个对象相等,则两个对象的hashCode()必须相等;
  • 如果两个对象不相等,则两个对象的hashCode()尽量不要相等。

即对应两个实例a和b:

  • 如果a和b相等,那么a.equals(b)一定为true,则a.hashCode()必须等于b.hashCode();
  • 如果a和b不相等,那么a.equals(b)一定为false,则a.hashCode()和b.hashCode()尽量不要相等。

三、由于扩容会导致重新分布已有的key-value,所以,频繁扩容对HashMap的性能影响很大。如果我们确定要使用一个容量为10000个key-value的HashMap,更好的方式是创建HashMap时就指定容量:

小结

要正确使用HashMap,作为key的类必须正确覆写equals()和hashCode()方法;
一个类如果覆写了equals(),就必须覆写hashCode(),并且覆写规则是:

  • 如果equals()返回true,则hashCode()返回值必须相等;
  • 如果equals()返回false,则hashCode()返回值尽量不要相等。

实现hashCode()方法可以通过Objects.hashCode()辅助方法实现。

EnumMap的使用

  1. public class Main {
  2. public static void main(String[] args) {
  3. Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
  4. map.put(DayOfWeek.MONDAY, "星期一");
  5. map.put(DayOfWeek.TUESDAY, "星期二");
  6. map.put(DayOfWeek.WEDNESDAY, "星期三");
  7. map.put(DayOfWeek.THURSDAY, "星期四");
  8. map.put(DayOfWeek.FRIDAY, "星期五");
  9. map.put(DayOfWeek.SATURDAY, "星期六");
  10. map.put(DayOfWeek.SUNDAY, "星期日");
  11. System.out.println(map);
  12. System.out.println(map.get(DayOfWeek.MONDAY));
  13. }
  14. }

小结

如果Map的key是enum类型,推荐使用EnumMap,既保证速度,也不浪费空间。
使用EnumMap的时候,根据面向抽象编程的原则,应持有Map接口。

TreeMap的使用

一、使用TreeMap时,放入的Key必须实现Comparable接口。String、Integer这些类已经实现了Comparable接口,因此可以直接作为Key使用。作为Value的对象则没有任何要求。

小结

SortedMap在遍历时严格按照Key的顺序遍历,最常用的实现类是TreeMap;
作为SortedMap的Key必须实现Comparable接口,或者传入Comparator;
要严格按照compare()规范实现比较逻辑,否则,TreeMap将不能正常工作。

properties的使用

小结

Java集合库提供的Properties用于读写配置文件.properties。.properties文件可以使用UTF-8编码。
可以从文件系统、classpath或其他任何地方读取.properties文件。
读写Properties时,注意仅使用getProperty()和setProperty()方法,不要调用继承而来的get()和put()等方法。

Set的使用

  1. 将元素添加进Set<E>:boolean add(E e)
  2. 将元素从Set<E>删除:boolean remove(Object e)
  3. 判断是否包含元素:boolean contains(Object e)

image.png
Set接口并不保证有序,而SortedSet接口则保证元素是有序的:

  • HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口;
  • TreeSet是有序的,因为它实现了SortedSet接口。

    小结

    Set用于存储不重复的元素集合:

  • 放入HashSet的元素与作为HashMap的key要求相同;

  • 放入TreeSet的元素与作为TreeMap的Key要求相同;

利用Set可以去除重复元素;
遍历SortedSet按照元素的排序顺序遍历,也可以自定义排序算法。

📘Queue的使用

  1. int size():获取队列长度;
  2. boolean add(E)/boolean offer(E):添加元素到队尾;
  3. E remove()/E poll():获取队首元素并从队列中删除;
  4. E element()/E peek():获取队首元素但并不从队列中删除。

image.png
注意:不要把null添加到队列中,否则poll()方法返回null时,很难确定是取到了null元素还是队列为空。

小结

队列Queue实现了一个先进先出(FIFO)的数据结构:

  • 通过add()/offer()方法将元素添加到队尾;
  • 通过remove()/poll()从队首获取元素并删除;
  • 通过element()/peek()从队首获取元素但不删除。

要避免把null添加到队列。