Java 集合框架

集合简介

集合,有时称为容器,只是将多个元素组合成一个单元的对象。集合用于存储、检索、操作和通信聚合数据。通常,它们代表组成自然组的数据项,比如扑克牌(卡片的集合)、邮件文件夹(信件的集合)或电话目录(电话号码的映射)。如果您使用过Java编程语言,或者使用其他编程语言,那您已经熟悉了集合。

什么是集合框架?

集合框架是表示和操作集合的统一体系结构。所有集合框架都包含以下内容:

接口:这些是表示集合的抽象数据类型。接口允许将集合独立于其表示的细节进行操作。在面向对象的语言中,接口通常形成一个层次结构。
实现:这些是集合接口的具体实现。本质上,它们是可重用的数据结构。

算法:这些方法在实现集合接口的对象上执行有用的计算,比如搜索和排序。这些算法被认为是多态的:也就是说,相同的方法可以在许多不同的不同实现的适当的集合接口上使用。本质上,算法是可重用的功能。

除了Java集合框架之外,集合框架最著名的例子是c++标准模板库(STL)和Smalltalk的集合层次结构。从历史上看,集合框架非常复杂,这给它们提供了一个陡峭的学习曲线的名声。我们相信Java集合框架打破了这个传统,您将在本章中学习。

Java集合框架的好处。

Java集合框架提供了以下好处:

  • 减少编程工作:通过提供有用的数据结构和算法,集合框架使您能够将精力集中于程序的重要部分,而不是使其工作所需的低级“管道”。通过促进不相关的api之间的互操作性,Java集合框架将您从编写适配器对象或转换代码中解放出来,以连接api。

  • 提高程序速度和质量:这个集合框架提供高性能、高质量的有用数据结构和算法实现。每个接口的各种实现都是可互换的,因此可以通过切换集合实现轻松地调整程序。因为您已经摆脱了编写自己的数据结构的枯燥工作,您将有更多的时间来致力于提高程序的质量和性能。

  • 允许不相关的api之间的互操作性:集合接口是api将集合来回传递的术语。如果我的网络管理API提供了一个节点名称的集合,如果您的GUI工具包希望收集一个列标题,那么我们的API将会无缝地互操作,即使它们是独立编写的。

  • 减少学习和使用新api的时间:许多api自然地收集输入并提供它们作为输出。在过去,每个这样的API都有一个小的子API用于处理它的集合。这些临时集合的子api之间几乎没有一致性,因此您必须从头开始学习每一个,并且在使用它们时很容易出错。随着标准收集接口的出现,问题就消失了。

  • 减少设计新api的努力:这是之前优势的另一面。设计人员和实现者不必每次创建依赖集合的API时都要重新设计轮子;相反,它们可以使用标准的集合接口。

  • 促进软件重用:符合标准集合接口的新数据结构是可重用的。对于实现这些接口的对象的新算法也是如此。

接口

核心集合接口封装了不同类型的集合,如下图所示。这些接口允许将集合独立于其表示的细节进行操作。核心集合接口是Java集合框架的基础。正如您在下图中看到的,核心集合接口构成了一个层次结构。

java集合框架

两个接口树,一个从集合开始,包括Set、SortedSet、List和Queue,另一个从Map开始,包括SortedMap。

核心接口集合。

Set是一种特殊的集合,SortedSet是一种特殊的集合,等等。还要注意,层次结构由两种不同的树组成——Map不是真正的集合。

注意,所有的核心集合接口都是通用的。例如,这是集合接口的声明。

public interface Collection<E>...

语法告诉您接口是通用的。当您声明一个集合实例时,您可以并且应该指定集合中包含的对象的类型。指定类型允许编译器(在编译时)验证您放入集合中的对象类型是正确的,从而在运行时减少错误。有关泛型类型的信息,请参见泛型(更新)课程。

当您了解如何使用这些接口时,您将知道关于Java集合框架的大部分知识。本章讨论了有效使用接口的一般准则,包括何时使用哪个接口。您还将学习每个接口的编程习惯,以帮助您充分利用它。

为了使核心集合接口的数量易于管理,Java平台没有为每种集合类型的每个变体提供单独的接口。(此类变体可能包括不可变的、固定大小的和仅附加的。)相反,每个接口中的修改操作都是可选的——给定的实现可以选择不支持所有操作。如果一个不受支持的操作被调用时,抛出UnsupportedOperationException。方式集合实现负责记录它们支持的可选操作。所有Java平台的通用实现都支持所有可选操作。

下面的列表描述了核心的集合接口:

Collection——集合层次结构的根。Collection表示一组被称为元素的对象。Collection接口是所有集合实现的最小公分母,并用于传递集合,并在需要最大通用性时对它们进行操作。某些类型的集合允许重复元素,而另一些则不允许。有些是有序的,有些是无序的。Java平台没有提供该接口的任何直接实现,而是提供了更具体的子接口的实现,如Set和List。还可以查看集合接口部分。
Set——不能包含重复元素的集合。这个接口建立数学集合的抽象,用来表示集合,比如扑克牌,包括扑克牌,学生的课程表,或者机器上运行的进程。参见Set接口部分。
List-有序集合(有时称为序列)。列表可以包含重复的元素。列表的用户通常可以精确地控制列表中的每个元素的位置,并且可以通过它们的整数索引(位置)访问元素。如果你用过向量,你就会熟悉列表的一般味道。可查看列表接口部分。
Queue——用于在处理之前保存多个元素的集合。除了基本的收集操作之外,队列还提供了额外的插入、提取和检查操作。
队列通常,但不一定,顺序元素在FIFO(先入先出)的方式。例外情况是优先队列,根据提供的比较器或元素的自然排序顺序排列元素。无论使用的顺序是什么,队列的头都是通过调用删除或轮询来删除的元素。在FIFO队列中,所有新元素都插入到队列的尾部。其他类型的队列可以使用不同的放置规则。每个队列实现都必须指定其排序属性。可查看队列接口部分。

Deque -一个用于在处理之前保存多个元素的集合。除了基本的收集操作之外,Deque还提供了额外的插入、提取和检查操作。
Deques可以同时使用,如FIFO(先入先出)和LIFO(后进先出)。在deque中,可以在两端插入、检索和删除所有新元素。可查看Deque接口部分。

Map——映射键值的对象。Map不能包含重复键;每个键最多可以映射到一个值。如果您使用了Hashtable,那么您已经熟悉了Map的基础知识。可查看Map接口部分。

最后两个核心集合接口仅仅是集合和映射的排序版本:

SortedSet——一个以升序维护其元素的集合。还提供了一些额外的操作来利用排序。排序集用于自然有序集,如单词列表和成员卷。还可以看到SortedSet接口部分。
SortedMap——一个以升序键顺序维护其映射的映射。这是SortedSet的地图模拟。排序的映射用于自然有序的键/值对集合,例如字典和电话目录。还可以看到SortedMap接口部分。
要了解排序的接口如何维护其元素的顺序,请参见对象订购部分。

聚合操作

注意:为了更好地理解本节中的概念,请复习一下Lambda表达式和方法引用。

你使用集合是来做什么呢?您不能简单地将对象存储在集合中,然后将它们放在那里。在大多数情况下,您使用集合来检索存储在其中的项。

再次考虑在Lambda表达式中描述的场景。假设您正在创建一个社交网络应用程序。您希望创建一个特性,使管理员能够执行任何类型的操作,例如发送消息,在满足特定条件的社交网络应用程序的成员上。

如前所述,假设该社交网络应用程序的成员由以下人员类表示:

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    // ...

    public int getAge() {
        // ...
    }

    public String getName() {
        // ...
    }
}

下面的例子打印了集合中包含的所有成员的名称:

for (Person p : roster) {
    System.out.println(p.getName());
}

下面的示例打印收集中包含的所有成员,但对每个成员进行聚合操作:

roster
    .stream()
    .forEach(e -> System.out.println(e.getName());

尽管在本例中,使用聚合操作的版本比使用for-each循环的版本要长,但您将看到使用批量数据操作的版本对于更复杂的任务将更加简洁。

所涵盖的主题如下:

管道和流
聚合操作和迭代器之间的区别。
发现在这一节中描述的代码摘录BulkDataOperationsExamples的例子。

管道和流
管道是聚合操作的序列。下面的示例打印收集中包含的男性成员,该管道由聚合操作过滤器和forEach组成:

roster
    .stream()
    .filter(e -> e.getGender() == Person.Sex.MALE)
    .forEach(e -> System.out.println(e.getName()));

将此示例与下面的示例进行比较,该示例将包含在每个循环的集合中包含的男性成员:

for (Person p : roster) {
    if (p.getGender() == Person.Sex.MALE) {
        System.out.println(p.getName());
    }
}

A pipeline contains the following components:

A source: This could be a collection, an array, a generator function, or an I/O channel. In this example, the source is the collection roster.

Zero or more intermediate operations. An intermediate operation, such as filter, produces a new stream.

A stream is a sequence of elements. Unlike a collection, it is not a data structure that stores elements. Instead, a stream carries values from a source through a pipeline. This example creates a stream from the collection roster by invoking the method stream.

The filter operation returns a new stream that contains elements that match its predicate (this operation’s parameter). In this example, the predicate is the lambda expression e -> e.getGender() == Person.Sex.MALE. It returns the boolean value true if the gender field of object e has the value Person.Sex.MALE. Consequently, the filter operation in this example returns a stream that contains all male members in the collection roster.

A terminal operation. A terminal operation, such as forEach, produces a non-stream result, such as a primitive value (like a double value), a collection, or in the case of forEach, no value at all. In this example, the parameter of the forEach operation is the lambda expression e -> System.out.println(e.getName()), which invokes the method getName on the object e. (The Java runtime and compiler infer that the type of the object e is Person.)

The following example calculates the average age of all male members contained in the collection roster with a pipeline that consists of the aggregate operations filter, mapToInt, and average:

double average = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .mapToInt(Person::getAge)
    .average()
    .getAsDouble();

The mapToInt operation returns a new stream of type IntStream (which is a stream that contains only integer values). The operation applies the function specified in its parameter to each element in a particular stream. In this example, the function is Person::getAge, which is a method reference that returns the age of the member. (Alternatively, you could use the lambda expression e -> e.getAge().) Consequently, the mapToInt operation in this example returns a stream that contains the ages of all male members in the collection roster.

The average operation calculates the average value of the elements contained in a stream of type IntStream. It returns an object of type OptionalDouble. If the stream contains no elements, then the average operation returns an empty instance of OptionalDouble, and invoking the method getAsDouble throws a NoSuchElementException. The JDK contains many terminal operations such as average that return one value by combining the contents of a stream. These operations are called reduction operations; see the section Reduction for more information.

Differences Between Aggregate Operations and Iterators
Aggregate operations, like forEach, appear to be like iterators. However, they have several fundamental differences:

They use internal iteration: Aggregate operations do not contain a method like next to instruct them to process the next element of the collection. With internal delegation, your application determines what collection it iterates, but the JDK determines how to iterate the collection. With external iteration, your application determines both what collection it iterates and how it iterates it. However, external iteration can only iterate over the elements of a collection sequentially. Internal iteration does not have this limitation. It can more easily take advantage of parallel computing, which involves dividing a problem into subproblems, solving those problems simultaneously, and then combining the results of the solutions to the subproblems. See the section Parallelism for more information.

They process elements from a stream: Aggregate operations process elements from a stream, not directly from a collection. Consequently, they are also called stream operations.

They support behavior as parameters: You can specify lambda expressions as parameters for most aggregate operations. This enables you to customize the behavior of a particular aggregate operation.

实现

Implementations are the data objects used to store collections, which implement the interfaces described in the Interfaces section. This lesson describes the following kinds of implementations:

General-purpose implementations are the most commonly used implementations, designed for everyday use. They are summarized in the table titled General-purpose-implementations.
Special-purpose implementations are designed for use in special situations and display nonstandard performance characteristics, usage restrictions, or behavior.
Concurrent implementations are designed to support high concurrency, typically at the expense of single-threaded performance. These implementations are part of the java.util.concurrent package.
Wrapper implementations are used in combination with other types of implementations, often the general-purpose ones, to provide added or restricted functionality.
Convenience implementations are mini-implementations, typically made available via static factory methods, that provide convenient, efficient alternatives to general-purpose implementations for special collections (for example, singleton sets).
Abstract implementations are skeletal implementations that facilitate the construction of custom implementations — described later in the Custom Collection Implementations section. An advanced topic, it’s not particularly difficult, but relatively few people will need to do it.
The general-purpose implementations are summarized in the following table.

General-purpose Implementations

InterfacesHash table ImplementationsResizable array ImplementationsTree ImplementationsLinked list ImplementationsHash table + Linked list Implementations
SetHashSet--TreeSet-
List-ArrayList-LinkedList-
Queue-
Deque-ArrayDeque-LinkedList-
MapHashMap-TreeMap-LinkedHashMap

As you can see from the table, the Java Collections Framework provides several general-purpose implementations of the Set, List , and Map interfaces. In each case, one implementation — HashSet, ArrayList, and HashMap — is clearly the one to use for most applications, all other things being equal. Note that the SortedSet and the SortedMap interfaces do not have rows in the table. Each of those interfaces has one implementation (TreeSet and TreeMap) and is listed in the Set and the Map rows. There are two general-purpose Queue implementations — LinkedList, which is also a List implementation, and PriorityQueue, which is omitted from the table. These two implementations provide very different semantics: LinkedList provides FIFO semantics, while PriorityQueue orders its elements according to their values.

Each of the general-purpose implementations provides all optional operations contained in its interface. All permit null elements, keys, and values. None are synchronized (thread-safe). All have fail-fast iterators, which detect illegal concurrent modification during iteration and fail quickly and cleanly rather than risking arbitrary, nondeterministic behavior at an undetermined time in the future. All are Serializable and all support a public clone method.

The fact that these implementations are unsynchronized represents a break with the past: The legacy collections Vector and Hashtable are synchronized. The present approach was taken because collections are frequently used when the synchronization is of no benefit. Such uses include single-threaded use, read-only use, and use as part of a larger data object that does its own synchronization. In general, it is good API design practice not to make users pay for a feature they don’t use. Furthermore, unnecessary synchronization can result in deadlock under certain circumstances.

If you need thread-safe collections, the synchronization wrappers, described in the Wrapper Implementations section, allow any collection to be transformed into a synchronized collection. Thus, synchronization is optional for general-purpose implementations, whereas it is mandatory for legacy implementations. Moreover, the java.util.concurrent package provides concurrent implementations of the BlockingQueue interface, which extends Queue, and of the ConcurrentMap interface, which extends Map. These implementations offer much higher concurrency than mere synchronized implementations.

As a rule, you should be thinking about the interfaces, not the implementations. That is why there are no programming examples in this section. For the most part, the choice of implementation affects only performance. The preferred style, as mentioned in the Interfaces section, is to choose an implementation when a Collection is created and to immediately assign the new collection to a variable of the corresponding interface type (or to pass the collection to a method expecting an argument of the interface type). In this way, the program does not become dependent on any added methods in a given implementation, leaving the programmer free to change implementations anytime that it is warranted by performance concerns or behavioral details.

The sections that follow briefly discuss the implementations. The performance of the implementations is described using words such as constant-time, log, linear, n log(n), and quadratic to refer to the asymptotic upper-bound on the time complexity of performing the operation. All this is quite a mouthful, and it doesn’t matter much if you don’t know what it means. If you’re interested in knowing more, refer to any good algorithms textbook. One thing to keep in mind is that this sort of performance metric has its limitations. Sometimes, the nominally slower implementation may be faster. When in doubt, measure the performance!

算法

http://blog.csdn.net/chenlushun12/article/details/79551076

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值