Java集合框架官方教程(3):SortedSet/SortedMap接口
2016-07-29 00:00
676 查看
Object Ordering
AList
lmay be sorted as follows.
Collections.sort(l);
If the
Listconsists of
Stringelements, it will be sorted into alphabetical order. If it consists of
Dateelements, it will be sorted into chronological order. How does this happen?
Stringand
Dateboth implement the
Comparableinterface.
Comparableimplementations provide a natural ordering for a class, which allows objects of that class to be sorted automatically.The following table summarizes some of the more important Java platform classes that implement
Comparable.
Classes ImplementingComparable
Class | Natural Ordering |
---|---|
Byte | Signed numerical |
Character | Unsigned numerical |
Long | Signed numerical |
Integer | Signed numerical |
Short | Signed numerical |
Double | Signed numerical |
Float | Signed numerical |
BigInteger | Signed numerical |
BigDecimal | Signed numerical |
Boolean | Boolean.FALSE < Boolean.TRUE |
File | System-dependent lexicographic on path name |
String | Lexicographic |
Date | Chronological |
CollationKey | Locale-specific lexicographic |
Comparable,
Collections.sort(list)will throw a
ClassCastException. Similarly,
Collections.sort(list,comparator)will throw a
ClassCastExceptionif you try to sort a list whose elements cannot becompared to one another using the
comparator. Elements that can becompared to one another are called mutually comparable. Although elements of different types may be mutually comparable, none of the classes listed here permit interclass comparison.
This is all you really need to know about the
Comparableinterface if you just want to sort lists of comparable elements or to create sorted collections of them. The next section will be of interest to you if you want to implement your own
Comparabletype.
Writing Your OwnComparable Types
TheComparableinterface consists of the following method.
public interfaceComparable<T> {
public intcompareTo(T o);
}
The
compareTomethodcompares the receiving object with the specified object andreturns a negative integer, 0, or a positive integer depending on whether the receiving object is less than, equal to, or greater than the specified object. If the specified object cannot becompared to the receiving object, the method throws a
ClassCastException.
The
following class representing a person's nameimplements
Comparable.
import java.util.*;
public className implementsComparable<Name> {
private finalString firstName, lastName;
publicName(String firstName,String lastName) {
if (firstName == null || lastName == null)
throw new NullPointerException();
this.firstName = firstName;
this.lastName = lastName;
}
publicString firstName() {return firstName; }
publicString lastName() {return lastName; }
public boolean equals(Object o) {
if (!(o instanceofName))
return false;
Name n = (Name) o;
return n.firstName.equals(firstName) && n.lastName.equals(lastName);
}
public int hashCode() {
return 31*firstName.hashCode() + lastName.hashCode();
}
publicStringtoString() {
return firstName + " " + lastName;
}
public intcompareTo(Name n) {
int lastCmp = lastName.compareTo(n.lastName);
return (lastCmp != 0 ? lastCmp : firstName.compareTo(n.firstName));
}
}
To keep the preceding example short, the class is somewhat limited: It doesn't support middle names, it demands both a first and a last name, and it is not internationalized in any way. Nonetheless, it illustrates the following important points:
Nameobjects are immutable. All other things being equal,immutable types are the way to go, especially for objects that will be used as elements in
Sets or as keys in
Maps. These collections will break if you modify their elements or keys while they're in the collection.
The constructor checks its arguments for
null. This ensures that all
Nameobjects are well formed so that none of the other methods will ever throw a
NullPointerException.
The
hashCodemethod is redefined. This is essential for any class that redefines the
equalsmethod. (Equal objects must have equal hash codes.)
The
equalsmethodreturns
falseif the specified object is
nullor of an inappropriate type. The
compareTomethod throws a runtime exception under these circumstances. Both of these behaviors are required by the general contracts of the respective methods.
The
toStringmethod has been redefined so it prints the
Namein human-readable form. This is always a good idea, especially for objects that are going to get put into collections. The various collection types'
toStringmethods depend on the
toStringmethods of their elements, keys, and values.
Since this section is about element ordering, let's talk a bit more about
Name's
compareTomethod. It implements the standard name-ordering algorithm, where last names take precedence over first names. This is exactly what you want in a natural ordering. It would be very confusing indeed if the natural ordering were unnatural!
Take a look at how
compareTois implemented, because it's quite typical.First, youcompare the most significant part of the object(in this case, the last name). Often, you can just use the natural ordering of the part's type. In this case, the part is a
Stringand the natural (lexicographic) ordering is exactly what's called for. If the comparison results in anything other than zero, which represents equality, you're done: You justreturn the result. If the most significant parts are equal, you go on tocompare the next most-significant parts. In this case, there are only two parts — first name and last name. If there were more parts, you'd proceed in the obvious fashion, comparing parts until you found two that weren't equal or you were comparing the least-significant parts, at which point you'dreturn the result of the comparison.
Just to show that it all works, here's
a program that builds a list of names and sorts them.
import java.util.*;If you run this program, here's what it prints.
public classNameSort {
public static void main(String[] args) {
Name nameArray[] = {
newName("John", "Smith"),
newName("Karl", "Ng"),
newName("Jeff", "Smith"),
newName("Tom", "Rich")
};
List<Name> names = Arrays.asList(nameArray);
Collections.sort(names);
System.out.println(names);
}
}
[Karl Ng, Tom Rich, Jeff Smith, John Smith]
There are four restrictions on the behavior of the
compareTomethod, which we won't go into now because they're fairly technical and boring and are better left in the API documentation. It's really important that all classes that implement
Comparableobey these restrictions, so read the documentation for
Comparableif you're writing a class that implements it. Attempting to sort a list of objects that violate the restrictions has undefined behavior. Technically speaking, these restrictions ensure that the natural ordering is atotal order on the objects of a class that implements it; this is necessary to ensure that sorting is well defined.
Comparators
What if you want to sort some objects in an order other than their natural ordering? Or what if you want to sort some objects that don't implementComparable? To do either of these things, you'll need to provide a
Comparator— an object that encapsulates an ordering. Like the
Comparableinterface, the
Comparatorinterface consists of a single method.
public interfaceComparator<T> { intcompare(T o1, T o2); }
The
comparemethodcompares its two arguments,returning a negative integer, 0, or a positive integer depending on whether the first argument is less than, equal to, or greater than the second. If either of the arguments has an inappropriate type for the
Comparator, the
comparemethod throws a
ClassCastException.
Much of what was said about
Comparableapplies to
Comparatoras well.Writing a
comparemethod is nearly identical to writing a
compareTomethod, except that the former gets both objects passed in as arguments. The
comparemethod has to obey the same four technical restrictions as
Comparable's
compareTomethod for the same reason — a
Comparatormust induce a total order on the objects itcompares.
Suppose you have a class called
Employee, as follows.
public class Employee implementsComparable<Employee> {Let's assume that the natural ordering of
publicName name() { ... }
public int number() { ... }
publicDate hireDate() { ... }
...
}
Employeeinstances is
Nameordering (as defined in the previous example) on employee name. Unfortunately, the boss has asked for a list of employees in order of seniority. This means we have to do some work, but not much. The following program will produce the required list.
import java.util.*;The
public class EmpSort {
static finalComparator<Employee> SENIORITY_ORDER =
newComparator<Employee>() {
public intcompare(Employee e1, Employee e2) {
return e2.hireDate().compareTo(e1.hireDate());
}
};
// Employee database
static final Collection<Employee> employees = ... ;
public static void main(String[] args) {
List<Employee> e = new ArrayList<Employee>(employees);
Collections.sort(e, SENIORITY_ORDER);
System.out.println(e);
}
}
Comparatorin the program is reasonably straightforward. It relies on the natural ordering of
Dateapplied to the valuesreturned by the
hireDateaccessor method. Note that the
Comparatorpasses the hire date of its second argument to its first rather than vice versa.
The reason is that the employee who was hired most recently is the least senior; sorting in the order of hire date would put the list in reverse seniority order. Another technique people sometimes use to achieve this effect is to maintain the argument order but to negate the result of the comparison.
// Don't do this!! return -r1.hireDate().compareTo(r2.hireDate());You should always use the former technique in favor of the latter because the latter is not guaranteed to work. The reason for this is that the
compareTomethod canreturn any negative
intif its argument is less than the object on which it is invoked. There is one negative
intthat remains negative when negated, strange as it may seem.
-Integer.MIN_VALUE == Integer.MIN_VALUE
The
Comparatorin the preceding program works fine for sorting a
List, but it does have one deficiency: It cannot be used to order a sorted collection, such as
TreeSet, because it generates an ordering that isnot compatible with equals. This means that this
Comparatorequates objects that the
equalsmethod does not. In particular, any two employees who were hired on the same date willcompare as equal. When you're sorting a
List, this doesn't matter; but when you're using the
Comparatorto order a sorted collection, it's fatal. If you use this
Comparatorto insert multiple employees hired on the same date into a
TreeSet, only the first one will be added to the set; the second will be seen as a duplicate element and will be ignored.
To fix this problem, simply tweak the
Comparatorso that it produces an ordering thatis compatible with
equals. In other words, tweak it so that the only elements seen as equal when using
compareare those that are also seen as equal whencompared using
equals. The way to do this is to perform a two-part comparison (as for
Name), where the first part is the one we're interested in — in this case, the hire date — and the second part is an attribute that uniquely identifies the object. Here the employee number is the obvious attribute. This is the
Comparatorthat results.
static finalComparator<Employee> SENIORITY_ORDER =One last note: You might be tempted to replace the final
newComparator<Employee>() {
public intcompare(Employee e1, Employee e2) {
int dateCmp = e2.hireDate().compareTo(e1.hireDate());
if (dateCmp != 0)
return dateCmp;
return (e1.number() < e2.number() ? -1 :
(e1.number() == e2.number() ? 0 : 1));
}
};
returnstatement in the
Comparatorwith the simpler:
return e1.number() - e2.number();Don't do it unless you're
absolutely sure no one will ever have a negative employee number!
This trick does not work in general because the signed integer type is not big enough to represent the difference of two arbitrary signed integers. If
iis a large positive integer and
jis a large negative integer,
i - jwill overflow and willreturn a negative integer. The resulting
comparatorviolates one of the four technical restrictions we keep talking about (transitivity) and produces horrible, subtle bugs. This is not a purely theoretical concern; people get burned by it.
The SortedSet Interface
ASortedSetis a
Setthat maintains its elements in ascending order, sorted according to the elements' natural ordering or according to a
Comparatorprovided at
SortedSetcreation time. In addition to the normal
Setoperations, the
SortedSetinterface provides operations for the following:
Range view— allows arbitrary range operations on the sorted set
Endpoints—returns the first or last element in the sorted set
Comparator access—returns the
Comparator, if any, used to sort the set
The code for the
SortedSetinterface follows.
public interface SortedSet<E> extends Set<E> {
// Range-view
SortedSet<E>subSet(E fromElement, E toElement);
SortedSet<E> headSet(E toElement);
SortedSet<E> tailSet(E fromElement);
// Endpoints
E first();
E last();
//Comparator access
Comparator<? super E>comparator();
}
Set Operations
The operations thatSortedSetinherits from
Setbehave identically on sorted sets and normal sets with two exceptions:
The
Iteratorreturned by the
iteratoroperation traverses the sorted set in order.
The arrayreturned by
toArraycontains the sorted set's elements in order.
Although the interface doesn't guarantee it, the
toStringmethod of the Java platform's
SortedSetimplementationsreturns a string containing all the elements of the sorted set, in order.
Standard Constructors
By convention, all general-purposeCollectionimplementations provide a standard conversion constructor that takes a
Collection;
SortedSetimplementations are no exception. In
TreeSet, this constructor creates an instance that sorts its elements according to their natural ordering. This was probably a mistake. It would have been better to check dynamically to see whether the specified collection was a
SortedSetinstance and, if so, to sort the new
TreeSetaccording to the same criterion (comparator or natural ordering).Because
TreeSettook the approach that it did, it also provides a constructor that takes a
SortedSetandreturns a new
TreeSetcontaining the same elements sorted according to the same criterion. Note that it is the compile-time type of the argument, not its runtime type, that determines which of these two constructors is invoked (and whether the sorting criterion is preserved).
SortedSetimplementations also provide, by convention, a constructor that takes a
Comparatorandreturns an empty set sorted according to the specified
Comparator. If
nullis passed to this constructor, itreturns a set that sorts its elements according to their natural ordering.
Range-view Operations
Therange-viewoperations are somewhat analogous to those provided by the
Listinterface, but there is one big difference. Range views of a sorted set remain valid even if the backing sorted set is modified directly. This is feasible because the endpoints of a range view of a sorted set are absolute points in the element space rather than specific elements in the backing collection, as is the case for lists. A
range-viewof a sorted set is really just a window onto whatever portion of the set lies in the designated part of the element space. Changes to the
range-viewwrite back to the backing sorted set and vice versa. Thus, it's okay to use
range-views on sorted sets for long periods of time, unlike
range-views on lists.
Sorted sets provide three
range-viewoperations. The first,
subSet, takes two endpoints, like
subList. Rather than indices, the endpoints are objects and must be comparable to the elements in the sorted set, using the
Set's
Comparatoror the natural ordering of its elements, whichever the
Setuses to order itself. Like
subList, the range is half open, including its low endpoint but excluding the high one.
Thus, the following line of code tells you how many words between
"doorbell"and
"pickle", including
"doorbell"but excluding
"pickle", are contained in a
SortedSetof strings called
dictionary:
int count = dictionary.subSet("doorbell", "pickle").size();In like manner, the following one-liner removes all the elements beginning with the letter
f.
dictionary.subSet("f", "g").clear();A similar trick can be used to print a table telling you how many words begin with each letter.
for (char ch = 'a'; ch <= 'z'; ) {
String from =String.valueOf(ch++);
String to =String.valueOf(ch);
System.out.println(from + ": " + dictionary.subSet(from, to).size());
}
Suppose you want to view a closed interval, which contains both of its endpoints, instead of an open interval. If the element type allows for the calculation of the successor of a given value in the element space, merely request the
subSetfrom
lowEndpointto
successor(highEndpoint). Although it isn't entirely obvious, the successor of a string
sin
String's natural ordering is
s + "\0"— that is,
swith a
nullcharacter appended.
Thus, the following one-liner tells you how many words between
"doorbell"and
"pickle", including doorbell and pickle, are contained in the dictionary.
count = dictionary.subSet("doorbell", "pickle\0").size();A similar technique can be used to view an
open interval, which contains neither endpoint. The open-interval view from
lowEndpointto
highEndpointis the half-open interval from
successor(lowEndpoint)to
highEndpoint. Use the following to calculate the number of words between
"doorbell"and
"pickle", excluding both.
count = dictionary.subSet("doorbell\0", "pickle").size();
The
SortedSetinterface contains two more
range-viewoperations —
headSetand
tailSet, both of which take a single
Objectargument. The formerreturns a view of the initial portion of the backing
SortedSet, up to but not including the specified object. The latterreturns a view of the final portion of the backing
SortedSet, beginning with the specified object and continuing to the end of the backing
SortedSet. Thus, the following code allows you to view the dictionary as two disjoint
volumes(
a-mand
n-z).
SortedSet<String> volume1 = dictionary.headSet("n"); SortedSet<String> volume2 = dictionary.tailSet("n");
Endpoint Operations
TheSortedSetinterface contains operations toreturn the first and last elements in the sorted set, not surprisingly called
firstand
last. In addition to their obvious uses,
lastallows a workaround for a deficiency in the
SortedSetinterface. One thing you'd like to do with a
SortedSetis to go into the interior of the
Setand iterate forward or backward. It's easy enough to go forward from the interior: Just get a
tailSetand iterate over it. Unfortunately, there's no easy way to go backward.
The following idiom obtains the first element that is less than a specified object
oin the element space.
Object predecessor = ss.headSet(o).last();
This is a fine way to go one element backward from a point in the interior of a sorted set. It could be applied repeatedly to iterate backward, but this is very inefficient, requiring a lookup for each elementreturned.
Comparator Accessor
TheSortedSetinterface contains an accessor method called
comparatorthatreturns the
Comparatorused to sort the set, or
nullif the set is sorted according to the natural ordering of its elements. This method is provided so that sorted sets can be copied into new sorted sets with the same ordering. It is used by the
SortedSetconstructor described previously.
The SortedMap Interface
ASortedMapis a
Mapthat maintains its entries in ascending order, sorted according to the keys' natural ordering, or according to a
Comparatorprovided at the time of the
SortedMapcreation. Natural ordering and
Comparators are discussed in the Object Ordering section. The
SortedMapinterface provides operations for normal
Mapoperations and for the following:
Range view— performs arbitrary range operations on the sorted map
Endpoints—returns the first or the last key in the sorted map
Comparator access—returns the
Comparator, if any, used to sort the map
The following interface is the
Mapanalog of
SortedSet.
public interface SortedMap<K, V> extends Map<K, V>{
Comparator<? super K>comparator();
SortedMap<K, V> subMap(K fromKey, K toKey);
SortedMap<K, V> headMap(K toKey);
SortedMap<K, V> tailMap(K fromKey);
K firstKey();
K lastKey();
}
Map Operations
The operationsSortedMapinherits from
Mapbehave identically on sorted maps and normal maps with two exceptions:
The
Iteratorreturned by the
iteratoroperation on any of the sorted map's
Collectionviews traverse the collections in order.
The arraysreturned by the
Collectionviews'
toArrayoperations contain the keys, values, or entries in order.
Although it isn't guaranteed by the interface, the
toStringmethod of the
Collectionviews in all the Java platform's
SortedMapimplementationsreturns a string containing all the elements of the view, in order.
Standard Constructors
By convention, all general-purposeMapimplementations provide a standard conversion constructor that takes a
Map;
SortedMapimplementations are no exception. In
TreeMap, this constructor creates an instance that orders its entries according to their keys' natural ordering. This was probably a mistake. It would have been better to check dynamically to see whether the specified
Mapinstance was a
SortedMapand, if so, to sort the new map according to the same criterion (comparator or natural ordering).Because
TreeMaptook the approach it did, it also provides a constructor that takes a
SortedMapandreturns a new
TreeMapcontaining the same mappings as the given
SortedMap, sorted according to the same criterion. Note that it is the compile-time type of the argument, not its runtime type, that determines whether the
SortedMapconstructor is invoked in preference to the ordinary
mapconstructor.
SortedMapimplementations also provide, by convention, a constructor that takes a
Comparatorandreturns an empty map sorted according to the specified
Comparator. If
nullis passed to this constructor, itreturns a
Mapthat sorts its mappings according to their keys' natural ordering.
Comparison to SortedSet
Because this interface is a preciseMapanalog of
SortedSet, all the idioms and code examples inThe SortedSet Interface section apply to
SortedMapwith only trivial modifications.
Summary of Interfaces
The core collection interfaces are the foundation of the Java Collections Framework.The Java Collections Framework hierarchy consists of two distinct interface trees:
The first tree starts with the
Collectioninterface, which provides for the basic functionality used by all collections, such as
addand
removemethods. Its subinterfaces —
Set,
List, and
Queue— provide for more specialized collections.
The
Setinterface does not allow duplicate elements. This can be useful for storing collections such as a deck of cards or student records. The
Setinterface has a subinterface,
SortedSet, that provides for ordering of elements in the set.
The
Listinterface provides for an ordered collection, for situations in which you need precise control over where each element is inserted. You can retrieve elements from a
Listby their exact position.
The
Queueinterface enables additional insertion, extraction, and inspection operations. Elements in a
Queueare typically ordered in on a FIFO basis.
The
Dequeinterface enables insertion, deletion, and inspection operations at both the ends. Elements in a
Dequecan be used in both LIFO and FIFO.
The second tree starts with the
Mapinterface, which maps keys and values similar to a
Hashtable.
Map's subinterface,
SortedMap, maintains its key-value pairs in ascending order or in an order specified by a
Comparator.
These interfaces allow collections to be manipulated independently of the details of their representation.
Answers to Questions and Exercises:
Questions
Question: At the beginning of this lesson, you learned that the core collection interfaces are organized into two distinctinheritance trees. One interface in particular is not considered to bea trueCollection, and therefore sits at the top of its own tree. What is the nameof this interface?
Answer:
Map
Question: Each interface in the collections framework is declaredwith the
<E>syntax, which tells you that it isgeneric. When you declare a
Collectioninstance, what isthe advantage of specifying the type of objects that it will contain?
Answer: Specifying the type allows the compiler to verify (at compile time) that the type of object you put into the collection is correct, thus reducing errors at runtime.
Question: What interface represents a collection that does not allow duplicate elements?
Answer:
Set
Question: What interface forms the root of the collections hierarchy?
Answer:
Collection
Question: What interface represents an ordered collection that may contain duplicate elements?
Answer:
List
Question: What interface represents a collection that holds elements prior to processing?
Answer:
Queue
Question: What interface represents a type that maps keys to values?
Answer:
Map
Question: What interface represents a double-ended queue?
Answer:
Deque
Question:Name three different ways to iterate over the elements of a
List.
Answer: You can iterate over a
Listusing streams, the enhanced
forstatement, or iterators.
Question: True or False: Aggregate operations are mutative operations that modify the underlying collection.
Answer: False. Aggregate operations do not mutate the underlying collection. In fact, you must be careful to never mutate a collection while invoking its aggregate operations.Doing so could lead to concurrency problems should the stream be changed to a parallel stream at some point in the future.
Exercises
Exercise: Write a program that prints its arguments in random order. Do not make a copy of the argument array. Demonstrate how to print out the elements using both streams and the traditional enhanced for statement.Answer:
import java.util.*; public class Ran { public static void main(String[] args) { // Get and shuffle the list of arguments List<String> argList = Arrays.asList(args); Collections.shuffle(argList); // Print out the elements using JDK 8 Streams argList.stream() .forEach(e->System.out.format("%s ",e)); // Print out the elements using for-each for (String arg: argList) { System.out.format("%s ", arg); } System.out.println(); } }
Exercise: Take the
FindDupsexample and modify it to use a
SortedSetinstead of a
Set. Specify a
Comparatorso that case is ignored when sorting and identifying set elements.
Answer:
import java.util.*; public class FindDups { public static void main(String[] args) { Set<String> s = new HashSet<String>(); for (String a : args) s.add(a); System.out.println(s.size() + " distinct words: " + s); } }
Exercise: Write a method that takes a
List<String>and applies
String.trimto each element.
Answer:
The enhanced
forstatement does not allow you to modify the
List. Using an instance of the
Iteratorclass allows you to delete elements, but not replace an existing element or add a new one. That leaves
ListIterator:
import java.util.*; public class ListTrim { static void listTrim(List<String> strings) { for (ListIterator<String> lit = strings.listIterator(); lit.hasNext(); ) { lit.set(lit.next().trim()); } } public static void main(String[] args) { List<String> l = Arrays.asList(" red ", " white ", " blue "); listTrim(l); for (String s : l) { System.out.format("\"%s\"%n", s); } } }
Exercise: Consider the four core interfaces,
Set,
List,
Queue, and
Map.For each of the following four assignments, specify which of the four core interfaces is best-suited, and explain how to use it to implement the assignment.
Answers:
Whimsical Toys Inc (WTI) needs to record the names of all its employees. Every month, an employee will be chosen at random from these records to receive a free toy.
Use a
List. Choose a random employee by picking a number between
0and
size()-1.
WTI has decided that each new product will be named after an employee — but only first names will be used, and each name will be used only once. Prepare a list of unique first names.
Use a
Set. Collections that implement this interface don't allow the same element to be entered more than once.
WTI decides that it only wants to use the most popular names for its toys. Count up the number of employees who have each first name.
Use a
Map, where the keys are first names, and each value is a count of the number of employees with that first name.
WTI acquires season tickets for the local lacrosse team, to be shared by employees. Create a waiting list for this popular sport.
Use a
Queue. Invoke
add()to add employees to the waiting list, and
remove()to remove them.
Original:
http://docs.oracle.com/javase/tutorial/collections/interfaces/order.html
相关文章推荐
- 高吞吐低延迟Java应用的垃圾回收优化
- Java并发与多线程教程(3)
- 深入理解Java类加载器(1):Java类加载原理解析
- 浅析Java虚拟机结构与机制
- 深入理解Java注解(2):高级应用
- Java集合框架官方教程(6):算法
- 深入理解Java国际化
- Java集合框架官方教程(3):SortedSet/SortedMap接口
- 高吞吐低延迟Java应用的垃圾回收优化
- Java集合框架总结
- 深入理解Java注解(1):基础详解
- 成为Java GC专家(3):如何优化Java垃圾回收机制
- Java并发与多线程教程(3)
- 深入理解Java类加载器(1):Java类加载原理解析
- 浅析Java虚拟机结构与机制
- 深入理解Java注解(2):高级应用
- Java集合框架官方教程(5):集合类的同步包装器/不可变包装器
- Java集合框架官方教程(6):算法
- 深入理解Java国际化
- Java集合框架总结