001/* 002 * Copyright (C) 2009 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.google.common.collect; 018 019import static com.google.common.base.Preconditions.checkArgument; 020import static com.google.common.base.Preconditions.checkElementIndex; 021import static com.google.common.base.Preconditions.checkNotNull; 022 023import com.google.common.annotations.Beta; 024import com.google.common.annotations.GwtCompatible; 025import com.google.common.annotations.GwtIncompatible; 026import com.google.common.base.Objects; 027import com.google.common.collect.Maps.IteratorBasedAbstractMap; 028import com.google.j2objc.annotations.WeakOuter; 029 030import java.io.Serializable; 031import java.lang.reflect.Array; 032import java.util.Arrays; 033import java.util.Collection; 034import java.util.Iterator; 035import java.util.Map; 036import java.util.Map.Entry; 037import java.util.Set; 038 039import javax.annotation.Nullable; 040 041/** 042 * Fixed-size {@link Table} implementation backed by a two-dimensional array. 043 * 044 * <p>The allowed row and column keys must be supplied when the table is 045 * created. The table always contains a mapping for every row key / column pair. 046 * The value corresponding to a given row and column is null unless another 047 * value is provided. 048 * 049 * <p>The table's size is constant: the product of the number of supplied row 050 * keys and the number of supplied column keys. The {@code remove} and {@code 051 * clear} methods are not supported by the table or its views. The {@link 052 * #erase} and {@link #eraseAll} methods may be used instead. 053 * 054 * <p>The ordering of the row and column keys provided when the table is 055 * constructed determines the iteration ordering across rows and columns in the 056 * table's views. None of the view iterators support {@link Iterator#remove}. 057 * If the table is modified after an iterator is created, the iterator remains 058 * valid. 059 * 060 * <p>This class requires less memory than the {@link HashBasedTable} and {@link 061 * TreeBasedTable} implementations, except when the table is sparse. 062 * 063 * <p>Null row keys or column keys are not permitted. 064 * 065 * <p>This class provides methods involving the underlying array structure, 066 * where the array indices correspond to the position of a row or column in the 067 * lists of allowed keys and values. See the {@link #at}, {@link #set}, {@link 068 * #toArray}, {@link #rowKeyList}, and {@link #columnKeyList} methods for more 069 * details. 070 * 071 * <p>Note that this implementation is not synchronized. If multiple threads 072 * access the same cell of an {@code ArrayTable} concurrently and one of the 073 * threads modifies its value, there is no guarantee that the new value will be 074 * fully visible to the other threads. To guarantee that modifications are 075 * visible, synchronize access to the table. Unlike other {@code Table} 076 * implementations, synchronization is unnecessary between a thread that writes 077 * to one cell and a thread that reads from another. 078 * 079 * <p>See the Guava User Guide article on <a href= 080 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#table"> 081 * {@code Table}</a>. 082 * 083 * @author Jared Levy 084 * @since 10.0 085 */ 086@Beta 087@GwtCompatible(emulated = true) 088public final class ArrayTable<R, C, V> extends AbstractTable<R, C, V> implements Serializable { 089 090 /** 091 * Creates an empty {@code ArrayTable}. 092 * 093 * @param rowKeys row keys that may be stored in the generated table 094 * @param columnKeys column keys that may be stored in the generated table 095 * @throws NullPointerException if any of the provided keys is null 096 * @throws IllegalArgumentException if {@code rowKeys} or {@code columnKeys} 097 * contains duplicates or is empty 098 */ 099 public static <R, C, V> ArrayTable<R, C, V> create( 100 Iterable<? extends R> rowKeys, Iterable<? extends C> columnKeys) { 101 return new ArrayTable<R, C, V>(rowKeys, columnKeys); 102 } 103 104 /* 105 * TODO(jlevy): Add factory methods taking an Enum class, instead of an 106 * iterable, to specify the allowed row keys and/or column keys. Note that 107 * custom serialization logic is needed to support different enum sizes during 108 * serialization and deserialization. 109 */ 110 111 /** 112 * Creates an {@code ArrayTable} with the mappings in the provided table. 113 * 114 * <p>If {@code table} includes a mapping with row key {@code r} and a 115 * separate mapping with column key {@code c}, the returned table contains a 116 * mapping with row key {@code r} and column key {@code c}. If that row key / 117 * column key pair in not in {@code table}, the pair maps to {@code null} in 118 * the generated table. 119 * 120 * <p>The returned table allows subsequent {@code put} calls with the row keys 121 * in {@code table.rowKeySet()} and the column keys in {@code 122 * table.columnKeySet()}. Calling {@link #put} with other keys leads to an 123 * {@code IllegalArgumentException}. 124 * 125 * <p>The ordering of {@code table.rowKeySet()} and {@code 126 * table.columnKeySet()} determines the row and column iteration ordering of 127 * the returned table. 128 * 129 * @throws NullPointerException if {@code table} has a null key 130 * @throws IllegalArgumentException if the provided table is empty 131 */ 132 public static <R, C, V> ArrayTable<R, C, V> create(Table<R, C, V> table) { 133 return (table instanceof ArrayTable<?, ?, ?>) 134 ? new ArrayTable<R, C, V>((ArrayTable<R, C, V>) table) 135 : new ArrayTable<R, C, V>(table); 136 } 137 138 private final ImmutableList<R> rowList; 139 private final ImmutableList<C> columnList; 140 141 // TODO(jlevy): Add getters returning rowKeyToIndex and columnKeyToIndex? 142 private final ImmutableMap<R, Integer> rowKeyToIndex; 143 private final ImmutableMap<C, Integer> columnKeyToIndex; 144 private final V[][] array; 145 146 private ArrayTable(Iterable<? extends R> rowKeys, Iterable<? extends C> columnKeys) { 147 this.rowList = ImmutableList.copyOf(rowKeys); 148 this.columnList = ImmutableList.copyOf(columnKeys); 149 checkArgument(!rowList.isEmpty()); 150 checkArgument(!columnList.isEmpty()); 151 152 /* 153 * TODO(jlevy): Support empty rowKeys or columnKeys? If we do, when 154 * columnKeys is empty but rowKeys isn't, the table is empty but 155 * containsRow() can return true and rowKeySet() isn't empty. 156 */ 157 rowKeyToIndex = Maps.indexMap(rowList); 158 columnKeyToIndex = Maps.indexMap(columnList); 159 160 @SuppressWarnings("unchecked") 161 V[][] tmpArray = (V[][]) new Object[rowList.size()][columnList.size()]; 162 array = tmpArray; 163 // Necessary because in GWT the arrays are initialized with "undefined" instead of null. 164 eraseAll(); 165 } 166 167 private ArrayTable(Table<R, C, V> table) { 168 this(table.rowKeySet(), table.columnKeySet()); 169 putAll(table); 170 } 171 172 private ArrayTable(ArrayTable<R, C, V> table) { 173 rowList = table.rowList; 174 columnList = table.columnList; 175 rowKeyToIndex = table.rowKeyToIndex; 176 columnKeyToIndex = table.columnKeyToIndex; 177 @SuppressWarnings("unchecked") 178 V[][] copy = (V[][]) new Object[rowList.size()][columnList.size()]; 179 array = copy; 180 // Necessary because in GWT the arrays are initialized with "undefined" instead of null. 181 eraseAll(); 182 for (int i = 0; i < rowList.size(); i++) { 183 System.arraycopy(table.array[i], 0, copy[i], 0, table.array[i].length); 184 } 185 } 186 187 private abstract static class ArrayMap<K, V> extends IteratorBasedAbstractMap<K, V> { 188 private final ImmutableMap<K, Integer> keyIndex; 189 190 private ArrayMap(ImmutableMap<K, Integer> keyIndex) { 191 this.keyIndex = keyIndex; 192 } 193 194 @Override 195 public Set<K> keySet() { 196 return keyIndex.keySet(); 197 } 198 199 K getKey(int index) { 200 return keyIndex.keySet().asList().get(index); 201 } 202 203 abstract String getKeyRole(); 204 205 @Nullable 206 abstract V getValue(int index); 207 208 @Nullable 209 abstract V setValue(int index, V newValue); 210 211 @Override 212 public int size() { 213 return keyIndex.size(); 214 } 215 216 @Override 217 public boolean isEmpty() { 218 return keyIndex.isEmpty(); 219 } 220 221 @Override 222 Iterator<Entry<K, V>> entryIterator() { 223 return new AbstractIndexedListIterator<Entry<K, V>>(size()) { 224 @Override 225 protected Entry<K, V> get(final int index) { 226 return new AbstractMapEntry<K, V>() { 227 @Override 228 public K getKey() { 229 return ArrayMap.this.getKey(index); 230 } 231 232 @Override 233 public V getValue() { 234 return ArrayMap.this.getValue(index); 235 } 236 237 @Override 238 public V setValue(V value) { 239 return ArrayMap.this.setValue(index, value); 240 } 241 }; 242 } 243 }; 244 } 245 246 // TODO(lowasser): consider an optimized values() implementation 247 248 @Override 249 public boolean containsKey(@Nullable Object key) { 250 return keyIndex.containsKey(key); 251 } 252 253 @Override 254 public V get(@Nullable Object key) { 255 Integer index = keyIndex.get(key); 256 if (index == null) { 257 return null; 258 } else { 259 return getValue(index); 260 } 261 } 262 263 @Override 264 public V put(K key, V value) { 265 Integer index = keyIndex.get(key); 266 if (index == null) { 267 throw new IllegalArgumentException( 268 getKeyRole() + " " + key + " not in " + keyIndex.keySet()); 269 } 270 return setValue(index, value); 271 } 272 273 @Override 274 public V remove(Object key) { 275 throw new UnsupportedOperationException(); 276 } 277 278 @Override 279 public void clear() { 280 throw new UnsupportedOperationException(); 281 } 282 } 283 284 /** 285 * Returns, as an immutable list, the row keys provided when the table was 286 * constructed, including those that are mapped to null values only. 287 */ 288 public ImmutableList<R> rowKeyList() { 289 return rowList; 290 } 291 292 /** 293 * Returns, as an immutable list, the column keys provided when the table was 294 * constructed, including those that are mapped to null values only. 295 */ 296 public ImmutableList<C> columnKeyList() { 297 return columnList; 298 } 299 300 /** 301 * Returns the value corresponding to the specified row and column indices. 302 * The same value is returned by {@code 303 * get(rowKeyList().get(rowIndex), columnKeyList().get(columnIndex))}, but 304 * this method runs more quickly. 305 * 306 * @param rowIndex position of the row key in {@link #rowKeyList()} 307 * @param columnIndex position of the row key in {@link #columnKeyList()} 308 * @return the value with the specified row and column 309 * @throws IndexOutOfBoundsException if either index is negative, {@code 310 * rowIndex} is greater then or equal to the number of allowed row keys, 311 * or {@code columnIndex} is greater then or equal to the number of 312 * allowed column keys 313 */ 314 public V at(int rowIndex, int columnIndex) { 315 // In GWT array access never throws IndexOutOfBoundsException. 316 checkElementIndex(rowIndex, rowList.size()); 317 checkElementIndex(columnIndex, columnList.size()); 318 return array[rowIndex][columnIndex]; 319 } 320 321 /** 322 * Associates {@code value} with the specified row and column indices. The 323 * logic {@code 324 * put(rowKeyList().get(rowIndex), columnKeyList().get(columnIndex), value)} 325 * has the same behavior, but this method runs more quickly. 326 * 327 * @param rowIndex position of the row key in {@link #rowKeyList()} 328 * @param columnIndex position of the row key in {@link #columnKeyList()} 329 * @param value value to store in the table 330 * @return the previous value with the specified row and column 331 * @throws IndexOutOfBoundsException if either index is negative, {@code 332 * rowIndex} is greater then or equal to the number of allowed row keys, 333 * or {@code columnIndex} is greater then or equal to the number of 334 * allowed column keys 335 */ 336 public V set(int rowIndex, int columnIndex, @Nullable V value) { 337 // In GWT array access never throws IndexOutOfBoundsException. 338 checkElementIndex(rowIndex, rowList.size()); 339 checkElementIndex(columnIndex, columnList.size()); 340 V oldValue = array[rowIndex][columnIndex]; 341 array[rowIndex][columnIndex] = value; 342 return oldValue; 343 } 344 345 /** 346 * Returns a two-dimensional array with the table contents. The row and column 347 * indices correspond to the positions of the row and column in the iterables 348 * provided during table construction. If the table lacks a mapping for a 349 * given row and column, the corresponding array element is null. 350 * 351 * <p>Subsequent table changes will not modify the array, and vice versa. 352 * 353 * @param valueClass class of values stored in the returned array 354 */ 355 @GwtIncompatible("reflection") 356 public V[][] toArray(Class<V> valueClass) { 357 // Can change to use varargs in JDK 1.6 if we want 358 @SuppressWarnings("unchecked") // TODO: safe? 359 V[][] copy = 360 (V[][]) Array.newInstance(valueClass, new int[] {rowList.size(), columnList.size()}); 361 for (int i = 0; i < rowList.size(); i++) { 362 System.arraycopy(array[i], 0, copy[i], 0, array[i].length); 363 } 364 return copy; 365 } 366 367 /** 368 * Not supported. Use {@link #eraseAll} instead. 369 * 370 * @throws UnsupportedOperationException always 371 * @deprecated Use {@link #eraseAll} 372 */ 373 @Override 374 @Deprecated 375 public void clear() { 376 throw new UnsupportedOperationException(); 377 } 378 379 /** 380 * Associates the value {@code null} with every pair of allowed row and column 381 * keys. 382 */ 383 public void eraseAll() { 384 for (V[] row : array) { 385 Arrays.fill(row, null); 386 } 387 } 388 389 /** 390 * Returns {@code true} if the provided keys are among the keys provided when 391 * the table was constructed. 392 */ 393 @Override 394 public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { 395 return containsRow(rowKey) && containsColumn(columnKey); 396 } 397 398 /** 399 * Returns {@code true} if the provided column key is among the column keys 400 * provided when the table was constructed. 401 */ 402 @Override 403 public boolean containsColumn(@Nullable Object columnKey) { 404 return columnKeyToIndex.containsKey(columnKey); 405 } 406 407 /** 408 * Returns {@code true} if the provided row key is among the row keys 409 * provided when the table was constructed. 410 */ 411 @Override 412 public boolean containsRow(@Nullable Object rowKey) { 413 return rowKeyToIndex.containsKey(rowKey); 414 } 415 416 @Override 417 public boolean containsValue(@Nullable Object value) { 418 for (V[] row : array) { 419 for (V element : row) { 420 if (Objects.equal(value, element)) { 421 return true; 422 } 423 } 424 } 425 return false; 426 } 427 428 @Override 429 public V get(@Nullable Object rowKey, @Nullable Object columnKey) { 430 Integer rowIndex = rowKeyToIndex.get(rowKey); 431 Integer columnIndex = columnKeyToIndex.get(columnKey); 432 return (rowIndex == null || columnIndex == null) ? null : at(rowIndex, columnIndex); 433 } 434 435 /** 436 * Always returns {@code false}. 437 */ 438 @Override 439 public boolean isEmpty() { 440 return false; 441 } 442 443 /** 444 * {@inheritDoc} 445 * 446 * @throws IllegalArgumentException if {@code rowKey} is not in {@link 447 * #rowKeySet()} or {@code columnKey} is not in {@link #columnKeySet()}. 448 */ 449 @Override 450 public V put(R rowKey, C columnKey, @Nullable V value) { 451 checkNotNull(rowKey); 452 checkNotNull(columnKey); 453 Integer rowIndex = rowKeyToIndex.get(rowKey); 454 checkArgument(rowIndex != null, "Row %s not in %s", rowKey, rowList); 455 Integer columnIndex = columnKeyToIndex.get(columnKey); 456 checkArgument(columnIndex != null, "Column %s not in %s", columnKey, columnList); 457 return set(rowIndex, columnIndex, value); 458 } 459 460 /* 461 * TODO(jlevy): Consider creating a merge() method, similar to putAll() but 462 * copying non-null values only. 463 */ 464 465 /** 466 * {@inheritDoc} 467 * 468 * <p>If {@code table} is an {@code ArrayTable}, its null values will be 469 * stored in this table, possibly replacing values that were previously 470 * non-null. 471 * 472 * @throws NullPointerException if {@code table} has a null key 473 * @throws IllegalArgumentException if any of the provided table's row keys or 474 * column keys is not in {@link #rowKeySet()} or {@link #columnKeySet()} 475 */ 476 @Override 477 public void putAll(Table<? extends R, ? extends C, ? extends V> table) { 478 super.putAll(table); 479 } 480 481 /** 482 * Not supported. Use {@link #erase} instead. 483 * 484 * @throws UnsupportedOperationException always 485 * @deprecated Use {@link #erase} 486 */ 487 @Override 488 @Deprecated 489 public V remove(Object rowKey, Object columnKey) { 490 throw new UnsupportedOperationException(); 491 } 492 493 /** 494 * Associates the value {@code null} with the specified keys, assuming both 495 * keys are valid. If either key is null or isn't among the keys provided 496 * during construction, this method has no effect. 497 * 498 * <p>This method is equivalent to {@code put(rowKey, columnKey, null)} when 499 * both provided keys are valid. 500 * 501 * @param rowKey row key of mapping to be erased 502 * @param columnKey column key of mapping to be erased 503 * @return the value previously associated with the keys, or {@code null} if 504 * no mapping existed for the keys 505 */ 506 public V erase(@Nullable Object rowKey, @Nullable Object columnKey) { 507 Integer rowIndex = rowKeyToIndex.get(rowKey); 508 Integer columnIndex = columnKeyToIndex.get(columnKey); 509 if (rowIndex == null || columnIndex == null) { 510 return null; 511 } 512 return set(rowIndex, columnIndex, null); 513 } 514 515 // TODO(jlevy): Add eraseRow and eraseColumn methods? 516 517 @Override 518 public int size() { 519 return rowList.size() * columnList.size(); 520 } 521 522 /** 523 * Returns an unmodifiable set of all row key / column key / value 524 * triplets. Changes to the table will update the returned set. 525 * 526 * <p>The returned set's iterator traverses the mappings with the first row 527 * key, the mappings with the second row key, and so on. 528 * 529 * <p>The value in the returned cells may change if the table subsequently 530 * changes. 531 * 532 * @return set of table cells consisting of row key / column key / value 533 * triplets 534 */ 535 @Override 536 public Set<Cell<R, C, V>> cellSet() { 537 return super.cellSet(); 538 } 539 540 @Override 541 Iterator<Cell<R, C, V>> cellIterator() { 542 return new AbstractIndexedListIterator<Cell<R, C, V>>(size()) { 543 @Override 544 protected Cell<R, C, V> get(final int index) { 545 return new Tables.AbstractCell<R, C, V>() { 546 final int rowIndex = index / columnList.size(); 547 final int columnIndex = index % columnList.size(); 548 549 @Override 550 public R getRowKey() { 551 return rowList.get(rowIndex); 552 } 553 554 @Override 555 public C getColumnKey() { 556 return columnList.get(columnIndex); 557 } 558 559 @Override 560 public V getValue() { 561 return at(rowIndex, columnIndex); 562 } 563 }; 564 } 565 }; 566 } 567 568 /** 569 * Returns a view of all mappings that have the given column key. If the 570 * column key isn't in {@link #columnKeySet()}, an empty immutable map is 571 * returned. 572 * 573 * <p>Otherwise, for each row key in {@link #rowKeySet()}, the returned map 574 * associates the row key with the corresponding value in the table. Changes 575 * to the returned map will update the underlying table, and vice versa. 576 * 577 * @param columnKey key of column to search for in the table 578 * @return the corresponding map from row keys to values 579 */ 580 @Override 581 public Map<R, V> column(C columnKey) { 582 checkNotNull(columnKey); 583 Integer columnIndex = columnKeyToIndex.get(columnKey); 584 return (columnIndex == null) ? ImmutableMap.<R, V>of() : new Column(columnIndex); 585 } 586 587 private class Column extends ArrayMap<R, V> { 588 final int columnIndex; 589 590 Column(int columnIndex) { 591 super(rowKeyToIndex); 592 this.columnIndex = columnIndex; 593 } 594 595 @Override 596 String getKeyRole() { 597 return "Row"; 598 } 599 600 @Override 601 V getValue(int index) { 602 return at(index, columnIndex); 603 } 604 605 @Override 606 V setValue(int index, V newValue) { 607 return set(index, columnIndex, newValue); 608 } 609 } 610 611 /** 612 * Returns an immutable set of the valid column keys, including those that 613 * are associated with null values only. 614 * 615 * @return immutable set of column keys 616 */ 617 @Override 618 public ImmutableSet<C> columnKeySet() { 619 return columnKeyToIndex.keySet(); 620 } 621 622 private transient ColumnMap columnMap; 623 624 @Override 625 public Map<C, Map<R, V>> columnMap() { 626 ColumnMap map = columnMap; 627 return (map == null) ? columnMap = new ColumnMap() : map; 628 } 629 630 @WeakOuter 631 private class ColumnMap extends ArrayMap<C, Map<R, V>> { 632 private ColumnMap() { 633 super(columnKeyToIndex); 634 } 635 636 @Override 637 String getKeyRole() { 638 return "Column"; 639 } 640 641 @Override 642 Map<R, V> getValue(int index) { 643 return new Column(index); 644 } 645 646 @Override 647 Map<R, V> setValue(int index, Map<R, V> newValue) { 648 throw new UnsupportedOperationException(); 649 } 650 651 @Override 652 public Map<R, V> put(C key, Map<R, V> value) { 653 throw new UnsupportedOperationException(); 654 } 655 } 656 657 /** 658 * Returns a view of all mappings that have the given row key. If the 659 * row key isn't in {@link #rowKeySet()}, an empty immutable map is 660 * returned. 661 * 662 * <p>Otherwise, for each column key in {@link #columnKeySet()}, the returned 663 * map associates the column key with the corresponding value in the 664 * table. Changes to the returned map will update the underlying table, and 665 * vice versa. 666 * 667 * @param rowKey key of row to search for in the table 668 * @return the corresponding map from column keys to values 669 */ 670 @Override 671 public Map<C, V> row(R rowKey) { 672 checkNotNull(rowKey); 673 Integer rowIndex = rowKeyToIndex.get(rowKey); 674 return (rowIndex == null) ? ImmutableMap.<C, V>of() : new Row(rowIndex); 675 } 676 677 private class Row extends ArrayMap<C, V> { 678 final int rowIndex; 679 680 Row(int rowIndex) { 681 super(columnKeyToIndex); 682 this.rowIndex = rowIndex; 683 } 684 685 @Override 686 String getKeyRole() { 687 return "Column"; 688 } 689 690 @Override 691 V getValue(int index) { 692 return at(rowIndex, index); 693 } 694 695 @Override 696 V setValue(int index, V newValue) { 697 return set(rowIndex, index, newValue); 698 } 699 } 700 701 /** 702 * Returns an immutable set of the valid row keys, including those that are 703 * associated with null values only. 704 * 705 * @return immutable set of row keys 706 */ 707 @Override 708 public ImmutableSet<R> rowKeySet() { 709 return rowKeyToIndex.keySet(); 710 } 711 712 private transient RowMap rowMap; 713 714 @Override 715 public Map<R, Map<C, V>> rowMap() { 716 RowMap map = rowMap; 717 return (map == null) ? rowMap = new RowMap() : map; 718 } 719 720 @WeakOuter 721 private class RowMap extends ArrayMap<R, Map<C, V>> { 722 private RowMap() { 723 super(rowKeyToIndex); 724 } 725 726 @Override 727 String getKeyRole() { 728 return "Row"; 729 } 730 731 @Override 732 Map<C, V> getValue(int index) { 733 return new Row(index); 734 } 735 736 @Override 737 Map<C, V> setValue(int index, Map<C, V> newValue) { 738 throw new UnsupportedOperationException(); 739 } 740 741 @Override 742 public Map<C, V> put(R key, Map<C, V> value) { 743 throw new UnsupportedOperationException(); 744 } 745 } 746 747 /** 748 * Returns an unmodifiable collection of all values, which may contain 749 * duplicates. Changes to the table will update the returned collection. 750 * 751 * <p>The returned collection's iterator traverses the values of the first row 752 * key, the values of the second row key, and so on. 753 * 754 * @return collection of values 755 */ 756 @Override 757 public Collection<V> values() { 758 return super.values(); 759 } 760 761 private static final long serialVersionUID = 0; 762}