1 /* 2 * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util; 27 28 import jdk.internal.misc.SharedSecrets; 29 30 /** 31 * A specialized {@link Map} implementation for use with enum type keys. All 32 * of the keys in an enum map must come from a single enum type that is 33 * specified, explicitly or implicitly, when the map is created. Enum maps 34 * are represented internally as arrays. This representation is extremely 35 * compact and efficient. 36 * 37 * <p>Enum maps are maintained in the <i>natural order</i> of their keys 38 * (the order in which the enum constants are declared). This is reflected 39 * in the iterators returned by the collections views ({@link #keySet()}, 40 * {@link #entrySet()}, and {@link #values()}). 41 * 42 * <p>Iterators returned by the collection views are <i>weakly consistent</i>: 43 * they will never throw {@link ConcurrentModificationException} and they may 44 * or may not show the effects of any modifications to the map that occur while 45 * the iteration is in progress. 46 * 47 * <p>Null keys are not permitted. Attempts to insert a null key will 48 * throw {@link NullPointerException}. Attempts to test for the 49 * presence of a null key or to remove one will, however, function properly. 50 * Null values are permitted. 51 52 * <P>Like most collection implementations {@code EnumMap} is not 53 * synchronized. If multiple threads access an enum map concurrently, and at 54 * least one of the threads modifies the map, it should be synchronized 55 * externally. This is typically accomplished by synchronizing on some 56 * object that naturally encapsulates the enum map. If no such object exists, 57 * the map should be "wrapped" using the {@link Collections#synchronizedMap} 58 * method. This is best done at creation time, to prevent accidental 59 * unsynchronized access: 60 * 61 * <pre> 62 * Map<EnumKey, V> m 63 * = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...)); 64 * </pre> 65 * 66 * <p>Implementation note: All basic operations execute in constant time. 67 * They are likely (though not guaranteed) to be faster than their 68 * {@link HashMap} counterparts. 69 * 70 * <p>This class is a member of the 71 * <a href="{@docRoot}/java/util/package-summary.html#CollectionsFramework"> 72 * Java Collections Framework</a>. 73 * 74 * @author Josh Bloch 75 * @see EnumSet 76 * @since 1.5 77 */ 78 public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> 79 implements java.io.Serializable, Cloneable 80 { 81 /** 82 * The {@code Class} object for the enum type of all the keys of this map. 83 * 84 * @serial 85 */ 86 private final Class<K> keyType; 87 88 /** 89 * All of the values comprising K. (Cached for performance.) 90 */ 91 private transient K[] keyUniverse; 92 93 /** 94 * Array representation of this map. The ith element is the value 95 * to which universe[i] is currently mapped, or null if it isn't 96 * mapped to anything, or NULL if it's mapped to null. 97 */ 98 private transient Object[] vals; 99 100 /** 101 * The number of mappings in this map. 102 */ 103 private transient int size = 0; 104 105 /** 106 * Distinguished non-null value for representing null values. 107 */ 108 private static final Object NULL = new Object() { 109 public int hashCode() { 110 return 0; 111 } 112 113 public String toString() { 114 return "java.util.EnumMap.NULL"; 115 } 116 }; 117 118 private Object maskNull(Object value) { 119 return (value == null ? NULL : value); 120 } 121 122 @SuppressWarnings("unchecked") 123 private V unmaskNull(Object value) { 124 return (V)(value == NULL ? null : value); 125 } 126 127 /** 128 * Creates an empty enum map with the specified key type. 129 * 130 * @param keyType the class object of the key type for this enum map 131 * @throws NullPointerException if {@code keyType} is null 132 */ 133 public EnumMap(Class<K> keyType) { 134 this.keyType = keyType; 135 keyUniverse = getKeyUniverse(keyType); 136 vals = new Object[keyUniverse.length]; 137 } 138 139 /** 140 * Creates an enum map with the same key type as the specified enum 141 * map, initially containing the same mappings (if any). 142 * 143 * @param m the enum map from which to initialize this enum map 144 * @throws NullPointerException if {@code m} is null 145 */ 146 public EnumMap(EnumMap<K, ? extends V> m) { 147 keyType = m.keyType; 148 keyUniverse = m.keyUniverse; 149 vals = m.vals.clone(); 150 size = m.size; 151 } 152 153 /** 154 * Creates an enum map initialized from the specified map. If the 155 * specified map is an {@code EnumMap} instance, this constructor behaves 156 * identically to {@link #EnumMap(EnumMap)}. Otherwise, the specified map 157 * must contain at least one mapping (in order to determine the new 158 * enum map's key type). 159 * 160 * @param m the map from which to initialize this enum map 161 * @throws IllegalArgumentException if {@code m} is not an 162 * {@code EnumMap} instance and contains no mappings 163 * @throws NullPointerException if {@code m} is null 164 */ 165 public EnumMap(Map<K, ? extends V> m) { 166 if (m instanceof EnumMap) { 167 EnumMap<K, ? extends V> em = (EnumMap<K, ? extends V>) m; 168 keyType = em.keyType; 169 keyUniverse = em.keyUniverse; 170 vals = em.vals.clone(); 171 size = em.size; 172 } else { 173 if (m.isEmpty()) 174 throw new IllegalArgumentException("Specified map is empty"); 175 keyType = m.keySet().iterator().next().getDeclaringClass(); 176 keyUniverse = getKeyUniverse(keyType); 177 vals = new Object[keyUniverse.length]; 178 putAll(m); 179 } 180 } 181 182 // Query Operations 183 184 /** 185 * Returns the number of key-value mappings in this map. 186 * 187 * @return the number of key-value mappings in this map 188 */ 189 public int size() { 190 return size; 191 } 192 193 /** 194 * Returns {@code true} if this map maps one or more keys to the 195 * specified value. 196 * 197 * @param value the value whose presence in this map is to be tested 198 * @return {@code true} if this map maps one or more keys to this value 199 */ 200 public boolean containsValue(Object value) { 201 value = maskNull(value); 202 203 for (Object val : vals) 204 if (value.equals(val)) 205 return true; 206 207 return false; 208 } 209 210 /** 211 * Returns {@code true} if this map contains a mapping for the specified 212 * key. 213 * 214 * @param key the key whose presence in this map is to be tested 215 * @return {@code true} if this map contains a mapping for the specified 216 * key 217 */ 218 public boolean containsKey(Object key) { 219 return isValidKey(key) && vals[((Enum<?>)key).ordinal()] != null; 220 } 221 222 private boolean containsMapping(Object key, Object value) { 223 return isValidKey(key) && 224 maskNull(value).equals(vals[((Enum<?>)key).ordinal()]); 225 } 226 227 /** 228 * Returns the value to which the specified key is mapped, 229 * or {@code null} if this map contains no mapping for the key. 230 * 231 * <p>More formally, if this map contains a mapping from a key 232 * {@code k} to a value {@code v} such that {@code (key == k)}, 233 * then this method returns {@code v}; otherwise it returns 234 * {@code null}. (There can be at most one such mapping.) 235 * 236 * <p>A return value of {@code null} does not <i>necessarily</i> 237 * indicate that the map contains no mapping for the key; it's also 238 * possible that the map explicitly maps the key to {@code null}. 239 * The {@link #containsKey containsKey} operation may be used to 240 * distinguish these two cases. 241 */ 242 public V get(Object key) { 243 return (isValidKey(key) ? 244 unmaskNull(vals[((Enum<?>)key).ordinal()]) : null); 245 } 246 247 // Modification Operations 248 249 /** 250 * Associates the specified value with the specified key in this map. 251 * If the map previously contained a mapping for this key, the old 252 * value is replaced. 253 * 254 * @param key the key with which the specified value is to be associated 255 * @param value the value to be associated with the specified key 256 * 257 * @return the previous value associated with specified key, or 258 * {@code null} if there was no mapping for key. (A {@code null} 259 * return can also indicate that the map previously associated 260 * {@code null} with the specified key.) 261 * @throws NullPointerException if the specified key is null 262 */ 263 public V put(K key, V value) { 264 typeCheck(key); 265 266 int index = key.ordinal(); 267 Object oldValue = vals[index]; 268 vals[index] = maskNull(value); 269 if (oldValue == null) 270 size++; 271 return unmaskNull(oldValue); 272 } 273 274 /** 275 * Removes the mapping for this key from this map if present. 276 * 277 * @param key the key whose mapping is to be removed from the map 278 * @return the previous value associated with specified key, or 279 * {@code null} if there was no entry for key. (A {@code null} 280 * return can also indicate that the map previously associated 281 * {@code null} with the specified key.) 282 */ 283 public V remove(Object key) { 284 if (!isValidKey(key)) 285 return null; 286 int index = ((Enum<?>)key).ordinal(); 287 Object oldValue = vals[index]; 288 vals[index] = null; 289 if (oldValue != null) 290 size--; 291 return unmaskNull(oldValue); 292 } 293 294 private boolean removeMapping(Object key, Object value) { 295 if (!isValidKey(key)) 296 return false; 297 int index = ((Enum<?>)key).ordinal(); 298 if (maskNull(value).equals(vals[index])) { 299 vals[index] = null; 300 size--; 301 return true; 302 } 303 return false; 304 } 305 306 /** 307 * Returns true if key is of the proper type to be a key in this 308 * enum map. 309 */ 310 private boolean isValidKey(Object key) { 311 if (key == null) 312 return false; 313 314 // Cheaper than instanceof Enum followed by getDeclaringClass 315 Class<?> keyClass = key.getClass(); 316 return keyClass == keyType || keyClass.getSuperclass() == keyType; 317 } 318 319 // Bulk Operations 320 321 /** 322 * Copies all of the mappings from the specified map to this map. 323 * These mappings will replace any mappings that this map had for 324 * any of the keys currently in the specified map. 325 * 326 * @param m the mappings to be stored in this map 327 * @throws NullPointerException the specified map is null, or if 328 * one or more keys in the specified map are null 329 */ 330 public void putAll(Map<? extends K, ? extends V> m) { 331 if (m instanceof EnumMap) { 332 EnumMap<?, ?> em = (EnumMap<?, ?>)m; 333 if (em.keyType != keyType) { 334 if (em.isEmpty()) 335 return; 336 throw new ClassCastException(em.keyType + " != " + keyType); 337 } 338 339 for (int i = 0; i < keyUniverse.length; i++) { 340 Object emValue = em.vals[i]; 341 if (emValue != null) { 342 if (vals[i] == null) 343 size++; 344 vals[i] = emValue; 345 } 346 } 347 } else { 348 super.putAll(m); 349 } 350 } 351 352 /** 353 * Removes all mappings from this map. 354 */ 355 public void clear() { 356 Arrays.fill(vals, null); 357 size = 0; 358 } 359 360 // Views 361 362 /** 363 * This field is initialized to contain an instance of the entry set 364 * view the first time this view is requested. The view is stateless, 365 * so there's no reason to create more than one. 366 */ 367 private transient Set<Map.Entry<K,V>> entrySet; 368 369 /** 370 * Returns a {@link Set} view of the keys contained in this map. 371 * The returned set obeys the general contract outlined in 372 * {@link Map#keySet()}. The set's iterator will return the keys 373 * in their natural order (the order in which the enum constants 374 * are declared). 375 * 376 * @return a set view of the keys contained in this enum map 377 */ 378 public Set<K> keySet() { 379 Set<K> ks = keySet; 380 if (ks == null) { 381 ks = new KeySet(); 382 keySet = ks; 383 } 384 return ks; 385 } 386 387 private class KeySet extends AbstractSet<K> { 388 public Iterator<K> iterator() { 389 return new KeyIterator(); 390 } 391 public int size() { 392 return size; 393 } 394 public boolean contains(Object o) { 395 return containsKey(o); 396 } 397 public boolean remove(Object o) { 398 int oldSize = size; 399 EnumMap.this.remove(o); 400 return size != oldSize; 401 } 402 public void clear() { 403 EnumMap.this.clear(); 404 } 405 } 406 407 /** 408 * Returns a {@link Collection} view of the values contained in this map. 409 * The returned collection obeys the general contract outlined in 410 * {@link Map#values()}. The collection's iterator will return the 411 * values in the order their corresponding keys appear in map, 412 * which is their natural order (the order in which the enum constants 413 * are declared). 414 * 415 * @return a collection view of the values contained in this map 416 */ 417 public Collection<V> values() { 418 Collection<V> vs = values; 419 if (vs == null) { 420 vs = new Values(); 421 values = vs; 422 } 423 return vs; 424 } 425 426 private class Values extends AbstractCollection<V> { 427 public Iterator<V> iterator() { 428 return new ValueIterator(); 429 } 430 public int size() { 431 return size; 432 } 433 public boolean contains(Object o) { 434 return containsValue(o); 435 } 436 public boolean remove(Object o) { 437 o = maskNull(o); 438 439 for (int i = 0; i < vals.length; i++) { 440 if (o.equals(vals[i])) { 441 vals[i] = null; 442 size--; 443 return true; 444 } 445 } 446 return false; 447 } 448 public void clear() { 449 EnumMap.this.clear(); 450 } 451 } 452 453 /** 454 * Returns a {@link Set} view of the mappings contained in this map. 455 * The returned set obeys the general contract outlined in 456 * {@link Map#keySet()}. The set's iterator will return the 457 * mappings in the order their keys appear in map, which is their 458 * natural order (the order in which the enum constants are declared). 459 * 460 * @return a set view of the mappings contained in this enum map 461 */ 462 public Set<Map.Entry<K,V>> entrySet() { 463 Set<Map.Entry<K,V>> es = entrySet; 464 if (es != null) 465 return es; 466 else 467 return entrySet = new EntrySet(); 468 } 469 470 private class EntrySet extends AbstractSet<Map.Entry<K,V>> { 471 public Iterator<Map.Entry<K,V>> iterator() { 472 return new EntryIterator(); 473 } 474 475 public boolean contains(Object o) { 476 if (!(o instanceof Map.Entry)) 477 return false; 478 Map.Entry<?,?> entry = (Map.Entry<?,?>)o; 479 return containsMapping(entry.getKey(), entry.getValue()); 480 } 481 public boolean remove(Object o) { 482 if (!(o instanceof Map.Entry)) 483 return false; 484 Map.Entry<?,?> entry = (Map.Entry<?,?>)o; 485 return removeMapping(entry.getKey(), entry.getValue()); 486 } 487 public int size() { 488 return size; 489 } 490 public void clear() { 491 EnumMap.this.clear(); 492 } 493 public Object[] toArray() { 494 return fillEntryArray(new Object[size]); 495 } 496 @SuppressWarnings("unchecked") 497 public <T> T[] toArray(T[] a) { 498 int size = size(); 499 if (a.length < size) 500 a = (T[])java.lang.reflect.Array 501 .newInstance(a.getClass().getComponentType(), size); 502 if (a.length > size) 503 a[size] = null; 504 return (T[]) fillEntryArray(a); 505 } 506 private Object[] fillEntryArray(Object[] a) { 507 int j = 0; 508 for (int i = 0; i < vals.length; i++) 509 if (vals[i] != null) 510 a[j++] = new AbstractMap.SimpleEntry<>( 511 keyUniverse[i], unmaskNull(vals[i])); 512 return a; 513 } 514 } 515 516 private abstract class EnumMapIterator<T> implements Iterator<T> { 517 // Lower bound on index of next element to return 518 int index = 0; 519 520 // Index of last returned element, or -1 if none 521 int lastReturnedIndex = -1; 522 523 public boolean hasNext() { 524 while (index < vals.length && vals[index] == null) 525 index++; 526 return index != vals.length; 527 } 528 529 public void remove() { 530 checkLastReturnedIndex(); 531 532 if (vals[lastReturnedIndex] != null) { 533 vals[lastReturnedIndex] = null; 534 size--; 535 } 536 lastReturnedIndex = -1; 537 } 538 539 private void checkLastReturnedIndex() { 540 if (lastReturnedIndex < 0) 541 throw new IllegalStateException(); 542 } 543 } 544 545 private class KeyIterator extends EnumMapIterator<K> { 546 public K next() { 547 if (!hasNext()) 548 throw new NoSuchElementException(); 549 lastReturnedIndex = index++; 550 return keyUniverse[lastReturnedIndex]; 551 } 552 } 553 554 private class ValueIterator extends EnumMapIterator<V> { 555 public V next() { 556 if (!hasNext()) 557 throw new NoSuchElementException(); 558 lastReturnedIndex = index++; 559 return unmaskNull(vals[lastReturnedIndex]); 560 } 561 } 562 563 private class EntryIterator extends EnumMapIterator<Map.Entry<K,V>> { 564 private Entry lastReturnedEntry; 565 566 public Map.Entry<K,V> next() { 567 if (!hasNext()) 568 throw new NoSuchElementException(); 569 lastReturnedEntry = new Entry(index++); 570 return lastReturnedEntry; 571 } 572 573 public void remove() { 574 lastReturnedIndex = 575 ((null == lastReturnedEntry) ? -1 : lastReturnedEntry.index); 576 super.remove(); 577 lastReturnedEntry.index = lastReturnedIndex; 578 lastReturnedEntry = null; 579 } 580 581 private class Entry implements Map.Entry<K,V> { 582 private int index; 583 584 private Entry(int index) { 585 this.index = index; 586 } 587 588 public K getKey() { 589 checkIndexForEntryUse(); 590 return keyUniverse[index]; 591 } 592 593 public V getValue() { 594 checkIndexForEntryUse(); 595 return unmaskNull(vals[index]); 596 } 597 598 public V setValue(V value) { 599 checkIndexForEntryUse(); 600 V oldValue = unmaskNull(vals[index]); 601 vals[index] = maskNull(value); 602 return oldValue; 603 } 604 605 public boolean equals(Object o) { 606 if (index < 0) 607 return o == this; 608 609 if (!(o instanceof Map.Entry)) 610 return false; 611 612 Map.Entry<?,?> e = (Map.Entry<?,?>)o; 613 V ourValue = unmaskNull(vals[index]); 614 Object hisValue = e.getValue(); 615 return (e.getKey() == keyUniverse[index] && 616 (ourValue == hisValue || 617 (ourValue != null && ourValue.equals(hisValue)))); 618 } 619 620 public int hashCode() { 621 if (index < 0) 622 return super.hashCode(); 623 624 return entryHashCode(index); 625 } 626 627 public String toString() { 628 if (index < 0) 629 return super.toString(); 630 631 return keyUniverse[index] + "=" 632 + unmaskNull(vals[index]); 633 } 634 635 private void checkIndexForEntryUse() { 636 if (index < 0) 637 throw new IllegalStateException("Entry was removed"); 638 } 639 } 640 } 641 642 // Comparison and hashing 643 644 /** 645 * Compares the specified object with this map for equality. Returns 646 * {@code true} if the given object is also a map and the two maps 647 * represent the same mappings, as specified in the {@link 648 * Map#equals(Object)} contract. 649 * 650 * @param o the object to be compared for equality with this map 651 * @return {@code true} if the specified object is equal to this map 652 */ 653 public boolean equals(Object o) { 654 if (this == o) 655 return true; 656 if (o instanceof EnumMap) 657 return equals((EnumMap<?,?>)o); 658 if (!(o instanceof Map)) 659 return false; 660 661 Map<?,?> m = (Map<?,?>)o; 662 if (size != m.size()) 663 return false; 664 665 for (int i = 0; i < keyUniverse.length; i++) { 666 if (null != vals[i]) { 667 K key = keyUniverse[i]; 668 V value = unmaskNull(vals[i]); 669 if (null == value) { 670 if (!((null == m.get(key)) && m.containsKey(key))) 671 return false; 672 } else { 673 if (!value.equals(m.get(key))) 674 return false; 675 } 676 } 677 } 678 679 return true; 680 } 681 682 private boolean equals(EnumMap<?,?> em) { 683 if (em.size != size) 684 return false; 685 686 if (em.keyType != keyType) 687 return size == 0; 688 689 // Key types match, compare each value 690 for (int i = 0; i < keyUniverse.length; i++) { 691 Object ourValue = vals[i]; 692 Object hisValue = em.vals[i]; 693 if (hisValue != ourValue && 694 (hisValue == null || !hisValue.equals(ourValue))) 695 return false; 696 } 697 return true; 698 } 699 700 /** 701 * Returns the hash code value for this map. The hash code of a map is 702 * defined to be the sum of the hash codes of each entry in the map. 703 */ 704 public int hashCode() { 705 int h = 0; 706 707 for (int i = 0; i < keyUniverse.length; i++) { 708 if (null != vals[i]) { 709 h += entryHashCode(i); 710 } 711 } 712 713 return h; 714 } 715 716 private int entryHashCode(int index) { 717 return (keyUniverse[index].hashCode() ^ vals[index].hashCode()); 718 } 719 720 /** 721 * Returns a shallow copy of this enum map. The values themselves 722 * are not cloned. 723 * 724 * @return a shallow copy of this enum map 725 */ 726 @SuppressWarnings("unchecked") 727 public EnumMap<K, V> clone() { 728 EnumMap<K, V> result = null; 729 try { 730 result = (EnumMap<K, V>) super.clone(); 731 } catch(CloneNotSupportedException e) { 732 throw new AssertionError(); 733 } 734 result.vals = result.vals.clone(); 735 result.entrySet = null; 736 return result; 737 } 738 739 /** 740 * Throws an exception if e is not of the correct type for this enum set. 741 */ 742 private void typeCheck(K key) { 743 Class<?> keyClass = key.getClass(); 744 if (keyClass != keyType && keyClass.getSuperclass() != keyType) 745 throw new ClassCastException(keyClass + " != " + keyType); 746 } 747 748 /** 749 * Returns all of the values comprising K. 750 * The result is uncloned, cached, and shared by all callers. 751 */ 752 private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) { 753 return SharedSecrets.getJavaLangAccess() 754 .getEnumConstantsShared(keyType); 755 } 756 757 private static final long serialVersionUID = 458661240069192865L; 758 759 /** 760 * Save the state of the {@code EnumMap} instance to a stream (i.e., 761 * serialize it). 762 * 763 * @serialData The <i>size</i> of the enum map (the number of key-value 764 * mappings) is emitted (int), followed by the key (Object) 765 * and value (Object) for each key-value mapping represented 766 * by the enum map. 767 */ 768 private void writeObject(java.io.ObjectOutputStream s) 769 throws java.io.IOException 770 { 771 // Write out the key type and any hidden stuff 772 s.defaultWriteObject(); 773 774 // Write out size (number of Mappings) 775 s.writeInt(size); 776 777 // Write out keys and values (alternating) 778 int entriesToBeWritten = size; 779 for (int i = 0; entriesToBeWritten > 0; i++) { 780 if (null != vals[i]) { 781 s.writeObject(keyUniverse[i]); 782 s.writeObject(unmaskNull(vals[i])); 783 entriesToBeWritten--; 784 } 785 } 786 } 787 788 /** 789 * Reconstitute the {@code EnumMap} instance from a stream (i.e., 790 * deserialize it). 791 */ 792 @SuppressWarnings("unchecked") 793 private void readObject(java.io.ObjectInputStream s) 794 throws java.io.IOException, ClassNotFoundException 795 { 796 // Read in the key type and any hidden stuff 797 s.defaultReadObject(); 798 799 keyUniverse = getKeyUniverse(keyType); 800 vals = new Object[keyUniverse.length]; 801 802 // Read in size (number of Mappings) 803 int size = s.readInt(); 804 805 // Read the keys and values, and put the mappings in the HashMap 806 for (int i = 0; i < size; i++) { 807 K key = (K) s.readObject(); 808 V value = (V) s.readObject(); 809 put(key, value); 810 } 811 } 812 }