Java语言十五讲(第十五讲容器框架三)

我们到此已经把ArrayList, HashSet,HashMap讲过了。JDK里面还有一些实现,如Queue,Deque,TreeSet,TreeMap等等,我就不一一讲了。现在回顾一下:
Collection保存单一的元素,而Map把保存键值对。用泛型技术,就可以将制定类型的对象放到容器中,不会出现类型错误的元素,取出来的时候也不需要再进行类型转换。
Collection和Map都不定长,向里面添加更多元素的时候,会自动调整容量。
跟数组一样,List维持位置索引和对象的关联,因此是由确定次序的容器。
如果进行大量的随机访问,使用ArrayList,如果经常增删改数据,应该用LinkedList。
各种Queue及栈,底层用得是LinkedList。
HashMap设计用来快速访问,而TreeMap保持键值处于排序状态,所以也没有HashMap快。LinkedHashMap保持元素插入的顺序。
Set的元素不可重复。HashSet访问速度快。TreeSet保持元素处于有序的状态。LinkedHashSet保持元素插入的顺序。
带tree的类能排序,通过comparable接口给定对象自然排序或者Comparator接口自定义对象的次序关系。
Vector, Stack, Hashtable是历史遗留下来的类,新程序不要使用。
有一个图:

图片

再介绍一下Collections工具类。This class consists exclusively of static methods that operate on or return collections. 提供了许多对List,Set和Map的操作方法。详细的大家可以看JDK文档,我这里只简要说一下基本的。
排序操作

reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
rotate(List list, int distance):将所有元素向右或向左移位指定长度,依据distance正负
查找和替换
static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
  使用二分搜索法搜索指定列表,以获得指定对象在List集合中的索引。注意:此前必须保证List集合中的元素已经处于有序状态。
static Object max(Collection coll)
  根据元素的自然顺序,返回给定collection 的最大元素。
static Object max(Collection coll,Comparator comp):
 根据指定比较器产生的顺序,返回给定 collection 的最大元素。 
static Object min(Collection coll):
 根据元素的自然顺序,返回给定collection 的最小元素。
static Object min(Collection coll,Comparator comp):
 根据指定比较器产生的顺序,返回给定 collection 的最小元素。
static <T> void fill(List<? super T> list, T obj) :
 使用指定元素替换指定列表中的所有元素。
static int frequency(Collection<?> c, Object o)
 返回指定 collection 中等于指定对象的出现次数。
static int indexOfSubList(List<?> source, List<?> target) :
 返回指定源列表中第一次出现指定目标列表的起始位置。 
static int lastIndexOfSubList(List<?> source, List<?> target)
 返回指定源列表中最后一次出现指定目标列表的起始位置。
static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)
 使用一个新值替换List对象的所有旧值oldVal
同步控制
static <T> Collection<T> synchronizedCollection(Collection<T> c)
 返回指定 collection 支持的同步(线程安全的)collection。
static <T> List<T> synchronizedList(List<T> list)
 返回指定列表支持的同步(线程安全的)列表。 
static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
 返回由指定映射支持的同步(线程安全的)映射。
static <T> Set<T> synchronizedSet(Set<T> s)
  返回指定 set 支持的同步(线程安全的)set。

Java集合框架还提供一种称之为Stream的操作,需要介绍一下。我们的程序,十有八九是与数据集合打交道的,其实我们已经很熟悉数据库了,并且很熟悉数据库操作语言SQL了,我们对数据批量的排序,分组,查找,修改,计算,都是通过SQL做的。那么,在Java程序里面,有没有这么一种类似的东西呢?有的,这就是Stream,并且它可以利用多核处理器而不用你自己管多线程编程。
比如,我们想把仓库里面的某类货品统一提价后按顺序列出,使用Stream操作可以一步到位,写成类似于下面的样子:

List<Product> Pds =
    inventory.stream()
             .filter(t -> t.getType() == "Type1")
             .map(Transaction::changePrice)
             .sorted()
             .collect(toList());

几步操作链接在一起,大家看着是不是很酷?
执行过程形如:

图片

像一根管道,操作一步步流下来,所以这个词Stream还是比较形象的。

构造Stream很简单,执行collection.parallelStream()或者collection.Stream()就可以了。有了stream,可以执行一些数据操作,主要分成几类:

Intermediate:
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
Terminal:
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
Short-circuiting:
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

terminal 操作执行后,Stream 的元素就被“消费”掉了,你无法对一个 Stream 进行两次 terminal 运算,而Intermediate操作可以一直流下去。比如你执行了forEach(System.out::println)之后,操作过程就终结了,不能再执行什么操作了,但是类似功能的peek(System.out::println),这是一个intermediate,因此可以继续执行别的操作的。Short-circuiting不一定会对整个Stream进行操作。

map/flatMap
我们先来看 map。如果你熟悉 scala 这类函数式语言,对这个方法应该很了解,它的作用就是把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素。

filter
filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。

forEach
forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。
另外一点需要注意,forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,你无法对一个 Stream 进行两次 terminal 运

peek 
对每个元素执行操作并返回一个新的 Stream

findFirst
这是一个 termimal 兼 short-circuiting 操作,它总是返回 Stream 的第一个元素,或者空。

reduce
这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。

limit/skip
limit 返回 Stream 的前面 n 个元素;
skip 则是扔掉前 n 个元素(它是由一个叫 subStream 的方法改名而来)。

sorted
对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。

Match
allMatch:Stream 中全部元素符合传入的 predicate,返回 true
anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true

下面有一个简单的例子,代码如下(StreamTest2.java):

public class StreamTest2 {
public static void main(String args[]) {
    List<String> strings = Arrays.asList("abcd", "", "yale", "test", "abcd", "", "java");
    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19);
    Random random = new Random();

    //Uppercase
    List<String> otherstrings = strings.stream()
               .map(String::toUpperCase)
               .collect(Collectors.toList());
    strings.stream()
           .map(String::toUpperCase)
           .forEach(System.out::println);

    //square
    List<Integer> squareNums = numbers.stream().
                map(n -> n * n).
                collect(Collectors.toList());
    squareNums.forEach(System.out::println);

    String concat = strings.stream().reduce("", String::concat);
    System.out.println(concat);
    Integer minValue = numbers.stream().reduce(Integer.MAX_VALUE, Integer::min); 
    System.out.println(minValue);
    int sumValue = numbers.stream().reduce(0, Integer::sum);
    System.out.println(sumValue);

    System.out.println("Strings: " + strings);
    long count = strings.stream().filter(s -> s.isEmpty()).count();
    System.out.println("empty string: " + count);
    count = strings.stream().filter(s -> s.length() == 3).count();
    System.out.println("length==3 : " + count);
    List<String> filtered = strings.stream().filter(s-> !s.isEmpty()).collect(Collectors.toList());
    System.out.println("Filtered: " + filtered);

    System.out.println("Numbers: " + integers);
    System.out.println(integers.stream().mapToInt((i)->i).sum());
    System.out.println(integers.stream().mapToInt((i)->i).max());
    System.out.println(integers.stream().mapToInt((i)->i).min());
    System.out.println(integers.stream().mapToInt((i)->i).average());   
    System.out.println(integers.stream().map(i -> i * i).distinct().collect(Collectors.toList()));

    System.out.println("Random: ");
    random.ints().limit(10).sorted().peek(System.out::println).forEach(System.out::println);
}
}

程序其实很简单,大家自己看一下结果。
JDK还提供了Collectors工具类可以帮我们完成的事情,例如:分组、排序(支持多字段排序)、最大值、最小值、平均值,简单的来说,以前我们在数据上面用sql 去完成的聚合相关的操作,Collectors 都可以完成。
但是这些大数据量的处理性能如何呢?我个人的测试结果,不如数据库SQL操作。这样带来一个尴尬,对批量数据进行操作的时候,究竟怎么样才好呢?我个人建议继续观察,不用着急往Stream上迁移。对于数据已经小规模读进内存了,需要进一步的处理,可以考虑使用。

还有一个需要了解的话题就是并发。再JDK5之后,Java提供了专门的并发处理包,在java.util.concurrent下。在通常情况下,很显著地提升了Java容器并发的吞吐量。由大师Doug Lea实现。
我们以ConcurrentHashMap来讲解,它效率比Hashtable高,并发性比hashmap好。
先看使用,其实蛮简单的,举一个例子,代码如下:

public class ConcurrentHashMapTest {
    static Map<Integer, String> map = new ConcurrentHashMap<Integer, String>();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            map.put(i, "test"+i);
        }

        Thread thread1 = new Thread(() -> {
                map.put(99, "test99");
                System.out.println("put new value");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        Thread thread2 = new Thread(() -> {
            Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<Integer, String> entry = iterator.next();
                System.out.println(entry.getKey() + " - " + entry.getValue());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread2.start();
        thread1.start();
    }
}

跟HashMap一样的。两个线程,对同一个map,一边写一边读,不会出问题。如果把上面的ConcurrentHashMap改成HashMap,运行时有时候就会报错:ConcurrentModificationException。

下面简单介绍一下原理。在有并发包出来之前,synchronized是针对整张Hash表的,即每次锁 住整张表让线程独占,于是就从这点下手,围绕着锁的颗粒度和如何锁进行改进。
在JDK7的实现中,首先在HashMap的bucket桶的概念之上增加一层Segment段,一个Hash表分成16个segment,把操作get,put,remove限定在segment内。通过这个隔离,一张Hash表就允许16个线程并发访问了。我们看到,读取的时候基本不需要锁表,修改的时候锁的颗粒度小只针对当前的segment,这样明显提高并发度。读JDK这一部分源码,就是一种享受,感叹大师四维的缜密。
同时,ConcurrentHashMap在iterate的时候不再使用fail-fast机制,而是用了弱一致迭代器。当iterator被创建后集合再发生改变就不再是抛出 ConcurrentModificationException,而是在改变时new新的数据从而不影响原有的数据,iterator完成后再将头指针替换为新的数据,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。
多个线程对内存同时读写,互相之间会有影响。这个是Java内存模型JMM要指明的。Java语言允许一些内存操作并不对于所有其他线程立即可见(好处是充分利用缓存和寄存器提高性能)。Java中有两种机制可用于保证跨线程内存操作的一致性:synchronized和volatile。需要遵循同步法则:写后读和读后写都要同步,保证线程都会得到它所使用的共享变量的正确的值。
为此,ConcurrentHashMap使用了volatile变量,按照语义,volatile变量在任意线程中都是正确共享的。当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量。
JDK8的时候,又做了进一步的工作。选择了与HashMap类似的数组+链表+红黑树的方式实现,而加锁则采用CAS和synchronized实现。一般而言,锁分为悲观锁和乐观锁:悲观锁认为对于同一个数据的并发操作,一定是为发生修改的;而乐观锁则任务对于同一个数据的并发操作是不会发生修改的。Java以前的锁都是悲观锁,而CAS实现的是乐观锁。CAS具体与volatile同样的语义。我已经在多线程讲座中讲到了CAS。
有了这些理论知识,我们使用这些并发类的时候,心里更有底气,知其然也知其所以然。

整个Java容器框架就讲到这里,作为基础讲座差不多了,但是作为进阶讲座还觉得不够。本来我是想讲一些自己如何实现数据结构的,后来还是觉得放到单独的数据结构及算法系列讲座中更好,只好割爱了。
Java的容器框架很好,但是数量不够。要用到别的结构,如有向图、数据表,这需要自己动手写,实现这些结构和算法。当然有好多第三方也做了同样的事情,比较普遍使用的是Google的Guava,这个瓜娃子提供了Multimaps、Table等结构。

看了JDK的人都会有一个疑惑,Java容器框架里面的API申明有一些不一样的地方,那就是有很多方法是可选的,在实现时抛出UnsupportedOperationException即可表示容器不支持该方法。这是特意这么安排,保持Java容器框架小而美。实现者可以只实现最基本的操作方法。
翻看JDK,可以看到除了TreeSet之外,所有的Set都跟Collection拥有一样的接口;而List和Collection存在明显不同;Queue接口中的方法都是独立的,创建具有Queue功能的实现时,不需要用Collection方法;Map和Collection之间唯一的重叠时Map可以通过entrySet()和values()产生Collection。从继承关系上,这种组织结构有些奇怪。
容器框架是一个语言最重要的类库,但是却一直是设计的难题,需要照顾到彼此之间相互矛盾的需求,所以任何一个容器框架都会有一堆问题和奇怪的地方。
Oracle官方文档中特别有说明,回答了大家的常见的疑惑问题,可以了解一下设计者的考量。

Core Interfaces - General Questions
Why don't you support immutability directly in the core collection interfaces so that you can do away with optional operations (and UnsupportedOperationException)?
This is the most controversial design decision in the whole API. Clearly, static (compile time) type checking is highly desirable, and is the norm in Java. We would have supported it if we believed it were feasible. Unfortunately, attempts to achieve this goal cause an explosion in the size of the interface hierarchy, and do not succeed in eliminating the need for runtime exceptions (though they reduce it substantially).
Doug Lea, who wrote a popular Java collections package that did reflect mutability distinctions in its interface hierarchy, no longer believes it is a viable approach, based on user experience with his collections package. In his words (from personal correspondence) "Much as it pains me to say it, strong static typing does not work for collection interfaces in Java."
To illustrate the problem in gory detail, suppose you want to add the notion of modifiability to the Hierarchy. You need four new interfaces: ModifiableCollection, ModifiableSet, ModifiableList, and ModifiableMap. What was previously a simple hierarchy is now a messy heterarchy. Also, you need a new Iterator interface for use with unmodifiable Collections, that does not contain the remove operation. Now can you do away with UnsupportedOperationException? Unfortunately not.
Consider arrays. They implement most of the List operations, but not remove and add. They are "fixed-size" Lists. If you want to capture this notion in the hierarchy, you have to add two new interfaces: VariableSizeList and VariableSizeMap. You don't have to add VariableSizeCollection and VariableSizeSet, because they'd be identical to ModifiableCollection and ModifiableSet, but you might choose to add them anyway for consistency's sake. Also, you need a new variety of ListIterator that doesn't support the add and remove operations, to go along with unmodifiable List. Now we're up to ten or twelve interfaces, plus two new Iterator interfaces, instead of our original four. Are we done? No.
Consider logs (such as error logs, audit logs and journals for recoverable data objects). They are natural append-only sequences, that support all of the List operations except for remove and set (replace). They require a new core interface, and a new iterator.
And what about immutable Collections, as opposed to unmodifiable ones? (i.e., Collections that cannot be changed by the client AND will never change for any other reason). Many argue that this is the most important distinction of all, because it allows multiple threads to access a collection concurrently without the need for synchronization. Adding this support to the type hierarchy requires four more interfaces.
Now we're up to twenty or so interfaces and five iterators, and it's almost certain that there are still collections arising in practice that don't fit cleanly into any of the interfaces. For example, the collection-views returned by Map are natural delete-only collections. Also, there are collections that will reject certain elements on the basis of their value, so we still haven't done away with runtime exceptions.
When all was said and done, we felt that it was a sound engineering compromise to sidestep the whole issue by providing a very small set of core interfaces that can throw a runtime exception.
Won't programmers have to surround any code that calls optional operations with a try-catch clause in case they throw an UnsupportedOperationException?
It was never our intention that programs should catch these exceptions: that's why they're unchecked (runtime) exceptions. They should only arise as a result of programming errors, in which case, your program will halt due to the uncaught exception.
Why isn't there a core interface for "bags" (AKA multisets)?
The Collection interface provides this functionality. We are not providing any public implementations of this interface, as we think that it wouldn't be used frequently enough to "pull its weight." We occasionally return such Collections, which are implemented easily atop AbstractCollection (for example, the Collection returned by Map.values).
Why didn't you use "Beans-style names" for consistency?
While the names of the new collections methods do not adhere to the "Beans naming conventions", we believe that they are reasonable, consistent and appropriate to their purpose. It should be remembered that the Beans naming conventions do not apply to the JDK as a whole; the AWT did adopt these conventions, but that decision was somewhat controversial. We suspect that the collections APIs will be used quite pervasively, often with multiple method calls on a single line of code, so it is important that the names be short. Consider, for example, the Iterator methods. Currently, a loop over a collection looks like this:
for (Iterator i = c.iterator(); i.hasNext(); )
        System.out.println(i.next());
Everything fits neatly on one line, even if the Collection name is a long expression. If we named the methods "getIterator", "hasNextElement" and "getNextElement", this would no longer be the case. Thus, we adopted the "traditional" JDK style rather than the Beans style.
Collection Interface
Why doesn't Collection extend Cloneable and Serializable?
Many Collection implementations (including all of the ones provided by the JDK) will have a public clone method, but it would be mistake to require it of all Collections. For example, what does it mean to clone a Collection that's backed by a terabyte SQL database? Should the method call cause the company to requisition a new disk farm? Similar arguments hold for serializable.
If the client doesn't know the actual type of a Collection, it's much more flexible and less error prone to have the client decide what type of Collection is desired, create an empty Collection of this type, and use the addAll method to copy the elements of the original collection into the new one.
Why don't you provide an "apply" method in Collection to apply a given method ("upcall") to all the elements of the Collection?
This is what is referred to as an "Internal Iterator" in the "Design Patterns" book (Gamma et al.). We considered providing it, but decided not to as it seems somewhat redundant to support internal and external iterators, and Java already has a precedent for external iterators (with Enumerations). The "throw weight" of this functionality is increased by the fact that it requires a public interface to describe upcalls.
Why didn't you provide a "Predicate" interface, and related methods (e.g., a method to find the first element in the Collection satisfying the predicate)?
It's easy to implement this functionality atop Iterators, and the resulting code may actually look cleaner as the user can inline the predicate. Thus, it's not clear whether this facility pulls its weight. It could be added to the Collections class at a later date (implemented atop Iterator), if it's deemed useful.
Why don't you provide a form of the addAll method that takes an Enumeration (or an Iterator)?
Because we don't believe in using Enumerations (or Iterators) as "poor man's collections." This was occasionally done in prior releases, but now that we have the Collection interface, it is the preferred way to pass around abstract collections of objects.
Why don't the concrete implementations in the JDK have Enumeration (or Iterator) constructors?
Again, this is an instance of an Enumeration serving as a "poor man's collection" and we're trying to discourage that. Note however, that we strongly suggest that all concrete implementations should have constructors that take a Collection (and create a new Collection with the same elements).
Why don't you provide an Iterator.add method?
The semantics are unclear, given that the contract for Iterator makes no guarantees about the order of iteration. Note, however, that ListIterator does provide an add operation, as it does guarantee the order of the iteration.
List Interface
Why don't you rename the List interface to Sequence; doesn't "list" generally suggest "linked list"? Also, doesn't it conflict with java.awt.List?
People were evenly divided as to whether List suggests linked lists. Given the implementation naming convention, <Implementation><Interface>, there was a strong desire to keep the core interface names short. Also, several existing names (AbstractSequentialList, LinkedList) would have been decidedly worse if we changed List to Sequence. The naming conflict can be dealt with by the following incantation:
    import java.util.*;
    import java.awt.*;
    import java.util.List;   // Dictates interpretation of "List"
Why don't you rename List's set method to replace, to avoid confusion with Set.
It was decided that the "set/get" naming convention was strongly enough enshrined in the language that we'd stick with it.
Map Interface
Why doesn't Map extend Collection?
This was by design. We feel that mappings are not collections and collections are not mappings. Thus, it makes little sense for Map to extend the Collection interface (or vice versa).
If a Map is a Collection, what are the elements? The only reasonable answer is "Key-value pairs", but this provides a very limited (and not particularly useful) Map abstraction. You can't ask what value a given key maps to, nor can you delete the entry for a given key without knowing what value it maps to.
Collection could be made to extend Map, but this raises the question: what are the keys? There's no really satisfactory answer, and forcing one leads to an unnatural interface.
Maps can be viewed as Collections (of keys, values, or pairs), and this fact is reflected in the three "Collection view operations" on Maps (keySet, entrySet, and values). While it is, in principle, possible to view a List as a Map mapping indices to elements, this has the nasty property that deleting an element from the List changes the Key associated with every element before the deleted element. That's why we don't have a map view operation on Lists.
Iterator Interface
Why doesn't Iterator extend Enumeration?
We view the method names for Enumeration as unfortunate. They're very long, and very frequently used. Given that we were adding a method and creating a whole new framework, we felt that it would be foolish not to take advantage of the opportunity to improve the names. Of course we could support the new and old names in Iterator, but it doesn't seem worthwhile.
Why don't you provide an Iterator.peek method that allows you to look at the next element in an iteration without advancing the iterator?
It can be implemented atop the current Iterators (a similar pattern to java.io.PushbackInputStream). We believe that its use would be rare enough that it isn't worth including in the interface that everyone has to implement.
Miscellaneous
Why did you write a new collections framework instead of adopting JGL (a preexisting collections package from ObjectSpace, Inc.) into the JDK?
If you examine the goals for our Collections framework (in the Overview), you'll see that we are not really "playing in the same space" as JGL. Quoting from the "Design Goals" Section of the Java Collections Overview: "Our main design goal was to produce an API that was reasonably small, both in size, and (more importantly) in 'conceptual weight.'"
JGL consists of approximately 130 classes and interfaces; its main goal was consistency with the C++ Standard Template Library (STL). This was not one of our goals. Java has traditionally stayed away from C++'s more complex features (e.g., multiple inheritance, operator overloading). Our entire framework, including all infrastructure, contains approximately 25 classes and interfaces.
While this may cause some discomfort for some C++ programmers, we feel that it will be good for Java in the long run. As the Java libraries mature, they inevitably grow, but we are trying as hard as we can to keep them small and manageable, so that Java continues to be an easy, fun language to learn and to use.
Why don't you eliminate all of the methods and classes that return "views" (Collections backed by other collection-like objects). This would greatly reduce aliasing.
Given that we provide core collection interfaces behind which programmers can "hide" their own implementations, there will be aliased collections whether the JDK provides them or not. Eliminating all views from the JDK would greatly increase the cost of common operations like making a Collection out of an array, and would do away with many useful facilities (like synchronizing wrappers). One view that we see as being particularly useful is List.subList. The existence of this method means that people who write methods taking List on input do not have to write secondary forms taking an offset and a length (as they do for arrays).
Why don't you provide for "observable" collections that send out Events when they're modified?
Primarily, resource constraints. If we're going to commit to such an API, it has to be something that works for everyone, that we can live with for the long haul. We may provide such a facility some day. In the meantime, it's not difficult to implement such a facility on top of the public APIs.

我不想翻译了,大家自己看原文,这样更加理解设计者的意图。
早期Java类库中确实有一些设计考虑不周,后来的升级版本,获得了广泛的赞誉。Java容器框架的项目带头人就是著名的软件大师Joshua Bloch,他说过"The main design goal of Collections Framework was to produce an API that was reasonably small, both in size, and, more importantly, in conceptual weight." 这里的重点是conceptual weight,即概念上轻量。概念上的轻量意味着使用者们可以“不必掌握大量信息,就能使用该API完成复杂的功能”。为此,Java集合框架项目组做出了大量的取舍。比如fail-fast机制还有接口中出现选择性实现的方法(optional methods),初看起来很奇怪,但是确实是深度思考之后的取舍。
现在的Java容器框架广受欢迎,屡获设计大奖,这也证明了它的成功。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值