Java 中文官方教程 2022 版(二十七)(3)

简介: Java 中文官方教程 2022 版(二十七)

Java 中文官方教程 2022 版(二十七)(2)https://developer.aliyun.com/article/1486849

干扰

流操作中的 Lambda 表达式不应该干扰。当流的源在流水线处理流时被修改时就会发生干扰。例如,下面的代码尝试连接List listOfStrings 中包含的字符串。然而,它会抛出ConcurrentModificationException

try {
    List<String> listOfStrings =
        new ArrayList<>(Arrays.asList("one", "two"));
    // This will fail as the peek operation will attempt to add the
    // string "three" to the source after the terminal operation has
    // commenced. 
    String concatenatedString = listOfStrings
        .stream()
        // Don't do this! Interference occurs here.
        .peek(s -> listOfStrings.add("three"))
        .reduce((a, b) -> a + " " + b)
        .get();
    System.out.println("Concatenated string: " + concatenatedString);
} catch (Exception e) {
    System.out.println("Exception caught: " + e.toString());
}

此示例使用reduce操作将listOfStrings 中包含的字符串连接成一个Optional 值,reduce是一个终端操作。然而,此处的管道调用了中间操作peek,它试图向listOfStrings添加一个新元素。请记住,所有中间操作都是惰性的。这意味着在此示例中,管道在调用操作get时开始执行,并在get操作完成时结束执行。peek操作的参数在管道执行过程中尝试修改流源,这会导致 Java 运行时抛出ConcurrentModificationException

有状态的 Lambda 表达式

避免在流操作中将有状态的 lambda 表达式用作参数。有状态的 lambda 表达式是指其结果取决于在管道执行过程中可能发生变化的任何状态。以下示例使用map中间操作将List listOfIntegers 中的元素添加到新的List实例中。它分别使用串行流和并行流执行两次:

List<Integer> serialStorage = new ArrayList<>();
System.out.println("Serial stream:");
listOfIntegers
    .stream()
    // Don't do this! It uses a stateful lambda expression.
    .map(e -> { serialStorage.add(e); return e; })
    .forEachOrdered(e -> System.out.print(e + " "));
System.out.println("");
serialStorage
    .stream()
    .forEachOrdered(e -> System.out.print(e + " "));
System.out.println("");
System.out.println("Parallel stream:");
List<Integer> parallelStorage = Collections.synchronizedList(
    new ArrayList<>());
listOfIntegers
    .parallelStream()
    // Don't do this! It uses a stateful lambda expression.
    .map(e -> { parallelStorage.add(e); return e; })
    .forEachOrdered(e -> System.out.print(e + " "));
System.out.println("");
parallelStorage
    .stream()
    .forEachOrdered(e -> System.out.print(e + " "));
System.out.println("");

Lambda 表达式e -> { parallelStorage.add(e); return e; }是一个有状态的 lambda 表达式。其结果可能每次运行代码时都会有所不同。此示例打印如下内容:

Serial stream:
8 7 6 5 4 3 2 1
8 7 6 5 4 3 2 1
Parallel stream:
8 7 6 5 4 3 2 1
1 3 6 2 4 5 8 7

操作forEachOrdered按照流指定的顺序处理元素,无论流是串行还是并行执行。然而,当流并行执行时,map操作处理由 Java 运行时和编译器指定的流元素。因此,lambda 表达式e -> { parallelStorage.add(e); return e; }List parallelStorage添加元素的顺序可能每次运行代码时都会有所不同。为了获得确定性和可预测的结果,请确保流操作中的 lambda 表达式参数不是有状态的。

注意:此示例调用了方法synchronizedList,以使List parallelStorage 线程安全。请记住,集合不是线程安全的。这意味着多个线程不应同时访问特定集合。假设在创建parallelStorage时未调用方法synchronizedList

List<Integer> parallelStorage = new ArrayList<>();

该示例行为不稳定,因为多个线程访问和修改parallelStorage而没有像同步这样的机制来安排特定线程何时可以访问List实例。因此,该示例可能打印类似以下内容的输出:

Parallel stream:
8 7 6 5 4 3 2 1
null 3 5 4 7 8 1 2

问题和练习:聚合操作

原文:docs.oracle.com/javase/tutorial/collections/streams/QandE/questions.html

问题

  1. 一系列的聚合操作被称为 ___。
  2. 每个流水线包含零个或多个 ___ 操作。
  3. 每个流水线以一个 ___ 操作结束。
  4. 什么样的操作以另一个流作为输出?
  5. 描述forEach聚合操作与增强的for语句或迭代器之间的一个区别。
  6. 真或假:流类似于集合,因为它是一个存储元素的数据结构。
  7. 在这段代码中识别中间操作和终端操作:
double average = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .mapToInt(Person::getAge)
    .average()
    .getAsDouble();
  1. 代码p -> p.getGender() == Person.Sex.MALE是什么的一个例子?
  2. 代码Person::getAge是什么的一个例子?
  3. 将流的内容组合并返回一个值的终端操作被称为什么?
  4. Stream.reduce方法和Stream.collect方法之间的一个重要区别是什么?
  5. 如果你想处理一个包含姓名的流,提取男性姓名,并将它们存储在一个新的List中,那么Stream.reduceStream.collect是最合适的操作?
  6. 真或假:聚合操作使得可以在非线程安全的集合中实现并行性。
  7. 流通常是串行的,除非另有规定。如何请求以并行方式处理流?

练习

  1. 将以下增强的for语句编写为使用 lambda 表达式的流水线。提示:使用filter中间操作和forEach终端操作。
for (Person p : roster) {
    if (p.getGender() == Person.Sex.MALE) {
        System.out.println(p.getName());
    }
}
  1. 将以下代码转换为一个使用 lambda 表达式和聚合操作而不是嵌套for循环的新实现。提示:创建一个依次调用filtersortedcollect操作的流水线。
List<Album> favs = new ArrayList<>();
for (Album a : albums) {
    boolean hasFavorite = false;
    for (Track t : a.tracks) {
        if (t.rating >= 4) {
            hasFavorite = true;
            break;
        }
    }
    if (hasFavorite)
        favs.add(a);
}
Collections.sort(favs, new Comparator<Album>() {
                           public int compare(Album a1, Album a2) {
                               return a1.name.compareTo(a2.name);
                           }});

检查你的答案。

教训:实现

原文:docs.oracle.com/javase/tutorial/collections/implementations/index.html

实现是用于存储集合的数据对象,实现了接口部分中描述的接口。本课程描述了以下类型的实现:

  • 通用实现是最常用的实现,设计用于日常使用。它们在标题为通用实现的表中总结。
  • 特殊用途实现设计用于特殊情况,并显示非标准性能特征、使用限制或行为。
  • 并发实现旨在支持高并发性,通常以牺牲单线程性能为代价。这些实现是java.util.concurrent包的一部分。
  • 包装实现通常与其他类型的实现结合使用,通常是通用实现,以提供附加或受限功能。
  • 便利实现是迷你实现,通常通过静态工厂方法提供,为特殊集合提供方便、高效的替代方案(例如,单例集合)。
  • 抽象实现是骨架实现,有助于构建自定义实现,稍后在自定义集合实现部分中描述。这是一个高级主题,不是特别困难,但相对较少的人会需要这样做。

通用实现总结如下表所示。

通用实现

接口 哈希表实现 可调整大小数组实现 树实现 链表实现 哈希表 + 链表实现
Set HashSet TreeSet LinkedHashSet
List ArrayList LinkedList
Queue
Deque ArrayDeque LinkedList
Map HashMap TreeMap LinkedHashMap

正如您从表中所看到的,Java 集合框架提供了几个通用实现的SetListMap接口。在每种情况下,一个实现——HashSetArrayListHashMap——显然是大多数应用程序中要使用的实现,其他条件相等。请注意,SortedSetSortedMap接口在表中没有行。每个接口都有一个实现(TreeSetTreeMap),并列在SetMap行中。有两个通用的Queue实现——LinkedList,也是List实现,和PriorityQueue,在表中被省略。这两个实现提供非常不同的语义:LinkedList提供 FIFO 语义,而PriorityQueue根据其值对元素进行排序。

每个通用实现都提供其接口中包含的所有可选操作。所有允许null元素、键和值。没有同步(线程安全)。所有都有快速失败迭代器,在迭代期间检测到非法并发修改,并快速干净地失败,而不是在未来的某个不确定时间冒险出现任意、非确定性的行为。所有都是Serializable,并支持公共clone方法。

这些实现不同步的事实代表了与过去的断裂:传统集合VectorHashtable是同步的。采取当前方法是因为在同步没有好处时集合经常被使用。这些用途包括单线程使用、只读使用以及作为执行自身同步的较大数据对象的一部分使用。一般来说,良好的 API 设计实践是不让用户为他们不使用的功能付费。此外,不必要的同步可能在某些情况下导致死锁。

如果你需要线程安全的集合,包装器实现部分描述的同步包装器允许任何集合转换为同步集合。因此,同步对于通用实现是可选的,而对于传统实现是强制的。此外,java.util.concurrent包提供了BlockingQueue接口的并发实现,它扩展了Queue,以及ConcurrentMap接口的并发实现,它扩展了Map。这些实现比单纯的同步实现具有更高的并发性。

通常情况下,你应该考虑接口,而不是实现。这就是为什么本节中没有编程示例。在很大程度上,实现的选择只影响性能。如接口部分所述,首选的风格是在创建Collection时选择一个实现,并立即将新集合分配给相应接口类型的变量(或将集合传递给期望接口类型参数的方法)。通过这种方式,程序不会依赖于给定实现中添加的任何方法,使程序员可以自由更改实现,只要性能或行为细节需要。

接下来的部分简要讨论了实现。使用诸如常数时间对数线性n log(n)二次等词语描述了实现的性能,以指代执行操作的时间复杂度的渐近上界。所有这些都是相当复杂的术语,如果你不知道它们的含义也没关系。如果你对更多信息感兴趣,请参考任何一本优秀的算法教材。要记住的一件事是,这种性能指标有其局限性。有时,名义上更慢的实现可能更快。如果有疑问,就要测量性能!

集合实现

原文:docs.oracle.com/javase/tutorial/collections/implementations/set.html

Set实现分为通用实现和特殊实现两类。

通用集合实现

有三种通用的Set实现 — HashSet, TreeSet, 和 LinkedHashSet。在这三种中选择哪一种通常很简单。HashSetTreeSet快得多(大部分操作的时间复杂度是常数时间对数时间),但不提供排序保证。如果你需要使用SortedSet接口中的操作,或者需要按值排序的迭代,使用TreeSet;否则,使用HashSet。很可能大部分时间你会使用HashSet

LinkedHashSet在某种意义上介于HashSetTreeSet之间。作为一个带有链表的哈希表,它提供了插入顺序的迭代(最近插入的到最近插入的)并且运行速度几乎和HashSet一样快。LinkedHashSet实现避免了HashSet提供的未指定、通常混乱的排序,而又不会增加TreeSet的成本。

关于HashSet值得注意的一点是,迭代在条目数和桶数(容量)的总和上是线性的。因此,选择一个初始容量太高会浪费空间和时间。另一方面,选择一个初始容量太低会浪费时间,因为每次强制增加容量时都需要复制数据结构。如果你不指定初始容量,默认值是 16。过去,选择一个质数作为初始容量有一些优势。但现在不再成立。在内部,容量总是向上舍入为 2 的幂。初始容量是通过使用int构造函数指定的。以下代码行分配了一个初始容量为 64 的HashSet

Set<String> s = new HashSet<String>(64);

HashSet类还有一个称为负载因子的调整参数。如果你非常关心你的HashSet的空间消耗,阅读HashSet文档以获取更多信息。否则,只需接受默认值;这几乎总是正确的做法。

如果你接受默认的负载因子但想指定一个初始容量,选择一个大约是你期望集合增长到两倍大小的数字。如果你的猜测完全错误,可能会浪费一点空间、时间或两者,但这不太可能成为一个大问题。

LinkedHashSet具有与HashSet相同的调整参数,但迭代时间不受容量影响。TreeSet没有调整参数。

特殊用途的 Set 实现

有两个特殊用途的Set实现 — EnumSetCopyOnWriteArraySet

EnumSet是用于枚举类型的高性能Set实现。枚举集合的所有成员必须是相同的枚举类型。在内部,它由一个位向量表示,通常是一个long。枚举集合支持对枚举类型范围的迭代。例如,给定一周中的工作日的枚举声明,你可以迭代工作日。EnumSet类提供了一个静态工厂,使其易于使用。

for (Day d : EnumSet.range(Day.MONDAY, Day.FRIDAY))
        System.out.println(d);

枚举集合也为传统位标志提供了丰富的、类型安全的替代品。

EnumSet.of(Style.BOLD, Style.ITALIC)

CopyOnWriteArraySet是一个由写时复制数组支持的Set实现。所有的变动操作,比如addsetremove,都是通过创建数组的新副本来实现的;永远不需要加锁。即使在元素插入和删除的同时进行迭代也是安全的。与大多数Set实现不同,addremovecontains方法所需的时间与集合大小成正比。这种实现适用于很少修改但频繁迭代的集合。它非常适合维护必须防止重复的事件处理程序列表。

列表实现

原文:docs.oracle.com/javase/tutorial/collections/implementations/list.html

List实现分为通用和特殊用途的实现。

通用列表实现

有两种通用的List实现——ArrayListLinkedList。大多数情况下,你可能会使用ArrayList,它提供常数时间的位置访问,并且非常快速。它不必为List中的每个元素分配一个节点对象,并且在需要同时移动多个元素时可以利用System.arraycopy。将ArrayList视为没有同步开销的Vector

如果你经常在List的开头添加元素或者迭代List以删除其内部的元素,你应该考虑使用LinkedList。这些操作在LinkedList中需要常数时间,在ArrayList中需要线性时间。但你会在性能上付出很大的代价。在LinkedList中,位置访问需要线性时间,在ArrayList中需要常数时间。此外,LinkedList的常数因子要糟糕得多。如果你认为你想使用LinkedList,在做出选择之前用LinkedListArrayList测量你的应用程序的性能;ArrayList通常更快。

ArrayList有一个调整参数——初始容量,指的是ArrayList在增长之前可以容纳的元素数量。LinkedList没有调整参数,但有七个可选操作,其中之一是clone。另外六个是addFirstgetFirstremoveFirstaddLastgetLastremoveLastLinkedList还实现了Queue接口。

特殊用途的列表实现

CopyOnWriteArrayList是一个由写入时复制数组支持的List实现。这种实现与CopyOnWriteArraySet类似。即使在迭代期间,也不需要同步,并且迭代器永远不会抛出ConcurrentModificationException。这种实现非常适合维护事件处理程序列表,其中变化不频繁,遍历频繁且可能耗时。

如果你需要同步,Vector比使用Collections.synchronizedList同步的ArrayList稍快一些。但Vector有很多遗留操作,所以一定要小心,始终使用List接口操作Vector,否则你将无法在以后替换实现。

如果你的List大小是固定的 — 也就是说,你永远不会使用removeadd或者除了containsAll之外的任何批量操作 — 那么你有第三个选项,绝对值得考虑。查看方便实现部分的Arrays.asList获取更多信息。

Map 实现

原文:docs.oracle.com/javase/tutorial/collections/implementations/map.html

Map实现分为通用、特殊和并发实现。

通用 Map 实现

三种通用Map实现是HashMapTreeMapLinkedHashMap。如果需要SortedMap操作或基于键排序的Collection-view 迭代,请使用TreeMap;如果希望最大速度且不关心迭代顺序,请使用HashMap;如果希望接近HashMap性能且插入顺序迭代,请使用LinkedHashMap。在这方面,Map的情况类似于Set。同样,Set Implementations 部分中的其他所有内容也适用于Map实现。

LinkedHashMap提供了两个LinkedHashSet不可用的功能。当您创建一个LinkedHashMap时,您可以根据键访问而不是插入对其进行排序。换句话说,仅查找与键关联的值会将该键移到地图的末尾。此外,LinkedHashMap提供了removeEldestEntry方法,可以被覆盖以在向地图添加新映射时自动实施删除过时映射的策略。这使得实现自定义缓存非常容易。

例如,这个覆盖将允许地图增长到多达 100 个条目,然后每次添加新条目时都会删除最老的条目,保持 100 个条目的稳定状态。

private static final int MAX_ENTRIES = 100;
protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > MAX_ENTRIES;
}

特殊用途 Map 实现

有三种特殊用途的 Map 实现 — EnumMapWeakHashMapIdentityHashMapEnumMap,内部实现为array,是用于枚举键的高性能Map实现。此实现将Map接口的丰富性和安全性与接近数组的速度结合在一起。如果要将枚举映射到值,应始终优先使用EnumMap而不是数组。

WeakHashMapMap接口的一个实现,只存储对其键的弱引用。只存储弱引用允许在其键不再在WeakHashMap之外被引用时,键值对可以被垃圾回收。这个类提供了利用弱引用功能的最简单方法。它对于实现“注册表样”数据结构非常有用,其中一个条目的实用性在其键不再被任何线程引用时消失。

IdentityHashMap 是基于哈希表的基于身份的Map实现。这个类对于保持拓扑结构的对象图转换非常有用,比如序列化或深拷贝。为了执行这样的转换,你需要维护一个基于身份的“节点表”,用于跟踪哪些对象已经被看到。基于身份的映射也用于在动态调试器和类似系统中维护对象到元信息的映射。最后,基于身份的映射对于阻止由于故意扭曲的equals方法而导致的“欺骗攻击”非常有用,因为IdentityHashMap永远不会在其键上调用equals方法。这个实现的一个额外好处是它很快。

并发映射实现

java.util.concurrent 包含了ConcurrentMap 接口,它通过原子的putIfAbsentremovereplace方法扩展了Map,以及该接口的ConcurrentHashMap 实现。

ConcurrentHashMap 是一个高度并发、高性能的哈希表实现。在执行检索操作时,此实现永远不会阻塞,并允许客户端选择更新的并发级别。它旨在作为Hashtable的一个可替换项:除了实现ConcurrentMap外,它还支持所有Hashtable特有的传统方法。再次强调,如果你不需要传统操作,请小心使用ConcurrentMap接口来操作它。

队列实现

原文:docs.oracle.com/javase/tutorial/collections/implementations/queue.html

Queue 实现分为通用和并发实现。

通用队列实现

如前一节所述,LinkedList 实现了 Queue 接口,为 addpoll 等提供先进先出(FIFO)队列操作。

PriorityQueue 类是基于 数据结构的优先队列。此队列根据在构造时指定的顺序对元素进行排序,可以是元素的自然顺序或由显式 Comparator 强加的顺序。

队列检索操作 — pollremovepeekelement — 访问队列头部的元素。队列的 头部 是相对于指定顺序的最小元素。如果多个元素具有最小值,则头部是这些元素之一;平局将被任意打破。

PriorityQueue 及其迭代器实现了 CollectionIterator 接口的所有可选方法。在 iterator 方法中提供的迭代器不能保证以任何特定顺序遍历 PriorityQueue 的元素。对于有序遍历,请考虑使用 Arrays.sort(pq.toArray())

并发队列实现

java.util.concurrent 包含一组同步的 Queue 接口和类。BlockingQueue 扩展了 Queue,具有在检索元素时等待队列变得非空以及在存储元素时等待队列中有空间可用的操作。该接口由以下类实现:

在 JDK 7 中,TransferQueue是一个专门的BlockingQueue,其中向队列添加元素的代码可以选择等待(阻塞),直到另一个线程中的代码检索元素。TransferQueue只有一个实现:

Deque 实现

原文:docs.oracle.com/javase/tutorial/collections/implementations/deque.html

Deque 接口,发音为*“deck”*, 代表双端队列。Deque 接口可以被实现为各种类型的CollectionsDeque 接口的实现被分为通用和并发实现。

通用 Deque 实现

通用实现包括LinkedListArrayDeque 类。Deque 接口支持在两端插入、删除和检索元素。ArrayDeque 类是Deque 接口的可调整大小数组实现,而LinkedList 类是列表实现。

Deque 接口中的基本插入、删除和检索操作有addFirstaddLastremoveFirstremoveLastgetFirstgetLastaddFirst 方法在头部添加元素,而addLast 方法在Deque 实例的尾部添加元素。

LinkedList 实现比ArrayDeque 实现更灵活。LinkedList 实现了所有可选的列表操作。LinkedList 实现允许null元素,但ArrayDeque 实现不允许。

就效率而言,ArrayDeque 在两端的添加和删除操作上比LinkedList 更高效。在迭代过程中,LinkedList 实现中最好的操作是删除当前元素。LinkedList 实现不是理想的迭代结构。

LinkedList 实现比ArrayDeque 实现消耗更多内存。对于ArrayDeque 实例的遍历,可以使用以下任意一种:

foreach

foreach 是一种快速且适用于各种列表的方法。

ArrayDeque<String> aDeque = new ArrayDeque<String>();
. . .
for (String str : aDeque) {
    System.out.println(str);
}

迭代器

Iterator 可用于所有类型的数据的所有类型列表的正向遍历。

ArrayDeque<String> aDeque = new ArrayDeque<String>();
. . .
for (Iterator<String> iter = aDeque.iterator(); iter.hasNext();  ) {
    System.out.println(iter.next());
}

ArrayDeque 类在本教程中用于实现Deque 接口。本教程中使用的示例完整代码在ArrayDequeSample中可用。LinkedListArrayDeque 类都不支持多线程并发访问。

并发 Deque 实现

LinkedBlockingDeque 类是Deque 接口的并发实现。如果双端队列为空,则takeFirsttakeLast 等方法会等待直到元素变为可用,然后检索并移除相同的元素。

包装器实现

原文:docs.oracle.com/javase/tutorial/collections/implementations/wrapper.html

包装器实现将所有真正的工作委托给指定的集合,但在该集合提供的功能之上添加额外的功能。对于设计模式爱好者,这是装饰者模式的一个例子。虽然这可能看起来有点奇特,但实际上非常简单。

这些实现是匿名的;而不是提供一个公共类,库提供了一个静态工厂方法。所有这些实现都在 Collections 类中,该类仅包含静态方法。

同步包装器

同步包装器为任意集合添加了自动同步(线程安全)。六个核心集合接口 — Collection, Set, List, Map, SortedSet, 和 SortedMap — 都有一个静态工厂方法。

public static <T> Collection<T> synchronizedCollection(Collection<T> c);
public static <T> Set<T> synchronizedSet(Set<T> s);
public static <T> List<T> synchronizedList(List<T> list);
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m);
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s);
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m);

这些方法中的每一个都返回由指定集合支持的同步(线程安全)Collection。为了保证串行访问,所有对支持集合的访问必须通过返回的集合完成。确保这一点的简单方法是不保留对支持集合的引用。使用以下技巧创建同步集合。

List<Type> list = Collections.synchronizedList(new ArrayList<Type>());

以这种方式创建的集合与通常同步的集合(如 Vector)一样线程安全。

面对并发访问时,在迭代时,用户必须手动对返回的集合进行同步。原因是迭代是通过对集合的多次调用完成的,这些调用必须组合成单个原子操作。以下是迭代包装同步集合的惯用法。

Collection<Type> c = Collections.synchronizedCollection(myCollection);
synchronized(c) {
    for (Type e : c)
        foo(e);
}

如果使用显式迭代器,则必须在 synchronized 块内调用 iterator 方法。不遵循此建议可能导致不确定的行为。在同步 MapCollection 视图上进行迭代的惯用法类似。在迭代任何 Collection 视图时,用户必须同步在同步的 Map 上,而不是在 Collection 视图本身上进行同步,如下例所示。

Map<KeyType, ValType> m = Collections.synchronizedMap(new HashMap<KeyType, ValType>());
    ...
Set<KeyType> s = m.keySet();
    ...
// Synchronizing on m, not s!
synchronized(m) {
    while (KeyType k : s)
        foo(k);
}

使用包装器实现的一个小缺点是,您无法执行包装实现的任何非接口操作。因此,在前面的List示例中,您无法在包装的ArrayList上调用ArrayListensureCapacity操作。

不可修改的包装器

与添加功能到包装集合的同步包装器不同,不可修改的包装器会剥夺功能。特别是,它们剥夺了通过拦截所有可能修改集合的操作并抛出UnsupportedOperationException来修改集合的能力。不可修改的包装器有两个主要用途,如下所示:

  • 使集合在构建后变为不可变。在这种情况下,最好不要保留对支持集合的引用。这绝对保证了不可变性。
  • 允许某些客户端对您的数据结构进行只读访问。您保留对支持集合的引用,但分发对包装器的引用。这样,客户端可以查看但不能修改,而您保持完全访问权限。

与同步包装器一样,每个六个核心Collection接口都有一个静态工厂方法。

public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c);
public static <T> Set<T> unmodifiableSet(Set<? extends T> s);
public static <T> List<T> unmodifiableList(List<? extends T> list);
public static <K,V> Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m);
public static <T> SortedSet<T> unmodifiableSortedSet(SortedSet<? extends T> s);
public static <K,V> SortedMap<K, V> unmodifiableSortedMap(SortedMap<K, ? extends V> m);

检查接口包装器

Collections.checked 接口包装器用于与泛型集合一起使用。这些实现返回指定集合的动态类型安全视图,如果客户端尝试添加错误类型的元素,则会抛出ClassCastException。语言中的泛型机制提供了编译时(静态)类型检查,但有可能击败此机制。动态类型安全视图完全消除了这种可能性。

便利实现

原文:docs.oracle.com/javase/tutorial/collections/implementations/convenience.html

这一部分描述了几个迷你实现,当你不需要它们的全部功能时,它们可能比通用实现更方便、更高效。本节中的所有实现都是通过静态工厂方法而不是public类提供的。

数组的列表视图

Arrays.asList方法返回其数组参数的List视图。对List的更改会写入数组,反之亦然。集合的大小与数组相同且不可更改。如果在List上调用addremove方法,将导致UnsupportedOperationException

这种实现的正常用法是作为基于数组和基于集合的 API 之间的桥梁。它允许你将数组传递给期望CollectionList的方法。然而,这种实现还有另一个用途。如果你需要一个固定大小的List,它比任何通用List实现更高效。这就是惯用法。

List<String> list = Arrays.asList(new String[size]);

注意,不会保留对支持数组的引用。

不可变多副本列表

有时你会需要一个由多个相同元素副本组成的不可变ListCollections.nCopies方法返回这样一个列表。这种实现有两个主要用途。第一个是初始化一个新创建的List;例如,假设你想要一个最初由 1,000 个null元素组成的ArrayList。下面的咒语就能实现。

List<Type> list = new ArrayList<Type>(Collections.nCopies(1000, (Type)null));

当然,每个元素的初始值不一定是null。第二个主要用途是扩展现有的List。例如,假设你想要向List的末尾添加 69 个字符串"fruit bat"的副本。不清楚为什么你想要做这样的事情,但让我们假设你确实想要。下面是如何做到的。

lovablePets.addAll(Collections.nCopies(69, "fruit bat"));

通过使用同时接受索引和CollectionaddAll形式,你可以将新元素添加到List的中间而不是末尾。

Java 中文官方教程 2022 版(二十七)(4)https://developer.aliyun.com/article/1486855

相关文章
|
7月前
|
JavaScript NoSQL Java
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
363 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
|
3月前
|
Oracle Java 关系型数据库
java 编程基础入门级超级完整版教程详解
这份文档是针对Java编程入门学习者的超级完整版教程,涵盖了从环境搭建到实际项目应用的全方位内容。首先介绍了Java的基本概念与开发环境配置方法,随后深入讲解了基础语法、控制流程、面向对象编程的核心思想,并配以具体代码示例。接着探讨了常用类库与API的应用,如字符串操作、集合框架及文件处理等。最后通过一个学生成绩管理系统的实例,帮助读者将理论知识应用于实践。此外,还提供了进阶学习建议,引导学员逐步掌握更复杂的Java技术。适合初学者系统性学习Java编程。资源地址:[点击访问](https://pan.quark.cn/s/14fcf913bae6)。
306 2
|
8月前
|
消息中间件 Java 数据库
自研Java框架 Sunrays-Framework使用教程「博客之星」
### Sunrays-Framework:助力高效开发的Java微服务框架 **Sunrays-Framework** 是一款基于 Spring Boot 构建的高效微服务开发框架,深度融合了 Spring Cloud 生态中的核心技术组件。它旨在简化数据访问、缓存管理、消息队列、文件存储等常见开发任务,帮助开发者快速构建高质量的企业级应用。 #### 核心功能 - **MyBatis-Plus**:简化数据访问层开发,提供强大的 CRUD 操作和分页功能。 - **Redis**:实现高性能缓存和分布式锁,提升系统响应速度。 - **RabbitMQ**:可靠的消息队列支持,适用于异步
自研Java框架 Sunrays-Framework使用教程「博客之星」
|
9月前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
8459 5
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
8月前
|
Java 数据库连接 数据处理
探究Java异常处理【保姆级教程】
Java 异常处理是确保程序稳健运行的关键机制。它通过捕获和处理运行时错误,避免程序崩溃。Java 的异常体系以 `Throwable` 为基础,分为 `Error` 和 `Exception`。前者表示严重错误,后者可细分为受检和非受检异常。常见的异常处理方式包括 `try-catch-finally`、`throws` 和 `throw` 关键字。此外,还可以自定义异常类以满足特定需求。最佳实践包括捕获具体异常、合理使用 `finally` 块和谨慎抛出异常。掌握这些技巧能显著提升程序的健壮性和可靠性。
133 4
|
8月前
|
存储 移动开发 算法
【潜意识Java】Java基础教程:从零开始的学习之旅
本文介绍了 Java 编程语言的基础知识,涵盖从简介、程序结构到面向对象编程的核心概念。首先,Java 是一种高级、跨平台的面向对象语言,支持“一次编写,到处运行”。接着,文章详细讲解了 Java 程序的基本结构,包括包声明、导入语句、类声明和 main 方法。随后,深入探讨了基础语法,如数据类型、变量、控制结构、方法和数组。此外,还介绍了面向对象编程的关键概念,例如类与对象、继承和多态。最后,针对常见的编程错误提供了调试技巧,并总结了学习 Java 的重要性和方法。适合初学者逐步掌握 Java 编程。
145 1
|
9月前
|
NoSQL Java 关系型数据库
Liunx部署java项目Tomcat、Redis、Mysql教程
本文详细介绍了如何在 Linux 服务器上安装和配置 Tomcat、MySQL 和 Redis,并部署 Java 项目。通过这些步骤,您可以搭建一个高效稳定的 Java 应用运行环境。希望本文能为您在实际操作中提供有价值的参考。
557 26
|
8月前
|
前端开发 Java 开发工具
Git使用教程-将idea本地Java等文件配置到gitte上【保姆级教程】
本内容详细介绍了使用Git进行版本控制的全过程,涵盖从本地仓库创建到远程仓库配置,以及最终推送代码至远程仓库的步骤。
416 0
|
9月前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
9月前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)

热门文章

最新文章