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