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