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