ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/jsr166/jsr166/src/main/java/util/concurrent/ConcurrentHashMap.java
(Generate patch)

Comparing jsr166/src/main/java/util/concurrent/ConcurrentHashMap.java (file contents):
Revision 1.24 by dl, Fri Oct 10 23:51:28 2003 UTC vs.
Revision 1.42 by dl, Tue Jan 27 11:36:31 2004 UTC

# Line 1 | Line 1
1   /*
2   * Written by Doug Lea with assistance from members of JCP JSR-166
3 < * Expert Group and released to the public domain. Use, modify, and
4 < * redistribute this code in any way without acknowledgement.
3 > * Expert Group and released to the public domain, as explained at
4 > * http://creativecommons.org/licenses/publicdomain
5   */
6  
7   package java.util.concurrent;
# Line 17 | Line 17 | import java.io.ObjectOutputStream;
17   * adjustable expected concurrency for updates. This class obeys the
18   * same functional specification as {@link java.util.Hashtable}, and
19   * includes versions of methods corresponding to each method of
20 < * <tt>Hashtable</tt> . However, even though all operations are
20 > * <tt>Hashtable</tt>. However, even though all operations are
21   * thread-safe, retrieval operations do <em>not</em> entail locking,
22   * and there is <em>not</em> any support for locking the entire table
23   * in a way that prevents all access.  This class is fully
24   * interoperable with <tt>Hashtable</tt> in programs that rely on its
25   * thread safety but not on its synchronization details.
26   *
27 < * <p> Retrieval operations (including <tt>get</tt>) ordinarily
28 < * overlap with update operations (including <tt>put</tt> and
29 < * <tt>remove</tt>). Retrievals reflect the results of the most
30 < * recently <em>completed</em> update operations holding upon their
31 < * onset.  For aggregate operations such as <tt>putAll</tt> and
32 < * <tt>clear</tt>, concurrent retrievals may reflect insertion or
27 > * <p> Retrieval operations (including <tt>get</tt>) generally do not
28 > * block, so may overlap with update operations (including
29 > * <tt>put</tt> and <tt>remove</tt>). Retrievals reflect the results
30 > * of the most recently <em>completed</em> update operations holding
31 > * upon their onset.  For aggregate operations such as <tt>putAll</tt>
32 > * and <tt>clear</tt>, concurrent retrievals may reflect insertion or
33   * removal of only some entries.  Similarly, Iterators and
34   * Enumerations return elements reflecting the state of the hash table
35   * at some point at or since the creation of the iterator/enumeration.
36 < * They do <em>not</em> throw <tt>ConcurrentModificationException</tt>.
37 < * However, Iterators are designed to be used by only one thread at a
38 < * time.
36 > * They do <em>not</em> throw
37 > * {@link ConcurrentModificationException}.  However, iterators are
38 > * designed to be used by only one thread at a time.
39   *
40   * <p> The allowed concurrency among update operations is guided by
41   * the optional <tt>concurrencyLevel</tt> constructor argument
# Line 44 | Line 44 | import java.io.ObjectOutputStream;
44   * number of concurrent updates without contention. Because placement
45   * in hash tables is essentially random, the actual concurrency will
46   * vary.  Ideally, you should choose a value to accommodate as many
47 < * threads as will ever concurrently access the table. Using a
47 > * threads as will ever concurrently modify the table. Using a
48   * significantly higher value than you need can waste space and time,
49   * and a significantly lower value can lead to thread contention. But
50   * overestimates and underestimates within an order of magnitude do
51 < * not usually have much noticeable impact.
51 > * not usually have much noticeable impact. A value of one is
52 > * appropriate when it is known that only one thread will modify
53 > * and all others will only read.
54   *
55   * <p>This class implements all of the <em>optional</em> methods
56   * of the {@link Map} and {@link Iterator} interfaces.
# Line 57 | Line 59 | import java.io.ObjectOutputStream;
59   * java.util.HashMap}, this class does NOT allow <tt>null</tt> to be
60   * used as a key or value.
61   *
62 + * <p>This class is a member of the
63 + * <a href="{@docRoot}/../guide/collections/index.html">
64 + * Java Collections Framework</a>.
65 + *
66   * @since 1.5
67   * @author Doug Lea
68 + * @param <K> the type of keys maintained by this map
69 + * @param <V> the type of mapped values
70   */
71   public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
72          implements ConcurrentMap<K, V>, Cloneable, Serializable {
# Line 75 | Line 83 | public class ConcurrentHashMap<K, V> ext
83       * The default initial number of table slots for this table.
84       * Used when not otherwise specified in constructor.
85       */
86 <    private static int DEFAULT_INITIAL_CAPACITY = 16;
86 >    static int DEFAULT_INITIAL_CAPACITY = 16;
87  
88      /**
89       * The maximum capacity, used if a higher value is implicitly
# Line 94 | Line 102 | public class ConcurrentHashMap<K, V> ext
102      /**
103       * The default number of concurrency control segments.
104       **/
105 <    private static final int DEFAULT_SEGMENTS = 16;
105 >    static final int DEFAULT_SEGMENTS = 16;
106  
107      /**
108 <     * The maximum number of segments to allow; used to bound ctor arguments.
108 >     * The maximum number of segments to allow; used to bound
109 >     * constructor arguments.
110       */
111 <    private static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
111 >    static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
112  
113      /* ---------------- Fields -------------- */
114  
# Line 107 | Line 116 | public class ConcurrentHashMap<K, V> ext
116       * Mask value for indexing into segments. The upper bits of a
117       * key's hash code are used to choose the segment.
118       **/
119 <    private final int segmentMask;
119 >    final int segmentMask;
120  
121      /**
122       * Shift value for indexing within segments.
123       **/
124 <    private final int segmentShift;
124 >    final int segmentShift;
125  
126      /**
127       * The segments, each of which is a specialized hash table
128       */
129 <    private final Segment[] segments;
129 >    final Segment[] segments;
130  
131 <    private transient Set<K> keySet;
132 <    private transient Set<Map.Entry<K,V>> entrySet;
133 <    private transient Collection<V> values;
131 >    transient Set<K> keySet;
132 >    transient Set<Map.Entry<K,V>> entrySet;
133 >    transient Collection<V> values;
134  
135      /* ---------------- Small Utilities -------------- */
136  
137      /**
138       * Return a hash code for non-null Object x.
139 <     * Uses the same hash code spreader as most other j.u hash tables.
139 >     * Uses the same hash code spreader as most other java.util hash tables.
140       * @param x the object serving as a key
141       * @return the hash code
142       */
143 <    private static int hash(Object x) {
143 >    static int hash(Object x) {
144          int h = x.hashCode();
145          h += ~(h << 9);
146          h ^=  (h >>> 14);
# Line 143 | Line 152 | public class ConcurrentHashMap<K, V> ext
152      /**
153       * Return the segment that should be used for key with given hash
154       */
155 <    private Segment<K,V> segmentFor(int hash) {
155 >    final Segment<K,V> segmentFor(int hash) {
156          return (Segment<K,V>) segments[(hash >>> segmentShift) & segmentMask];
157      }
158  
# Line 154 | Line 163 | public class ConcurrentHashMap<K, V> ext
163       * subclasses from ReentrantLock opportunistically, just to
164       * simplify some locking and avoid separate construction.
165       **/
166 <    private static final class Segment<K,V> extends ReentrantLock implements Serializable {
166 >    static final class Segment<K,V> extends ReentrantLock implements Serializable {
167          /*
168           * Segments maintain a table of entry lists that are ALWAYS
169           * kept in a consistent state, so can be read without locking.
# Line 213 | Line 222 | public class ConcurrentHashMap<K, V> ext
222           * (The value of this field is always (int)(capacity *
223           * loadFactor).)
224           */
225 <        private transient int threshold;
225 >        transient int threshold;
226  
227          /**
228           * The per-segment table
# Line 226 | Line 235 | public class ConcurrentHashMap<K, V> ext
235           * links to outer object.
236           * @serial
237           */
238 <        private final float loadFactor;
238 >        final float loadFactor;
239  
240          Segment(int initialCapacity, float lf) {
241              loadFactor = lf;
# Line 237 | Line 246 | public class ConcurrentHashMap<K, V> ext
246           * Set table to new HashEntry array.
247           * Call only while holding lock or in constructor.
248           **/
249 <        private void setTable(HashEntry[] newTable) {
249 >        void setTable(HashEntry[] newTable) {
250              table = newTable;
251              threshold = (int)(newTable.length * loadFactor);
252              count = count; // write-volatile
# Line 245 | Line 254 | public class ConcurrentHashMap<K, V> ext
254  
255          /* Specialized implementations of map methods */
256  
257 <        V get(K key, int hash) {
257 >        V get(Object key, int hash) {
258              if (count != 0) { // read-volatile
259                  HashEntry[] tab = table;
260                  int index = hash & (tab.length - 1);
# Line 285 | Line 294 | public class ConcurrentHashMap<K, V> ext
294              return false;
295          }
296  
297 +        boolean replace(K key, int hash, V oldValue, V newValue) {
298 +            lock();
299 +            try {
300 +                int c = count;
301 +                HashEntry[] tab = table;
302 +                int index = hash & (tab.length - 1);
303 +                HashEntry<K,V> first = (HashEntry<K,V>) tab[index];
304 +                HashEntry<K,V> e = first;
305 +                for (;;) {
306 +                    if (e == null)
307 +                        return false;
308 +                    if (e.hash == hash && key.equals(e.key))
309 +                        break;
310 +                    e = e.next;
311 +                }
312 +
313 +                V v = e.value;
314 +                if (v == null || !oldValue.equals(v))
315 +                    return false;
316 +
317 +                e.value = newValue;
318 +                count = c; // write-volatile
319 +                return true;
320 +                
321 +            } finally {
322 +                unlock();
323 +            }
324 +        }
325 +
326 +        V replace(K key, int hash, V newValue) {
327 +            lock();
328 +            try {
329 +                int c = count;
330 +                HashEntry[] tab = table;
331 +                int index = hash & (tab.length - 1);
332 +                HashEntry<K,V> first = (HashEntry<K,V>) tab[index];
333 +                HashEntry<K,V> e = first;
334 +                for (;;) {
335 +                    if (e == null)
336 +                        return null;
337 +                    if (e.hash == hash && key.equals(e.key))
338 +                        break;
339 +                    e = e.next;
340 +                }
341 +
342 +                V v = e.value;
343 +                e.value = newValue;
344 +                count = c; // write-volatile
345 +                return v;
346 +                
347 +            } finally {
348 +                unlock();
349 +            }
350 +        }
351 +
352 +
353          V put(K key, int hash, V value, boolean onlyIfAbsent) {
354              lock();
355              try {
# Line 316 | Line 381 | public class ConcurrentHashMap<K, V> ext
381              }
382          }
383  
384 <        private HashEntry[] rehash(HashEntry[] oldTable) {
384 >        HashEntry[] rehash(HashEntry[] oldTable) {
385              int oldCapacity = oldTable.length;
386              if (oldCapacity >= MAXIMUM_CAPACITY)
387                  return oldTable;
# Line 328 | Line 393 | public class ConcurrentHashMap<K, V> ext
393               * offset. We eliminate unnecessary node creation by catching
394               * cases where old nodes can be reused because their next
395               * fields won't change. Statistically, at the default
396 <             * threshhold, only about one-sixth of them need cloning when
396 >             * threshold, only about one-sixth of them need cloning when
397               * a table doubles. The nodes they replace will be garbage
398               * collectable as soon as they are no longer referenced by any
399               * reader thread that may be in the midst of traversing table
# Line 404 | Line 469 | public class ConcurrentHashMap<K, V> ext
469                      return null;
470  
471                  // All entries following removed node can stay in list, but
472 <                // all preceeding ones need to be cloned.
472 >                // all preceding ones need to be cloned.
473                  HashEntry<K,V> newFirst = e.next;
474                  for (HashEntry<K,V> p = first; p != e; p = p.next)
475                      newFirst = new HashEntry<K,V>(p.hash, p.key,
# Line 433 | Line 498 | public class ConcurrentHashMap<K, V> ext
498      }
499  
500      /**
501 <     * ConcurrentHashMap list entry.
501 >     * ConcurrentHashMap list entry. Note that this is never exported
502 >     * out as a user-visible Map.Entry
503       */
504 <    private static class HashEntry<K,V> implements Entry<K,V> {
505 <        private final K key;
506 <        private V value;
507 <        private final int hash;
508 <        private final HashEntry<K,V> next;
504 >    static final class HashEntry<K,V> {
505 >        final K key;
506 >        V value;
507 >        final int hash;
508 >        final HashEntry<K,V> next;
509  
510          HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
511              this.value = value;
# Line 447 | Line 513 | public class ConcurrentHashMap<K, V> ext
513              this.key = key;
514              this.next = next;
515          }
450
451        public K getKey() {
452            return key;
453        }
454
455        public V getValue() {
456            return value;
457        }
458
459        public V setValue(V newValue) {
460            // We aren't required to, and don't provide any
461            // visibility barriers for setting value.
462            if (newValue == null)
463                throw new NullPointerException();
464            V oldValue = this.value;
465            this.value = newValue;
466            return oldValue;
467        }
468
469        public boolean equals(Object o) {
470            if (!(o instanceof Entry))
471                return false;
472            Entry<K,V> e = (Entry<K,V>)o;
473            return (key.equals(e.getKey()) && value.equals(e.getValue()));
474        }
475
476        public int hashCode() {
477            return  key.hashCode() ^ value.hashCode();
478        }
479
480        public String toString() {
481            return key + "=" + value;
482        }
516      }
517  
518  
# Line 556 | Line 589 | public class ConcurrentHashMap<K, V> ext
589       * Constructs a new map with the same mappings as the given map.  The
590       * map is created with a capacity of twice the number of mappings in
591       * the given map or 11 (whichever is greater), and a default load factor.
592 +     * @param t the map
593       */
594 <    public <A extends K, B extends V> ConcurrentHashMap(Map<A,B> t) {
594 >    public ConcurrentHashMap(Map<? extends K, ? extends V> t) {
595          this(Math.max((int) (t.size() / DEFAULT_LOAD_FACTOR) + 1,
596                        11),
597               DEFAULT_LOAD_FACTOR, DEFAULT_SEGMENTS);
# Line 566 | Line 600 | public class ConcurrentHashMap<K, V> ext
600  
601      // inherit Map javadoc
602      public boolean isEmpty() {
603 +        final Segment[] segments = this.segments;
604          /*
605           * We need to keep track of per-segment modCounts to avoid ABA
606           * problems in which an element in one segment was added and
# Line 598 | Line 633 | public class ConcurrentHashMap<K, V> ext
633  
634      // inherit Map javadoc
635      public int size() {
636 +        final Segment[] segments = this.segments;
637          int[] mc = new int[segments.length];
638          for (;;) {
639              long sum = 0;
# Line 638 | Line 674 | public class ConcurrentHashMap<K, V> ext
674       */
675      public V get(Object key) {
676          int hash = hash(key); // throws NullPointerException if key null
677 <        return segmentFor(hash).get((K) key, hash);
677 >        return segmentFor(hash).get(key, hash);
678      }
679  
680      /**
# Line 671 | Line 707 | public class ConcurrentHashMap<K, V> ext
707          if (value == null)
708              throw new NullPointerException();
709  
710 +        final Segment[] segments = this.segments;
711          int[] mc = new int[segments.length];
712          for (;;) {
713              int sum = 0;
# Line 823 | Line 860 | public class ConcurrentHashMap<K, V> ext
860          return segmentFor(hash).remove(key, hash, value) != null;
861      }
862  
863 +
864 +    /**
865 +     * Replace entry for key only if currently mapped to given value.
866 +     * Acts as
867 +     * <pre>
868 +     *  if (map.get(key).equals(oldValue)) {
869 +     *     map.put(key, newValue);
870 +     *     return true;
871 +     * } else return false;
872 +     * </pre>
873 +     * except that the action is performed atomically.
874 +     * @param key key with which the specified value is associated.
875 +     * @param oldValue value expected to be associated with the specified key.
876 +     * @param newValue value to be associated with the specified key.
877 +     * @return true if the value was replaced
878 +     * @throws NullPointerException if the specified key or values are
879 +     * <tt>null</tt>.
880 +     */
881 +    public boolean replace(K key, V oldValue, V newValue) {
882 +        if (oldValue == null || newValue == null)
883 +            throw new NullPointerException();
884 +        int hash = hash(key);
885 +        return segmentFor(hash).replace(key, hash, oldValue, newValue);
886 +    }
887 +
888 +    /**
889 +     * Replace entry for key only if currently mapped to some value.
890 +     * Acts as
891 +     * <pre>
892 +     *  if ((map.containsKey(key)) {
893 +     *     return map.put(key, value);
894 +     * } else return null;
895 +     * </pre>
896 +     * except that the action is performed atomically.
897 +     * @param key key with which the specified value is associated.
898 +     * @param value value to be associated with the specified key.
899 +     * @return previous value associated with specified key, or <tt>null</tt>
900 +     *         if there was no mapping for key.  
901 +     * @throws NullPointerException if the specified key or value is
902 +     *            <tt>null</tt>.
903 +     */
904 +    public V replace(K key, V value) {
905 +        if (value == null)
906 +            throw new NullPointerException();
907 +        int hash = hash(key);
908 +        return segmentFor(hash).replace(key, hash, value);
909 +    }
910 +
911 +
912      /**
913       * Removes all mappings from this map.
914       */
# Line 931 | Line 1017 | public class ConcurrentHashMap<K, V> ext
1017  
1018      /**
1019       * Returns an enumeration of the values in this table.
934     * Use the Enumeration methods on the returned object to fetch the elements
935     * sequentially.
1020       *
1021       * @return  an enumeration of the values in this table.
1022       * @see     #values
# Line 943 | Line 1027 | public class ConcurrentHashMap<K, V> ext
1027  
1028      /* ---------------- Iterator Support -------------- */
1029  
1030 <    private abstract class HashIterator {
1031 <        private int nextSegmentIndex;
1032 <        private int nextTableIndex;
1033 <        private HashEntry[] currentTable;
1034 <        private HashEntry<K, V> nextEntry;
1035 <        private HashEntry<K, V> lastReturned;
1030 >    abstract class HashIterator {
1031 >        int nextSegmentIndex;
1032 >        int nextTableIndex;
1033 >        HashEntry[] currentTable;
1034 >        HashEntry<K, V> nextEntry;
1035 >        HashEntry<K, V> lastReturned;
1036  
1037 <        private HashIterator() {
1037 >        HashIterator() {
1038              nextSegmentIndex = segments.length - 1;
1039              nextTableIndex = -1;
1040              advance();
# Line 958 | Line 1042 | public class ConcurrentHashMap<K, V> ext
1042  
1043          public boolean hasMoreElements() { return hasNext(); }
1044  
1045 <        private void advance() {
1045 >        final void advance() {
1046              if (nextEntry != null && (nextEntry = nextEntry.next) != null)
1047                  return;
1048  
# Line 999 | Line 1083 | public class ConcurrentHashMap<K, V> ext
1083          }
1084      }
1085  
1086 <    private class KeyIterator extends HashIterator implements Iterator<K>, Enumeration<K> {
1086 >    final class KeyIterator extends HashIterator implements Iterator<K>, Enumeration<K> {
1087          public K next() { return super.nextEntry().key; }
1088          public K nextElement() { return super.nextEntry().key; }
1089      }
1090  
1091 <    private class ValueIterator extends HashIterator implements Iterator<V>, Enumeration<V> {
1091 >    final class ValueIterator extends HashIterator implements Iterator<V>, Enumeration<V> {
1092          public V next() { return super.nextEntry().value; }
1093          public V nextElement() { return super.nextEntry().value; }
1094      }
1095  
1096 <    private class EntryIterator extends HashIterator implements Iterator<Entry<K,V>> {
1097 <        public Map.Entry<K,V> next() { return super.nextEntry(); }
1096 >    
1097 >
1098 >    /**
1099 >     * Entry iterator. Exported Entry objects must write-through
1100 >     * changes in setValue, even if the nodes have been cloned. So we
1101 >     * cannot return internal HashEntry objects. Instead, the iterator
1102 >     * itself acts as a forwarding pseudo-entry.
1103 >     */
1104 >    final class EntryIterator extends HashIterator implements Map.Entry<K,V>, Iterator<Entry<K,V>> {
1105 >        public Map.Entry<K,V> next() {
1106 >            nextEntry();
1107 >            return this;
1108 >        }
1109 >
1110 >        public K getKey() {
1111 >            if (lastReturned == null)
1112 >                throw new IllegalStateException("Entry was removed");
1113 >            return lastReturned.key;
1114 >        }
1115 >
1116 >        public V getValue() {
1117 >            if (lastReturned == null)
1118 >                throw new IllegalStateException("Entry was removed");
1119 >            return ConcurrentHashMap.this.get(lastReturned.key);
1120 >        }
1121 >
1122 >        public V setValue(V value) {
1123 >            if (lastReturned == null)
1124 >                throw new IllegalStateException("Entry was removed");
1125 >            return ConcurrentHashMap.this.put(lastReturned.key, value);
1126 >        }
1127 >
1128 >        public boolean equals(Object o) {
1129 >            if (!(o instanceof Map.Entry))
1130 >                return false;
1131 >            Map.Entry e = (Map.Entry)o;
1132 >            return eq(getKey(), e.getKey()) && eq(getValue(), e.getValue());
1133 >        }
1134 >
1135 >        public int hashCode() {
1136 >            Object k = getKey();
1137 >            Object v = getValue();
1138 >            return ((k == null) ? 0 : k.hashCode()) ^
1139 >                   ((v == null) ? 0 : v.hashCode());
1140 >        }
1141 >
1142 >        public String toString() {
1143 >            // If not acting as entry, just use default toString.
1144 >            if (lastReturned == null)
1145 >                return super.toString();
1146 >            else
1147 >                return getKey() + "=" + getValue();
1148 >        }
1149 >
1150 >        boolean eq(Object o1, Object o2) {
1151 >            return (o1 == null ? o2 == null : o1.equals(o2));
1152 >        }
1153 >
1154      }
1155  
1156 <    private class KeySet extends AbstractSet<K> {
1156 >    final class KeySet extends AbstractSet<K> {
1157          public Iterator<K> iterator() {
1158              return new KeyIterator();
1159          }
# Line 1031 | Line 1171 | public class ConcurrentHashMap<K, V> ext
1171          }
1172      }
1173  
1174 <    private class Values extends AbstractCollection<V> {
1174 >    final class Values extends AbstractCollection<V> {
1175          public Iterator<V> iterator() {
1176              return new ValueIterator();
1177          }
# Line 1046 | Line 1186 | public class ConcurrentHashMap<K, V> ext
1186          }
1187      }
1188  
1189 <    private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
1189 >    final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
1190          public Iterator<Map.Entry<K,V>> iterator() {
1191              return new EntryIterator();
1192          }
# Line 1069 | Line 1209 | public class ConcurrentHashMap<K, V> ext
1209          public void clear() {
1210              ConcurrentHashMap.this.clear();
1211          }
1212 +        public Object[] toArray() {
1213 +            // Since we don't ordinarily have distinct Entry objects, we
1214 +            // must pack elements using exportable SimpleEntry
1215 +            Collection<Map.Entry<K,V>> c = new ArrayList<Map.Entry<K,V>>(size());
1216 +            for (Iterator<Map.Entry<K,V>> i = iterator(); i.hasNext(); )
1217 +                c.add(new SimpleEntry<K,V>(i.next()));
1218 +            return c.toArray();
1219 +        }
1220 +        public <T> T[] toArray(T[] a) {
1221 +            Collection<Map.Entry<K,V>> c = new ArrayList<Map.Entry<K,V>>(size());
1222 +            for (Iterator<Map.Entry<K,V>> i = iterator(); i.hasNext(); )
1223 +                c.add(new SimpleEntry<K,V>(i.next()));
1224 +            return c.toArray(a);
1225 +        }
1226 +
1227 +    }
1228 +
1229 +    /**
1230 +     * This duplicates java.util.AbstractMap.SimpleEntry until this class
1231 +     * is made accessible.
1232 +     */
1233 +    static final class SimpleEntry<K,V> implements Entry<K,V> {
1234 +        K key;
1235 +        V value;
1236 +
1237 +        public SimpleEntry(K key, V value) {
1238 +            this.key   = key;
1239 +            this.value = value;
1240 +        }
1241 +
1242 +        public SimpleEntry(Entry<K,V> e) {
1243 +            this.key   = e.getKey();
1244 +            this.value = e.getValue();
1245 +        }
1246 +
1247 +        public K getKey() {
1248 +            return key;
1249 +        }
1250 +
1251 +        public V getValue() {
1252 +            return value;
1253 +        }
1254 +
1255 +        public V setValue(V value) {
1256 +            V oldValue = this.value;
1257 +            this.value = value;
1258 +            return oldValue;
1259 +        }
1260 +
1261 +        public boolean equals(Object o) {
1262 +            if (!(o instanceof Map.Entry))
1263 +                return false;
1264 +            Map.Entry e = (Map.Entry)o;
1265 +            return eq(key, e.getKey()) && eq(value, e.getValue());
1266 +        }
1267 +
1268 +        public int hashCode() {
1269 +            return ((key   == null)   ? 0 :   key.hashCode()) ^
1270 +                   ((value == null)   ? 0 : value.hashCode());
1271 +        }
1272 +
1273 +        public String toString() {
1274 +            return key + "=" + value;
1275 +        }
1276 +
1277 +        static boolean eq(Object o1, Object o2) {
1278 +            return (o1 == null ? o2 == null : o1.equals(o2));
1279 +        }
1280      }
1281  
1282      /* ---------------- Serialization Support -------------- */

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines