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