第8章 - Java 集合

Java 集合

为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),Java 提供了集合类(也被称为容器类)。

集合类和数组不一样,数组元素既可以是基本数据类型的值,也可以是对象;而集合里只能保存对象。

1. Java 集合概述

Java 的集合类主要有两个接口派生而出:Collection 和 Map:

第8章 - Java 集合_第1张图片

第8章 - Java 集合_第2张图片

2. Collection 和 Iterator 接口

Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法可以操作这三种集合。

ArrayList c = new ArrayList();
// 添加元素
c.add("孙悟空");
c.add("猪八戒");
System.out.println(c);  // [孙悟空, 猪八戒]
// 返回集合元素的个数
System.out.println(c.size());  // 2
// 删除元素
c.remove("猪八戒");
System.out.println(c);  // [孙悟空]
// 判断是否包含指定元素
System.out.println(c.contains("孙悟空"));  // true
System.out.println(c.contains("哪吒"));  // false

c.add("白骨精");
ArrayList c2 = new ArrayList();
// 返回集合是否为空
System.out.println(c2.isEmpty());  // true
c2.add("孙悟空");
c2.add("白骨精");
c2.add("蜘蛛精");
c2.add("琵琶精");
c2.add("玉兔精");
// 判断集合里是否包含另一个集合里所有元素
System.out.println(c2.containsAll(c));  // true
// 从集合 c2 删除 c 集合里的元素
c2.removeAll(c);
System.out.println(c2);  // [蜘蛛精, 琵琶精, 玉兔精]

c.add("黑熊精");
c.add("蜘蛛精");
c2.add("孙悟空");
System.out.println(c);  // [孙悟空, 白骨精, 黑熊精, 蜘蛛精]
System.out.println(c2);  // [蜘蛛精, 琵琶精, 玉兔精, 孙悟空]
// 取交集
c2.retainAll(c);
System.out.println(c2);  // [蜘蛛精, 孙悟空]
// 删除集合里所有元素
c2.clear();
System.out.println(c2.size());  // 0
// 将所有元素添加到指定集合里
c2.addAll(c);
System.out.println(c2);  // [孙悟空, 白骨精, 黑熊精, 蜘蛛精]

// 将集合转换为数组
Object[] arr = c.toArray();
System.out.println(Arrays.toString(arr));  // [孙悟空, 白骨精, 黑熊精, 蜘蛛精]

System.out.println("========== 使用 foreach 遍历集合元素 ==========");
// 遍历集合,常规方法
System.out.println("foreach 常规写法:");
for (Object obj : c) {
    String name = (String) obj;
    System.out.println(name);
}
// 简写方法
System.out.println("使用 Lambda 表达式:");
c.forEach(obj -> System.out.println(obj));

System.out.println("========== 使用 Iterator 遍历集合元素 ==========");
// 使用 Iterator 遍历集合元素
Iterator it = c.iterator();
while (it.hasNext()) {
    String name = (String) it.next();
    System.out.println(name);
}

System.out.println("========== 使用 Lambda 表达式 ==========");
// 使用 Lambda 表达式
Iterator it2 = c.iterator();
it2.forEachRemaining(obj -> System.out.println(obj));

// 使用 Predicate 操作集合
HashSet books = new HashSet();
books.add("疯狂Java讲义");
books.add("深入理解计算机系统");
books.add("你不知道的JavaScript");
books.add("图解HTTP");
books.add("算法图解");
// 过滤长度小于7的元素
books.removeIf(ele -> ((String) ele).length() < 7);
System.out.println(books);  // [深入理解计算机系统, 疯狂Java讲义, 你不知道的JavaScript]
// 过滤包含 Java 字符的元素
books.removeIf(ele -> ((String) ele).contains("Java"));
System.out.println(books);  // [深入理解计算机系统]

// 使用 Stream 操作集合
HashSet books2 = new HashSet();
books2.add("疯狂Java讲义");
books2.add("深入理解计算机系统");
books2.add("你不知道的JavaScript");
books2.add("图解HTTP");
books2.add("算法图解");
// 统计包含 Java 子串的图书数量
System.out.println(books2.stream()
                   .filter(ele -> ((String) ele).contains("Java"))
                   .count());  // 2
// 统计书名长度大于 7 的图书数量
System.out.println(books2.stream()
                   .filter(ele -> ((String) ele).length() > 7)
                   .count());  // 3
// 输出每个元素的长度,并生成一个新数组
int[] a = books2.stream().mapToInt(ele -> ((String) ele).length()).toArray();
System.out.println(books2);  // [算法图解, 图解HTTP, 深入理解计算机系统, 疯狂Java讲义, 你不知道的JavaScript]
System.out.println(Arrays.toString(a));  // [4, 6, 9, 8, 15]

3. Set 集合

Set 集合类似一个罐子,程序可以依次把多个对象“丢进” Set 集合,而 Set 集合通常不能记住元素的添加顺序。

// HashSet类 不按添加顺序排序
HashSet books = new HashSet();
books.add("疯狂Java讲义");
books.add("深入理解计算机系统");
books.add("你不知道的JavaScript");
books.add("图解HTTP");
books.add("算法图解");
System.out.println(books.add("什么是数学"));  // true
System.out.println(books.add("什么是数学"));  // false
System.out.println(books);  // [什么是数学, 算法图解, 图解HTTP, 深入理解计算机系统, 疯狂Java讲义, 你不知道的JavaScript]

// LinkedHashSet类 按添加顺序排序
LinkedHashSet books2 = new LinkedHashSet();
books2.add("疯狂Java讲义");
books2.add("深入理解计算机系统");
books2.add("你不知道的JavaScript");
books2.add("图解HTTP");
books2.add("算法图解");
System.out.println(books2);  // [疯狂Java讲义, 深入理解计算机系统, 你不知道的JavaScript, 图解HTTP, 算法图解]

// TreeSet类 集合元素处于排序状态
TreeSet books3 = new TreeSet();
books3.add("疯狂Java讲义");
books3.add("深入理解计算机系统");
books3.add("你不知道的JavaScript");
books3.add("图解HTTP");
books3.add("算法图解");
System.out.println(books3);  // [你不知道的JavaScript, 图解HTTP, 深入理解计算机系统, 疯狂Java讲义, 算法图解]
// 返回第一个元素
System.out.println(books3.first());  // 你不知道的JavaScript
// 返回最后一个元素
System.out.println(books3.last());  // 算法图解
// 返回指定元素的前一个元素
System.out.println(books3.lower("疯狂Java讲义"));  // 深入理解计算机系统
// 返回指定元素的后一个元素
System.out.println(books3.higher("疯狂Java讲义"));  // 算法图解
// 返回从 fromElement(包含)到 toElement(不包含)的子集合
System.out.println(books3.subSet("图解HTTP", "疯狂Java讲义"));  // [图解HTTP, 深入理解计算机系统]
// 返回小于指定元素的集合
System.out.println(books3.headSet("深入理解计算机系统"));  // [你不知道的JavaScript, 图解HTTP]
// 返回大于等于指定元素的集合
System.out.println(books3.tailSet("深入理解计算机系统"));  // [深入理解计算机系统, 疯狂Java讲义, 算法图解]

// 定制排序
TreeSet books4 = new TreeSet((a, b) -> {
    String s1 = (String) a;
    String s2 = (String) b;
    // 使元素按字符串长度排序
    return s1.length() - s2.length();
});
books4.add("疯狂Java讲义");
books4.add("深入理解计算机系统");
books4.add("你不知道的JavaScript");
books4.add("图解HTTP");
books4.add("算法图解");
System.out.println(books4);  // [算法图解, 图解HTTP, 疯狂Java讲义, 深入理解计算机系统, 你不知道的JavaScript]

// EnumSet类 以枚举值在 Enum 类内定义顺序来决定集合元素的顺序
// 创建一个包含指定枚举类的集合
EnumSet es1 = EnumSet.allOf(Season.class);
System.out.println(es1);  // [SPRING, SUMMER, AUTUMN, WINTER]
// 创建一个元素类型为指定类型的空集合
EnumSet es2 = EnumSet.noneOf(Season.class);
System.out.println(es2);  // []
// 手动添加元素
es2.add(Season.WINTER);
es2.add(Season.SPRING);
System.out.println(es2);  // [SPRING, WINTER]
// 创建一个包含一个或多个枚举值的集合,多个值必须同属于同一个枚举类
EnumSet es3 = EnumSet.of(Season.SUMMER, Season.AUTUMN);
System.out.println(es3);  // [SUMMER, AUTUMN]
// 创建一个包含从 from 枚举值到 to 枚举值范围内所有枚举值的集合
EnumSet es4 = EnumSet.range(Season.SUMMER, Season.WINTER);
System.out.println(es4);  // [SUMMER, AUTUMN, WINTER]
// 创建一个与指定集合不包含,此枚举类剩下的元素的集合
EnumSet es5 = EnumSet.complementOf(es4);
System.out.println(es5);  // [SPRING]
// 使用集合创建集合
HashSet hs = new HashSet();
hs.add(Season.SUMMER);
hs.add(Season.SPRING);
System.out.println(hs);  // [SUMMER, SPRING]
EnumSet es6 = EnumSet.copyOf(hs);
System.out.println(es6);  // [SPRING, SUMMER]

各 Set 实现类的性能分析

HashSet 和 TreeSet 是 Set 的两个典型实现,HashSet 的性能总是比 TreeSet 好(特别是最常用的添加、查询元素等操作),因为 TreeSet 需要额外的红黑树算法来维护集合元素的次序。只有当需要一个保持排序的 Set 时,才应该使用 TreeSet,否则都应该使用 HashSet。

LinkedHashSet 是 HashSet 的子类,它使用了链表维护元素的次序,因此在普通的插入、删除操作会比 HashSet 慢一点,但是遍历会很快。

EnumSet 是所有 Set 实现类里性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。

Set 的三个实现类 HashSet、TreeSet、EnumSet 都是线程不安全的。

4. List 集合

List 集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List 集合还允许使用重复元素,可以通过索引来访问指定位置的集合元素。List 集合默认按元素的添加顺序设置元素的索引。

ArrayList books = new ArrayList();
ArrayList books2 = new ArrayList();
books.add("疯狂Java讲义");
books.add("图解HTTP");
books.add("算法图解");
books2.add("算法图解");
books2.add("人类简史");
books2.add("中国通史");
books2.add("全球通史");
System.out.println(books);  // [疯狂Java讲义, 图解HTTP, 算法图解]
System.out.println(books2);  // [人类简史, 中国通史, 全球通史]

// 将元素插入指定位置
books.add(1, "重构");
System.out.println(books);  // [疯狂Java讲义, 重构, 图解HTTP, 算法图解]

// 将指定集合包含的所有元素插入
books.addAll(books2);
System.out.println(books);  // [疯狂Java讲义, 重构, 图解HTTP, 算法图解, 算法图解, 人类简史, 中国通史, 全球通史]

// 返回指定索引的元素
System.out.println(books.get(3));  // 算法图解
// 返回指定元素第一次出现的索引
System.out.println(books.indexOf("算法图解"));  // 3
System.out.println(books.indexOf("浪潮之巅"));  // -1
// 返回指定元素最后一次出现的索引
System.out.println(books.lastIndexOf("算法图解"));  // 4
// 删除指定元素
books.remove("人类简史");  // [疯狂Java讲义, 重构, 图解HTTP, 算法图解, 算法图解, 中国通史, 全球通史]
System.out.println(books);
// 替换知道你位置元素
books.set(4, "什么是数学");
System.out.println(books);  // [疯狂Java讲义, 重构, 图解HTTP, 算法图解, 什么是数学, 中国通史, 全球通史]
// 返回指定索引区间的子集合
System.out.println(books.subList(1, 3));  // [重构, 图解HTTP]
// 使用自定义规则排序
books.sort((a, b) -> ((String) a).length() - ((String) b).length());  // 以字符串长度排序
System.out.println(books);  // [重构, 算法图解, 中国通史, 全球通史, 什么是数学, 图解HTTP, 疯狂Java讲义]
// 使用自定义规则替换集合所有元素
books.replaceAll(ele -> "A-" + ele);
System.out.println(books);  // [A-重构, A-算法图解, A-中国通史, A-全球通史, A-什么是数学, A-图解HTTP, A-疯狂Java讲义]

5. Queue 集合

Queue 用于模拟队列这种数据结构,队列通常是指“先进先出”(FIFO)的容器。队列的头部保存在队列中存放时间最长的元素,队列的尾部保存在队列中存放时间最短的元素。新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素。通常,队列不允许随机访问队列中的元素。

// PriorityQueue 实现类
PriorityQueue pq = new PriorityQueue();
// 将指定元素添加到队列尾部,当使用容量限制的队列时,offer方法更好
pq.add(6);
pq.offer(-3);
pq.offer(20);
pq.offer(18);
System.out.println(pq);  // [-3, 6, 20, 18]
// 获取队列头部元素,不删除
System.out.println(pq.peek());  // -3
// 获取队列的第一个元素,并删除
System.out.println(pq.poll());  // -3
System.out.println(pq);  // [6, 18, 20]
System.out.println(pq.remove());  // 6
System.out.println(pq);  // [18, 20]

// ArrayDeque 实现类
// 1. 当做栈来使用
ArrayDeque stack = new ArrayDeque();
// 入栈
stack.push("张楚岚");
stack.push("冯宝宝");
stack.push("张灵玉");
System.out.println(stack);  // [张灵玉, 冯宝宝, 张楚岚]
// 访问第一个元素,但不删除
System.out.println(stack.peek());  // 张灵玉
// pop 出第一个元素
System.out.println(stack.pop());  // 张灵玉
System.out.println(stack);  // [冯宝宝, 张楚岚]

// 2. 当做队列来使用
ArrayDeque queue = new ArrayDeque();
// 加入队列
queue.offer("张楚岚");
queue.offer("冯宝宝");
queue.offer("张灵玉");
System.out.println(queue);  // [张楚岚, 冯宝宝, 张灵玉]
// 访问队列头部,不删除
System.out.println(queue.peek());  // 张楚岚
// poll 出第一个元素
System.out.println(queue.poll());  // 张楚岚
System.out.println(queue);  // [冯宝宝, 张灵玉]

// LinkedList 实现类
LinkedList team = new LinkedList();
// 将元素加入队列尾部
team.offer("张楚岚");
// 将元素加入栈顶部
team.push("王也");
// 将元素加入队列头部
team.offerFirst("张灵玉");
System.out.println(team);  // [张灵玉, 王也, 张楚岚]
// 遍历集合
for (int i = 0; i < team.size(); i++) {
    System.out.println(i + 1 + "." + team.get(i));
}
// 访问并不删除栈顶元素、队列最后一个元素
System.out.println(team.peekFirst());  // 张灵玉
System.out.println(team.peekLast());  // 张楚岚
// 将栈顶元素弹出
System.out.println(team.pop());  // 张灵玉
System.out.println(team);  // [王也, 张楚岚]
// 访问并删除最后一个元素
System.out.println(team.pollLast());  // 张楚岚
System.out.println(team);  // [王也]

各种线性表的性能分析

Java 提供的 List 就是一个线性表接口,而 ArrayList、LinkedList 又是线性表的两种典型实现:基于数组的线性表和基于链的线性表。Queue 代表了队列,Deque 代表了双端队列(既可作为队列使用,也可作为栈使用)。

一般来说,由于数组以一块连续内存区来保存所有的数组元素,所以数组在随机访问时性能最好,所有的内部以数组作为底层实现的集合在随机访问时性能都比较好;而内部以链表作为底层实现的集合在执行插入、删除操作时有较好的性能。但总体来说,ArrayList 的性能比 LinkedList 的性能要好,因此大部分时候都应该考虑使用 ArrayList。

关于使用 List 集合有如下建议:

  • 如果需要遍历 List 集合元素,对于 ArrayList、Vector 集合,应该使用随机访问方法(get)来遍历集合元素,这样性能更好;对于 LinkedList 集合,则应该采用迭代器(Iterator)来遍历集合元素。
  • 如果需要经常执行插入、删除操作来改变包含大量数据的 List 集合的大小,可考虑使用 LinkedList 集合。使用 ArrayList、Vector 集合可能需要经常重新分配内部数组的大小,效果可能较差。
  • 如果有多个线程需要同时访问 List 集合中的元素,开发者可考虑使用 Collections 将集合包装成线程安全的集合。

6. Map 集合

Map 用于保存具有映射关系的数据,保存两组值(key-value)。Map 的 key 不允许重复。

// HashMap 实现类
HashMap roles = new HashMap();
roles.put("张楚岚", "炁体源流");
roles.put("王也", "风后奇门");
roles.put("风星潼", "拘灵遣将");
roles.put("王并", "拘灵遣将");
roles.put("马仙洪", "神机百炼");

System.out.println(roles);  // {马仙洪=神机百炼, 风星潼=拘灵遣将, 张楚岚=炁体源流, 王并=拘灵遣将, 王也=风后奇门}
// 覆盖原有 value 值
roles.put("张楚岚", "神明灵");
System.out.println(roles);  // {马仙洪=神机百炼, 风星潼=拘灵遣将, 张楚岚=神明灵, 王并=拘灵遣将, 王也=风后奇门}
// 判断是否包含指定 key
System.out.println(roles.containsKey("张楚岚"));  // true
System.out.println(roles.containsKey("冯宝宝"));  // false
// 判断是否包含指定 value
System.out.println(roles.containsValue("拘灵遣将"));  // true
// 返回指定 key 对应的 value
System.out.println(roles.get("王也"));  // 风后奇门
System.out.println(roles.get("张灵玉"));  // null
// 查询集合是否为空
System.out.println(roles.isEmpty());  // false
// 返回所有 key 组成的 Set 集合
System.out.println(roles.keySet());  // [马仙洪, 风星潼, 张楚岚, 王并, 王也]
// 删除指定 key 所对应的 key-value 对
roles.remove("王并");
System.out.println(roles);  // {马仙洪=神机百炼, 风星潼=拘灵遣将, 张楚岚=神明灵, 王也=风后奇门}
// 返回 key-value 对个数
System.out.println(roles.size());  // 4
// 查找并替换新的 key-value对
System.out.println(roles.replace("张楚岚", "金光咒"));  // 神明灵
System.out.println(roles.replace("冯宝宝", "阿威十八式"));  // null
System.out.println(roles);  // {马仙洪=神机百炼, 风星潼=拘灵遣将, 张楚岚=金光咒, 王也=风后奇门}
// 使用自定方法改变 value
roles.merge("张楚岚", "加强版", (oldVal, param) -> "" + oldVal + param);
System.out.println(roles.get("张楚岚"));  // 金光咒加强版
// 当key对应value为null时,通过key计算一个新结果,否则覆盖原有value,如果不存在此key,则创建一个新的key-value对
roles.computeIfAbsent("冯宝宝", key -> "机智一比" + key);
System.out.println(roles);  // {马仙洪=神机百炼, 冯宝宝=机智一比冯宝宝, 风星潼=拘灵遣将, 张楚岚=金光咒加强版, 王也=风后奇门}
// 计算一个新结果
roles.computeIfPresent("冯宝宝", (key, value) -> key + "自称" + value);
System.out.println(roles.get("冯宝宝"));  // 冯宝宝自称机智一比冯宝宝

// LinkedHashMap 实现类
LinkedHashMap scores = new LinkedHashMap();
scores.put("语文", 80);
scores.put("数学", 92);
scores.put("英语", 76);
scores.put("物理", 89);
// 调用 foreach() 方法遍历
scores.forEach((key, val) -> System.out.println(key + "-->" + val));
System.out.println(scores);  // {语文=80, 数学=92, 英语=76, 物理=89}

// 使用 Properties 读写属性文件
Properties props = new Properties();
props.setProperty("username", "Lee");
props.setProperty("password", "123456");
props.setProperty("role", "admin");
props.setProperty("email", "[email protected]");
// 将集合保存到文件 a.ini 中
try {
    props.store(new FileOutputStream("a.ini"), "comment line");
} catch (Exception e) {
    e.printStackTrace();
}

// TreeMap 实现类 常用方法与 TreeSet 类似
TreeMap tm = new TreeMap();
tm.putAll(scores);
System.out.println(tm);  // {数学=92, 物理=89, 英语=76, 语文=80}

// WeakHashMap 实现类 key 只保留对象的弱引用,可能会被垃圾回收
WeakHashMap whm = new WeakHashMap();
whm.put(new String("语文"), new String("良好"));
whm.put(new String("数学"), new String("优秀"));
whm.put(new String("英语"), new String("及格"));
whm.put("物理", new String("中等"));
System.out.println(whm);  // {数学=优秀, 英语=及格, 物理=中等, 语文=良好}
// 通知系统立即进行垃圾回收
System.gc();
System.runFinalization();
System.out.println(whm);  // {物理=中等}

// IdentityHashMap 实现类 只有两个 key 严格相等时才认为两个 key 相等
IdentityHashMap ihm = new IdentityHashMap();
ihm.put(new String("java"), 89);
ihm.put(new String("java"), 56);
ihm.put("html", 90);
ihm.put("html", 98);
System.out.println(ihm);  // {html=98, java=89, java=56}

// EnumMap 实现类
EnumMap em = new EnumMap(Season.class);
em.put(Season.SUMMER, "夏日炎炎");
em.put(Season.SPRING, "春暖花开");
System.out.println(em);  // {SPRING=春暖花开, SUMMER=夏日炎炎}

各 Map 实现类的性能分析

TreeMap 通常比 HashMap 要慢(尤其在插入、删除 key-value 对时更慢),因为 TreeMap 底层采用红黑树来管理 key-value 对。

使用 TreeMap 有一个好处:TreeMap 中的 key-value 对总是处于有序状态,无须专门进行排序操作。当 TreeMap 被填充之后,就可以调用 keySet(),取得由 key 组成的 Set,然后使用 toArray() 方法生成 key 的数组,接下来使用 Arrays 的 binarySearch() 方法在已排序的数组中快速地查询对象。

对于一般的应用场景,程序应该多考虑使用 HashMap,因为 HashMap 正式为快速查询设计的。如果程序需要一个总是排好序的 Map 时,则可以考虑使用 TreeMap。

7. HashSet 和 HashMap 的性能选项

  • HashSet、HashMap 的构造器允许指定一个负载极限(默认0.75);
    • 较高的“负载极限”可以降低 hash 表所占的内存空间,但会增加查询数据的时间开销;
    • 较低的“负载极限”可以增加 hash 表所占的内存空间,但会提高查询性能。
  • 如果开始就知道集合需要保存很多记录,可以在创建时就使用较大的初始化容量。

8. 操作集合的工具类:Collections

ArrayList nums = new ArrayList();
nums.add(2);
nums.add(-5);
nums.add(3);
nums.add(0);
nums.add(7);
System.out.println(nums);  // [2, -5, 3, 0, 7]
// 反转
Collections.reverse(nums);
System.out.println(nums);  // [7, 0, 3, -5, 2]
// 排序
Collections.sort(nums);
System.out.println(nums);  // [-5, 0, 2, 3, 7]
// 指定排序规则
Collections.sort(nums, (a, b) -> (int)b - (int)a);
System.out.println(nums);  // [7, 3, 2, 0, -5]
// 指定两位置元素互换
Collections.swap(nums, 1, 3);
System.out.println(nums);  // [7, 0, 2, 3, -5]
// distance 为负数时,将集合后 distance 个元素整体移到前面;
// distance 为正数时,将集合后 distance 个元素整体移到后面。
Collections.rotate(nums, -3);
System.out.println(nums);  // [3, -5, 7, 0, 2]
Collections.rotate(nums, 2);
System.out.println(nums);  // [0, 2, 3, -5, 7]
// 对集合元素进行随机排序(模拟“洗牌”)
Collections.shuffle(nums);
System.out.println(nums);  // 每次输出不同
// 输出最大、最小元素
System.out.println(Collections.max(nums));  // 7
System.out.println(Collections.min(nums));  // -5
// 使用新值替换旧值
Collections.replaceAll(nums, 0, 1);
System.out.println(nums);  // [-5, 7, 2, 3, 1]
// 使用指定元素替换集合中所有元素
Collections.fill(nums, 6);
System.out.println(nums);  // [6, 6, 6, 6, 6]
// 返回集合中指定元素出现的次数
System.out.println(Collections.frequency(nums, 6));  // 5

// 设置不可变集合
// 创建空的不可变集合
List unList = Collections.emptyList();
// 创建只有一个元素的不可变集合
Set unSet = Collections.singleton("java");
// 创建普通的不可变集合
HashMap hm = new HashMap();
hm.put("周伯通", "老顽童");
hm.put("黄药师", "东邪");
Map unMap = Collections.unmodifiableMap(hm);

// 以下三条都将引发 UnsupportedOperationException 异常
// unList.add("test");
// unSet.add("test");
// unMap.put("裘千仞", "铁掌水上飘");

// Java 9 新增不可变集合
Set set = Set.of("Java", "C++", "Python", "JavaScript");
System.out.println(set);  // [Java, C++, Python, JavaScript]
// set.add("Ruby");  // 报 UnsupportedOperationException 异常

List list = List.of("HTML", "CSS", "Vue", "Node");
System.out.println(list);  // [HTML, CSS, Vue, Node]
// list.remove(1);  // 报 UnsupportedOperationException 异常

Map map = Map.of("语文", 89, "数学", 92, "英语", 76);
System.out.println(map);  // {数学=92, 英语=76, 语文=89}

Map map2 = Map.ofEntries(Map.entry("物理", 98),
                         Map.entry("化学", 83),
                         Map.entry("生物", 92));
System.out.println(map2);  // {物理=98, 生物=92, 化学=83}

9. 练习

  • 创建一个 Set 集合,并用 Set 集合保存用户通过控制台输入的 20 个字符串。
  • 创建一个 List 集合,并随意添加 10 个元素。然后获取索引为 5 处的元素;再获取其中某 2 个元素的索引;再删除索引为 3 处的元素。
  • 给定 [“a”, “b”, “a”, “b”, “c”, “a”, “b”, “c”, “b”] 字符串数组,然后使用 Map 的 key 来保存数组中字符串元素,value 保存该字符串元素的出现次数,最后统计出各字符串元素的出现次数,
  • 将本章未完成的梭哈游戏补充完整,不断地添加梭哈规则,开发一个控制台的梭哈游戏。
  • 将学到的知识和遇到的问题,整理成笔记,记录下来

你可能感兴趣的:(Java学习)