001/* 002 * Copyright (C) 2008 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.checkNotNull; 021 022import com.google.common.annotations.Beta; 023import com.google.common.annotations.GwtCompatible; 024import com.google.common.base.Function; 025import com.google.common.base.Objects; 026import com.google.common.base.Supplier; 027import com.google.common.collect.Table.Cell; 028import java.io.Serializable; 029import java.util.Collection; 030import java.util.Collections; 031import java.util.Iterator; 032import java.util.Map; 033import java.util.Set; 034import java.util.SortedMap; 035import java.util.SortedSet; 036import java.util.Spliterator; 037import java.util.function.BinaryOperator; 038import java.util.stream.Collector; 039import org.checkerframework.checker.nullness.qual.Nullable; 040 041/** 042 * Provides static methods that involve a {@code Table}. 043 * 044 * <p>See the Guava User Guide article on <a href= 045 * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#tables"> {@code Tables}</a>. 046 * 047 * @author Jared Levy 048 * @author Louis Wasserman 049 * @since 7.0 050 */ 051@GwtCompatible 052public final class Tables { 053 private Tables() {} 054 055 /** 056 * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the 057 * specified supplier, whose cells are generated by applying the provided mapping functions to the 058 * input elements. Cells are inserted into the generated {@code Table} in encounter order. 059 * 060 * <p>If multiple input elements map to the same row and column, an {@code IllegalStateException} 061 * is thrown when the collection operation is performed. 062 * 063 * @since 21.0 064 */ 065 @Beta 066 public static <T, R, C, V, I extends Table<R, C, V>> Collector<T, ?, I> toTable( 067 java.util.function.Function<? super T, ? extends R> rowFunction, 068 java.util.function.Function<? super T, ? extends C> columnFunction, 069 java.util.function.Function<? super T, ? extends V> valueFunction, 070 java.util.function.Supplier<I> tableSupplier) { 071 return TableCollectors.toTable(rowFunction, columnFunction, valueFunction, tableSupplier); 072 } 073 074 /** 075 * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the 076 * specified supplier, whose cells are generated by applying the provided mapping functions to the 077 * input elements. Cells are inserted into the generated {@code Table} in encounter order. 078 * 079 * <p>If multiple input elements map to the same row and column, the specified merging function is 080 * used to combine the values. Like {@link 081 * java.util.stream.Collectors#toMap(java.util.function.Function, java.util.function.Function, 082 * BinaryOperator, java.util.function.Supplier)}, this Collector throws a {@code 083 * NullPointerException} on null values returned from {@code valueFunction}, and treats nulls 084 * returned from {@code mergeFunction} as removals of that row/column pair. 085 * 086 * @since 21.0 087 */ 088 public static <T, R, C, V, I extends Table<R, C, V>> Collector<T, ?, I> toTable( 089 java.util.function.Function<? super T, ? extends R> rowFunction, 090 java.util.function.Function<? super T, ? extends C> columnFunction, 091 java.util.function.Function<? super T, ? extends V> valueFunction, 092 BinaryOperator<V> mergeFunction, 093 java.util.function.Supplier<I> tableSupplier) { 094 return TableCollectors.toTable( 095 rowFunction, columnFunction, valueFunction, mergeFunction, tableSupplier); 096 } 097 098 /** 099 * Returns an immutable cell with the specified row key, column key, and value. 100 * 101 * <p>The returned cell is serializable. 102 * 103 * @param rowKey the row key to be associated with the returned cell 104 * @param columnKey the column key to be associated with the returned cell 105 * @param value the value to be associated with the returned cell 106 */ 107 public static <R, C, V> Cell<R, C, V> immutableCell( 108 @Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { 109 return new ImmutableCell<>(rowKey, columnKey, value); 110 } 111 112 static final class ImmutableCell<R, C, V> extends AbstractCell<R, C, V> implements Serializable { 113 private final @Nullable R rowKey; 114 private final @Nullable C columnKey; 115 private final @Nullable V value; 116 117 ImmutableCell(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { 118 this.rowKey = rowKey; 119 this.columnKey = columnKey; 120 this.value = value; 121 } 122 123 @Override 124 public R getRowKey() { 125 return rowKey; 126 } 127 128 @Override 129 public C getColumnKey() { 130 return columnKey; 131 } 132 133 @Override 134 public V getValue() { 135 return value; 136 } 137 138 private static final long serialVersionUID = 0; 139 } 140 141 abstract static class AbstractCell<R, C, V> implements Cell<R, C, V> { 142 // needed for serialization 143 AbstractCell() {} 144 145 @Override 146 public boolean equals(Object obj) { 147 if (obj == this) { 148 return true; 149 } 150 if (obj instanceof Cell) { 151 Cell<?, ?, ?> other = (Cell<?, ?, ?>) obj; 152 return Objects.equal(getRowKey(), other.getRowKey()) 153 && Objects.equal(getColumnKey(), other.getColumnKey()) 154 && Objects.equal(getValue(), other.getValue()); 155 } 156 return false; 157 } 158 159 @Override 160 public int hashCode() { 161 return Objects.hashCode(getRowKey(), getColumnKey(), getValue()); 162 } 163 164 @Override 165 public String toString() { 166 return "(" + getRowKey() + "," + getColumnKey() + ")=" + getValue(); 167 } 168 } 169 170 /** 171 * Creates a transposed view of a given table that flips its row and column keys. In other words, 172 * calling {@code get(columnKey, rowKey)} on the generated table always returns the same value as 173 * calling {@code get(rowKey, columnKey)} on the original table. Updating the original table 174 * changes the contents of the transposed table and vice versa. 175 * 176 * <p>The returned table supports update operations as long as the input table supports the 177 * analogous operation with swapped rows and columns. For example, in a {@link HashBasedTable} 178 * instance, {@code rowKeySet().iterator()} supports {@code remove()} but {@code 179 * columnKeySet().iterator()} doesn't. With a transposed {@link HashBasedTable}, it's the other 180 * way around. 181 */ 182 public static <R, C, V> Table<C, R, V> transpose(Table<R, C, V> table) { 183 return (table instanceof TransposeTable) 184 ? ((TransposeTable<R, C, V>) table).original 185 : new TransposeTable<C, R, V>(table); 186 } 187 188 private static class TransposeTable<C, R, V> extends AbstractTable<C, R, V> { 189 final Table<R, C, V> original; 190 191 TransposeTable(Table<R, C, V> original) { 192 this.original = checkNotNull(original); 193 } 194 195 @Override 196 public void clear() { 197 original.clear(); 198 } 199 200 @Override 201 public Map<C, V> column(R columnKey) { 202 return original.row(columnKey); 203 } 204 205 @Override 206 public Set<R> columnKeySet() { 207 return original.rowKeySet(); 208 } 209 210 @Override 211 public Map<R, Map<C, V>> columnMap() { 212 return original.rowMap(); 213 } 214 215 @Override 216 public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { 217 return original.contains(columnKey, rowKey); 218 } 219 220 @Override 221 public boolean containsColumn(@Nullable Object columnKey) { 222 return original.containsRow(columnKey); 223 } 224 225 @Override 226 public boolean containsRow(@Nullable Object rowKey) { 227 return original.containsColumn(rowKey); 228 } 229 230 @Override 231 public boolean containsValue(@Nullable Object value) { 232 return original.containsValue(value); 233 } 234 235 @Override 236 public V get(@Nullable Object rowKey, @Nullable Object columnKey) { 237 return original.get(columnKey, rowKey); 238 } 239 240 @Override 241 public V put(C rowKey, R columnKey, V value) { 242 return original.put(columnKey, rowKey, value); 243 } 244 245 @Override 246 public void putAll(Table<? extends C, ? extends R, ? extends V> table) { 247 original.putAll(transpose(table)); 248 } 249 250 @Override 251 public V remove(@Nullable Object rowKey, @Nullable Object columnKey) { 252 return original.remove(columnKey, rowKey); 253 } 254 255 @Override 256 public Map<R, V> row(C rowKey) { 257 return original.column(rowKey); 258 } 259 260 @Override 261 public Set<C> rowKeySet() { 262 return original.columnKeySet(); 263 } 264 265 @Override 266 public Map<C, Map<R, V>> rowMap() { 267 return original.columnMap(); 268 } 269 270 @Override 271 public int size() { 272 return original.size(); 273 } 274 275 @Override 276 public Collection<V> values() { 277 return original.values(); 278 } 279 280 // Will cast TRANSPOSE_CELL to a type that always succeeds 281 private static final Function<Cell<?, ?, ?>, Cell<?, ?, ?>> TRANSPOSE_CELL = 282 new Function<Cell<?, ?, ?>, Cell<?, ?, ?>>() { 283 @Override 284 public Cell<?, ?, ?> apply(Cell<?, ?, ?> cell) { 285 return immutableCell(cell.getColumnKey(), cell.getRowKey(), cell.getValue()); 286 } 287 }; 288 289 @SuppressWarnings("unchecked") 290 @Override 291 Iterator<Cell<C, R, V>> cellIterator() { 292 return Iterators.transform(original.cellSet().iterator(), (Function) TRANSPOSE_CELL); 293 } 294 295 @SuppressWarnings("unchecked") 296 @Override 297 Spliterator<Cell<C, R, V>> cellSpliterator() { 298 return CollectSpliterators.map(original.cellSet().spliterator(), (Function) TRANSPOSE_CELL); 299 } 300 } 301 302 /** 303 * Creates a table that uses the specified backing map and factory. It can generate a table based 304 * on arbitrary {@link Map} classes. 305 * 306 * <p>The {@code factory}-generated and {@code backingMap} classes determine the table iteration 307 * order. However, the table's {@code row()} method returns instances of a different class than 308 * {@code factory.get()} does. 309 * 310 * <p>Call this method only when the simpler factory methods in classes like {@link 311 * HashBasedTable} and {@link TreeBasedTable} won't suffice. 312 * 313 * <p>The views returned by the {@code Table} methods {@link Table#column}, {@link 314 * Table#columnKeySet}, and {@link Table#columnMap} have iterators that don't support {@code 315 * remove()}. Otherwise, all optional operations are supported. Null row keys, columns keys, and 316 * values are not supported. 317 * 318 * <p>Lookups by row key are often faster than lookups by column key, because the data is stored 319 * in a {@code Map<R, Map<C, V>>}. A method call like {@code column(columnKey).get(rowKey)} still 320 * runs quickly, since the row key is provided. However, {@code column(columnKey).size()} takes 321 * longer, since an iteration across all row keys occurs. 322 * 323 * <p>Note that this implementation is not synchronized. If multiple threads access this table 324 * concurrently and one of the threads modifies the table, it must be synchronized externally. 325 * 326 * <p>The table is serializable if {@code backingMap}, {@code factory}, the maps generated by 327 * {@code factory}, and the table contents are all serializable. 328 * 329 * <p>Note: the table assumes complete ownership over of {@code backingMap} and the maps returned 330 * by {@code factory}. Those objects should not be manually updated and they should not use soft, 331 * weak, or phantom references. 332 * 333 * @param backingMap place to store the mapping from each row key to its corresponding column key 334 * / value map 335 * @param factory supplier of new, empty maps that will each hold all column key / value mappings 336 * for a given row key 337 * @throws IllegalArgumentException if {@code backingMap} is not empty 338 * @since 10.0 339 */ 340 @Beta 341 public static <R, C, V> Table<R, C, V> newCustomTable( 342 Map<R, Map<C, V>> backingMap, Supplier<? extends Map<C, V>> factory) { 343 checkArgument(backingMap.isEmpty()); 344 checkNotNull(factory); 345 // TODO(jlevy): Wrap factory to validate that the supplied maps are empty? 346 return new StandardTable<>(backingMap, factory); 347 } 348 349 /** 350 * Returns a view of a table where each value is transformed by a function. All other properties 351 * of the table, such as iteration order, are left intact. 352 * 353 * <p>Changes in the underlying table are reflected in this view. Conversely, this view supports 354 * removal operations, and these are reflected in the underlying table. 355 * 356 * <p>It's acceptable for the underlying table to contain null keys, and even null values provided 357 * that the function is capable of accepting null input. The transformed table might contain null 358 * values, if the function sometimes gives a null result. 359 * 360 * <p>The returned table is not thread-safe or serializable, even if the underlying table is. 361 * 362 * <p>The function is applied lazily, invoked when needed. This is necessary for the returned 363 * table to be a view, but it means that the function will be applied many times for bulk 364 * operations like {@link Table#containsValue} and {@code Table.toString()}. For this to perform 365 * well, {@code function} should be fast. To avoid lazy evaluation when the returned table doesn't 366 * need to be a view, copy the returned table into a new table of your choosing. 367 * 368 * @since 10.0 369 */ 370 @Beta 371 public static <R, C, V1, V2> Table<R, C, V2> transformValues( 372 Table<R, C, V1> fromTable, Function<? super V1, V2> function) { 373 return new TransformedTable<>(fromTable, function); 374 } 375 376 private static class TransformedTable<R, C, V1, V2> extends AbstractTable<R, C, V2> { 377 final Table<R, C, V1> fromTable; 378 final Function<? super V1, V2> function; 379 380 TransformedTable(Table<R, C, V1> fromTable, Function<? super V1, V2> function) { 381 this.fromTable = checkNotNull(fromTable); 382 this.function = checkNotNull(function); 383 } 384 385 @Override 386 public boolean contains(Object rowKey, Object columnKey) { 387 return fromTable.contains(rowKey, columnKey); 388 } 389 390 @Override 391 public V2 get(Object rowKey, Object columnKey) { 392 // The function is passed a null input only when the table contains a null 393 // value. 394 return contains(rowKey, columnKey) ? function.apply(fromTable.get(rowKey, columnKey)) : null; 395 } 396 397 @Override 398 public int size() { 399 return fromTable.size(); 400 } 401 402 @Override 403 public void clear() { 404 fromTable.clear(); 405 } 406 407 @Override 408 public V2 put(R rowKey, C columnKey, V2 value) { 409 throw new UnsupportedOperationException(); 410 } 411 412 @Override 413 public void putAll(Table<? extends R, ? extends C, ? extends V2> table) { 414 throw new UnsupportedOperationException(); 415 } 416 417 @Override 418 public V2 remove(Object rowKey, Object columnKey) { 419 return contains(rowKey, columnKey) 420 ? function.apply(fromTable.remove(rowKey, columnKey)) 421 : null; 422 } 423 424 @Override 425 public Map<C, V2> row(R rowKey) { 426 return Maps.transformValues(fromTable.row(rowKey), function); 427 } 428 429 @Override 430 public Map<R, V2> column(C columnKey) { 431 return Maps.transformValues(fromTable.column(columnKey), function); 432 } 433 434 Function<Cell<R, C, V1>, Cell<R, C, V2>> cellFunction() { 435 return new Function<Cell<R, C, V1>, Cell<R, C, V2>>() { 436 @Override 437 public Cell<R, C, V2> apply(Cell<R, C, V1> cell) { 438 return immutableCell( 439 cell.getRowKey(), cell.getColumnKey(), function.apply(cell.getValue())); 440 } 441 }; 442 } 443 444 @Override 445 Iterator<Cell<R, C, V2>> cellIterator() { 446 return Iterators.transform(fromTable.cellSet().iterator(), cellFunction()); 447 } 448 449 @Override 450 Spliterator<Cell<R, C, V2>> cellSpliterator() { 451 return CollectSpliterators.map(fromTable.cellSet().spliterator(), cellFunction()); 452 } 453 454 @Override 455 public Set<R> rowKeySet() { 456 return fromTable.rowKeySet(); 457 } 458 459 @Override 460 public Set<C> columnKeySet() { 461 return fromTable.columnKeySet(); 462 } 463 464 @Override 465 Collection<V2> createValues() { 466 return Collections2.transform(fromTable.values(), function); 467 } 468 469 @Override 470 public Map<R, Map<C, V2>> rowMap() { 471 Function<Map<C, V1>, Map<C, V2>> rowFunction = 472 new Function<Map<C, V1>, Map<C, V2>>() { 473 @Override 474 public Map<C, V2> apply(Map<C, V1> row) { 475 return Maps.transformValues(row, function); 476 } 477 }; 478 return Maps.transformValues(fromTable.rowMap(), rowFunction); 479 } 480 481 @Override 482 public Map<C, Map<R, V2>> columnMap() { 483 Function<Map<R, V1>, Map<R, V2>> columnFunction = 484 new Function<Map<R, V1>, Map<R, V2>>() { 485 @Override 486 public Map<R, V2> apply(Map<R, V1> column) { 487 return Maps.transformValues(column, function); 488 } 489 }; 490 return Maps.transformValues(fromTable.columnMap(), columnFunction); 491 } 492 } 493 494 /** 495 * Returns an unmodifiable view of the specified table. This method allows modules to provide 496 * users with "read-only" access to internal tables. Query operations on the returned table "read 497 * through" to the specified table, and attempts to modify the returned table, whether direct or 498 * via its collection views, result in an {@code UnsupportedOperationException}. 499 * 500 * <p>The returned table will be serializable if the specified table is serializable. 501 * 502 * <p>Consider using an {@link ImmutableTable}, which is guaranteed never to change. 503 * 504 * @since 11.0 505 */ 506 public static <R, C, V> Table<R, C, V> unmodifiableTable( 507 Table<? extends R, ? extends C, ? extends V> table) { 508 return new UnmodifiableTable<>(table); 509 } 510 511 private static class UnmodifiableTable<R, C, V> extends ForwardingTable<R, C, V> 512 implements Serializable { 513 final Table<? extends R, ? extends C, ? extends V> delegate; 514 515 UnmodifiableTable(Table<? extends R, ? extends C, ? extends V> delegate) { 516 this.delegate = checkNotNull(delegate); 517 } 518 519 @SuppressWarnings("unchecked") // safe, covariant cast 520 @Override 521 protected Table<R, C, V> delegate() { 522 return (Table<R, C, V>) delegate; 523 } 524 525 @Override 526 public Set<Cell<R, C, V>> cellSet() { 527 return Collections.unmodifiableSet(super.cellSet()); 528 } 529 530 @Override 531 public void clear() { 532 throw new UnsupportedOperationException(); 533 } 534 535 @Override 536 public Map<R, V> column(@Nullable C columnKey) { 537 return Collections.unmodifiableMap(super.column(columnKey)); 538 } 539 540 @Override 541 public Set<C> columnKeySet() { 542 return Collections.unmodifiableSet(super.columnKeySet()); 543 } 544 545 @Override 546 public Map<C, Map<R, V>> columnMap() { 547 Function<Map<R, V>, Map<R, V>> wrapper = unmodifiableWrapper(); 548 return Collections.unmodifiableMap(Maps.transformValues(super.columnMap(), wrapper)); 549 } 550 551 @Override 552 public V put(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { 553 throw new UnsupportedOperationException(); 554 } 555 556 @Override 557 public void putAll(Table<? extends R, ? extends C, ? extends V> table) { 558 throw new UnsupportedOperationException(); 559 } 560 561 @Override 562 public V remove(@Nullable Object rowKey, @Nullable Object columnKey) { 563 throw new UnsupportedOperationException(); 564 } 565 566 @Override 567 public Map<C, V> row(@Nullable R rowKey) { 568 return Collections.unmodifiableMap(super.row(rowKey)); 569 } 570 571 @Override 572 public Set<R> rowKeySet() { 573 return Collections.unmodifiableSet(super.rowKeySet()); 574 } 575 576 @Override 577 public Map<R, Map<C, V>> rowMap() { 578 Function<Map<C, V>, Map<C, V>> wrapper = unmodifiableWrapper(); 579 return Collections.unmodifiableMap(Maps.transformValues(super.rowMap(), wrapper)); 580 } 581 582 @Override 583 public Collection<V> values() { 584 return Collections.unmodifiableCollection(super.values()); 585 } 586 587 private static final long serialVersionUID = 0; 588 } 589 590 /** 591 * Returns an unmodifiable view of the specified row-sorted table. This method allows modules to 592 * provide users with "read-only" access to internal tables. Query operations on the returned 593 * table "read through" to the specified table, and attempts to modify the returned table, whether 594 * direct or via its collection views, result in an {@code UnsupportedOperationException}. 595 * 596 * <p>The returned table will be serializable if the specified table is serializable. 597 * 598 * @param table the row-sorted table for which an unmodifiable view is to be returned 599 * @return an unmodifiable view of the specified table 600 * @since 11.0 601 */ 602 @Beta 603 public static <R, C, V> RowSortedTable<R, C, V> unmodifiableRowSortedTable( 604 RowSortedTable<R, ? extends C, ? extends V> table) { 605 /* 606 * It's not ? extends R, because it's technically not covariant in R. Specifically, 607 * table.rowMap().comparator() could return a comparator that only works for the ? extends R. 608 * Collections.unmodifiableSortedMap makes the same distinction. 609 */ 610 return new UnmodifiableRowSortedMap<>(table); 611 } 612 613 static final class UnmodifiableRowSortedMap<R, C, V> extends UnmodifiableTable<R, C, V> 614 implements RowSortedTable<R, C, V> { 615 616 public UnmodifiableRowSortedMap(RowSortedTable<R, ? extends C, ? extends V> delegate) { 617 super(delegate); 618 } 619 620 @Override 621 protected RowSortedTable<R, C, V> delegate() { 622 return (RowSortedTable<R, C, V>) super.delegate(); 623 } 624 625 @Override 626 public SortedMap<R, Map<C, V>> rowMap() { 627 Function<Map<C, V>, Map<C, V>> wrapper = unmodifiableWrapper(); 628 return Collections.unmodifiableSortedMap(Maps.transformValues(delegate().rowMap(), wrapper)); 629 } 630 631 @Override 632 public SortedSet<R> rowKeySet() { 633 return Collections.unmodifiableSortedSet(delegate().rowKeySet()); 634 } 635 636 private static final long serialVersionUID = 0; 637 } 638 639 @SuppressWarnings("unchecked") 640 private static <K, V> Function<Map<K, V>, Map<K, V>> unmodifiableWrapper() { 641 return (Function) UNMODIFIABLE_WRAPPER; 642 } 643 644 private static final Function<? extends Map<?, ?>, ? extends Map<?, ?>> UNMODIFIABLE_WRAPPER = 645 new Function<Map<Object, Object>, Map<Object, Object>>() { 646 @Override 647 public Map<Object, Object> apply(Map<Object, Object> input) { 648 return Collections.unmodifiableMap(input); 649 } 650 }; 651 652 /** 653 * Returns a synchronized (thread-safe) table backed by the specified table. In order to guarantee 654 * serial access, it is critical that <b>all</b> access to the backing table is accomplished 655 * through the returned table. 656 * 657 * <p>It is imperative that the user manually synchronize on the returned table when accessing any 658 * of its collection views: 659 * 660 * <pre>{@code 661 * Table<R, C, V> table = Tables.synchronizedTable(HashBasedTable.<R, C, V>create()); 662 * ... 663 * Map<C, V> row = table.row(rowKey); // Needn't be in synchronized block 664 * ... 665 * synchronized (table) { // Synchronizing on table, not row! 666 * Iterator<Entry<C, V>> i = row.entrySet().iterator(); // Must be in synchronized block 667 * while (i.hasNext()) { 668 * foo(i.next()); 669 * } 670 * } 671 * }</pre> 672 * 673 * <p>Failure to follow this advice may result in non-deterministic behavior. 674 * 675 * <p>The returned table will be serializable if the specified table is serializable. 676 * 677 * @param table the table to be wrapped in a synchronized view 678 * @return a synchronized view of the specified table 679 * @since 22.0 680 */ 681 public static <R, C, V> Table<R, C, V> synchronizedTable(Table<R, C, V> table) { 682 return Synchronized.table(table, null); 683 } 684 685 static boolean equalsImpl(Table<?, ?, ?> table, @Nullable Object obj) { 686 if (obj == table) { 687 return true; 688 } else if (obj instanceof Table) { 689 Table<?, ?, ?> that = (Table<?, ?, ?>) obj; 690 return table.cellSet().equals(that.cellSet()); 691 } else { 692 return false; 693 } 694 } 695}