/*
 * Copyright (C) 2008 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.collect;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtCompatible;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Supplier;
import com.google.common.collect.Table.Cell;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.Spliterator;
import java.util.function.BinaryOperator;
import java.util.stream.Collector;
import org.checkerframework.checker.nullness.qual.Nullable;

Provides static methods that involve a Table.

See the Guava User Guide article on Tables.

Author:Jared Levy, Louis Wasserman
Since:7.0
/** * Provides static methods that involve a {@code Table}. * * <p>See the Guava User Guide article on <a href= * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#tables"> {@code Tables}</a>. * * @author Jared Levy * @author Louis Wasserman * @since 7.0 */
@GwtCompatible public final class Tables { private Tables() {}
Returns a Collector that accumulates elements into a Table created using the specified supplier, whose cells are generated by applying the provided mapping functions to the input elements. Cells are inserted into the generated Table in encounter order.

If multiple input elements map to the same row and column, an IllegalStateException is thrown when the collection operation is performed.

Since:21.0
/** * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the * specified supplier, whose cells are generated by applying the provided mapping functions to the * input elements. Cells are inserted into the generated {@code Table} in encounter order. * * <p>If multiple input elements map to the same row and column, an {@code IllegalStateException} * is thrown when the collection operation is performed. * * @since 21.0 */
@Beta public static <T, R, C, V, I extends Table<R, C, V>> Collector<T, ?, I> toTable( java.util.function.Function<? super T, ? extends R> rowFunction, java.util.function.Function<? super T, ? extends C> columnFunction, java.util.function.Function<? super T, ? extends V> valueFunction, java.util.function.Supplier<I> tableSupplier) { return toTable( rowFunction, columnFunction, valueFunction, (v1, v2) -> { throw new IllegalStateException("Conflicting values " + v1 + " and " + v2); }, tableSupplier); }
Returns a Collector that accumulates elements into a Table created using the specified supplier, whose cells are generated by applying the provided mapping functions to the input elements. Cells are inserted into the generated Table in encounter order.

If multiple input elements map to the same row and column, the specified merging function is used to combine the values. Like Collectors.toMap(Function<? super Object,? extends Object>, Function<? super Object,? extends Object>, BinaryOperator<Object>, Supplier<Map<Object,Object>>), this Collector throws a NullPointerException on null values returned from valueFunction, and treats nulls returned from mergeFunction as removals of that row/column pair.

Since:21.0
/** * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the * specified supplier, whose cells are generated by applying the provided mapping functions to the * input elements. Cells are inserted into the generated {@code Table} in encounter order. * * <p>If multiple input elements map to the same row and column, the specified merging function is * used to combine the values. Like {@link * java.util.stream.Collectors#toMap(java.util.function.Function, java.util.function.Function, * BinaryOperator, java.util.function.Supplier)}, this Collector throws a {@code * NullPointerException} on null values returned from {@code valueFunction}, and treats nulls * returned from {@code mergeFunction} as removals of that row/column pair. * * @since 21.0 */
public static <T, R, C, V, I extends Table<R, C, V>> Collector<T, ?, I> toTable( java.util.function.Function<? super T, ? extends R> rowFunction, java.util.function.Function<? super T, ? extends C> columnFunction, java.util.function.Function<? super T, ? extends V> valueFunction, BinaryOperator<V> mergeFunction, java.util.function.Supplier<I> tableSupplier) { checkNotNull(rowFunction); checkNotNull(columnFunction); checkNotNull(valueFunction); checkNotNull(mergeFunction); checkNotNull(tableSupplier); return Collector.of( tableSupplier, (table, input) -> merge( table, rowFunction.apply(input), columnFunction.apply(input), valueFunction.apply(input), mergeFunction), (table1, table2) -> { for (Table.Cell<R, C, V> cell2 : table2.cellSet()) { merge(table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), mergeFunction); } return table1; }); } private static <R, C, V> void merge( Table<R, C, V> table, R row, C column, V value, BinaryOperator<V> mergeFunction) { checkNotNull(value); V oldValue = table.get(row, column); if (oldValue == null) { table.put(row, column, value); } else { V newValue = mergeFunction.apply(oldValue, value); if (newValue == null) { table.remove(row, column); } else { table.put(row, column, newValue); } } }
Returns an immutable cell with the specified row key, column key, and value.

The returned cell is serializable.

Params:
  • rowKey – the row key to be associated with the returned cell
  • columnKey – the column key to be associated with the returned cell
  • value – the value to be associated with the returned cell
/** * Returns an immutable cell with the specified row key, column key, and value. * * <p>The returned cell is serializable. * * @param rowKey the row key to be associated with the returned cell * @param columnKey the column key to be associated with the returned cell * @param value the value to be associated with the returned cell */
public static <R, C, V> Cell<R, C, V> immutableCell( @Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { return new ImmutableCell<>(rowKey, columnKey, value); } static final class ImmutableCell<R, C, V> extends AbstractCell<R, C, V> implements Serializable { private final @Nullable R rowKey; private final @Nullable C columnKey; private final @Nullable V value; ImmutableCell(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { this.rowKey = rowKey; this.columnKey = columnKey; this.value = value; } @Override public R getRowKey() { return rowKey; } @Override public C getColumnKey() { return columnKey; } @Override public V getValue() { return value; } private static final long serialVersionUID = 0; } abstract static class AbstractCell<R, C, V> implements Cell<R, C, V> { // needed for serialization AbstractCell() {} @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Cell) { Cell<?, ?, ?> other = (Cell<?, ?, ?>) obj; return Objects.equal(getRowKey(), other.getRowKey()) && Objects.equal(getColumnKey(), other.getColumnKey()) && Objects.equal(getValue(), other.getValue()); } return false; } @Override public int hashCode() { return Objects.hashCode(getRowKey(), getColumnKey(), getValue()); } @Override public String toString() { return "(" + getRowKey() + "," + getColumnKey() + ")=" + getValue(); } }
Creates a transposed view of a given table that flips its row and column keys. In other words, calling get(columnKey, rowKey) on the generated table always returns the same value as calling get(rowKey, columnKey) on the original table. Updating the original table changes the contents of the transposed table and vice versa.

The returned table supports update operations as long as the input table supports the analogous operation with swapped rows and columns. For example, in a HashBasedTable instance, rowKeySet().iterator() supports remove() but columnKeySet().iterator() doesn't. With a transposed HashBasedTable, it's the other way around.

/** * Creates a transposed view of a given table that flips its row and column keys. In other words, * calling {@code get(columnKey, rowKey)} on the generated table always returns the same value as * calling {@code get(rowKey, columnKey)} on the original table. Updating the original table * changes the contents of the transposed table and vice versa. * * <p>The returned table supports update operations as long as the input table supports the * analogous operation with swapped rows and columns. For example, in a {@link HashBasedTable} * instance, {@code rowKeySet().iterator()} supports {@code remove()} but {@code * columnKeySet().iterator()} doesn't. With a transposed {@link HashBasedTable}, it's the other * way around. */
public static <R, C, V> Table<C, R, V> transpose(Table<R, C, V> table) { return (table instanceof TransposeTable) ? ((TransposeTable<R, C, V>) table).original : new TransposeTable<C, R, V>(table); } private static class TransposeTable<C, R, V> extends AbstractTable<C, R, V> { final Table<R, C, V> original; TransposeTable(Table<R, C, V> original) { this.original = checkNotNull(original); } @Override public void clear() { original.clear(); } @Override public Map<C, V> column(R columnKey) { return original.row(columnKey); } @Override public Set<R> columnKeySet() { return original.rowKeySet(); } @Override public Map<R, Map<C, V>> columnMap() { return original.rowMap(); } @Override public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { return original.contains(columnKey, rowKey); } @Override public boolean containsColumn(@Nullable Object columnKey) { return original.containsRow(columnKey); } @Override public boolean containsRow(@Nullable Object rowKey) { return original.containsColumn(rowKey); } @Override public boolean containsValue(@Nullable Object value) { return original.containsValue(value); } @Override public V get(@Nullable Object rowKey, @Nullable Object columnKey) { return original.get(columnKey, rowKey); } @Override public V put(C rowKey, R columnKey, V value) { return original.put(columnKey, rowKey, value); } @Override public void putAll(Table<? extends C, ? extends R, ? extends V> table) { original.putAll(transpose(table)); } @Override public V remove(@Nullable Object rowKey, @Nullable Object columnKey) { return original.remove(columnKey, rowKey); } @Override public Map<R, V> row(C rowKey) { return original.column(rowKey); } @Override public Set<C> rowKeySet() { return original.columnKeySet(); } @Override public Map<C, Map<R, V>> rowMap() { return original.columnMap(); } @Override public int size() { return original.size(); } @Override public Collection<V> values() { return original.values(); } // Will cast TRANSPOSE_CELL to a type that always succeeds private static final Function<Cell<?, ?, ?>, Cell<?, ?, ?>> TRANSPOSE_CELL = new Function<Cell<?, ?, ?>, Cell<?, ?, ?>>() { @Override public Cell<?, ?, ?> apply(Cell<?, ?, ?> cell) { return immutableCell(cell.getColumnKey(), cell.getRowKey(), cell.getValue()); } }; @SuppressWarnings("unchecked") @Override Iterator<Cell<C, R, V>> cellIterator() { return Iterators.transform(original.cellSet().iterator(), (Function) TRANSPOSE_CELL); } @SuppressWarnings("unchecked") @Override Spliterator<Cell<C, R, V>> cellSpliterator() { return CollectSpliterators.map(original.cellSet().spliterator(), (Function) TRANSPOSE_CELL); } }
Creates a table that uses the specified backing map and factory. It can generate a table based on arbitrary Map classes.

The factory-generated and backingMap classes determine the table iteration order. However, the table's row() method returns instances of a different class than factory.get() does.

Call this method only when the simpler factory methods in classes like HashBasedTable and TreeBasedTable won't suffice.

The views returned by the Table methods Table.column, Table.columnKeySet, and Table.columnMap have iterators that don't support remove(). Otherwise, all optional operations are supported. Null row keys, columns keys, and values are not supported.

Lookups by row key are often faster than lookups by column key, because the data is stored in a Map<R, Map<C, V>>. A method call like column(columnKey).get(rowKey) still runs quickly, since the row key is provided. However, column(columnKey).size() takes longer, since an iteration across all row keys occurs.

Note that this implementation is not synchronized. If multiple threads access this table concurrently and one of the threads modifies the table, it must be synchronized externally.

The table is serializable if backingMap, factory, the maps generated by factory, and the table contents are all serializable.

Note: the table assumes complete ownership over of backingMap and the maps returned by factory. Those objects should not be manually updated and they should not use soft, weak, or phantom references.

Params:
  • backingMap – place to store the mapping from each row key to its corresponding column key / value map
  • factory – supplier of new, empty maps that will each hold all column key / value mappings for a given row key
Throws:
Since:10.0
/** * Creates a table that uses the specified backing map and factory. It can generate a table based * on arbitrary {@link Map} classes. * * <p>The {@code factory}-generated and {@code backingMap} classes determine the table iteration * order. However, the table's {@code row()} method returns instances of a different class than * {@code factory.get()} does. * * <p>Call this method only when the simpler factory methods in classes like {@link * HashBasedTable} and {@link TreeBasedTable} won't suffice. * * <p>The views returned by the {@code Table} methods {@link Table#column}, {@link * Table#columnKeySet}, and {@link Table#columnMap} have iterators that don't support {@code * remove()}. Otherwise, all optional operations are supported. Null row keys, columns keys, and * values are not supported. * * <p>Lookups by row key are often faster than lookups by column key, because the data is stored * in a {@code Map<R, Map<C, V>>}. A method call like {@code column(columnKey).get(rowKey)} still * runs quickly, since the row key is provided. However, {@code column(columnKey).size()} takes * longer, since an iteration across all row keys occurs. * * <p>Note that this implementation is not synchronized. If multiple threads access this table * concurrently and one of the threads modifies the table, it must be synchronized externally. * * <p>The table is serializable if {@code backingMap}, {@code factory}, the maps generated by * {@code factory}, and the table contents are all serializable. * * <p>Note: the table assumes complete ownership over of {@code backingMap} and the maps returned * by {@code factory}. Those objects should not be manually updated and they should not use soft, * weak, or phantom references. * * @param backingMap place to store the mapping from each row key to its corresponding column key * / value map * @param factory supplier of new, empty maps that will each hold all column key / value mappings * for a given row key * @throws IllegalArgumentException if {@code backingMap} is not empty * @since 10.0 */
@Beta public static <R, C, V> Table<R, C, V> newCustomTable( Map<R, Map<C, V>> backingMap, Supplier<? extends Map<C, V>> factory) { checkArgument(backingMap.isEmpty()); checkNotNull(factory); // TODO(jlevy): Wrap factory to validate that the supplied maps are empty? return new StandardTable<>(backingMap, factory); }
Returns a view of a table where each value is transformed by a function. All other properties of the table, such as iteration order, are left intact.

Changes in the underlying table are reflected in this view. Conversely, this view supports removal operations, and these are reflected in the underlying table.

It's acceptable for the underlying table to contain null keys, and even null values provided that the function is capable of accepting null input. The transformed table might contain null values, if the function sometimes gives a null result.

The returned table is not thread-safe or serializable, even if the underlying table is.

The function is applied lazily, invoked when needed. This is necessary for the returned table to be a view, but it means that the function will be applied many times for bulk operations like Table.containsValue and Table.toString(). For this to perform well, function should be fast. To avoid lazy evaluation when the returned table doesn't need to be a view, copy the returned table into a new table of your choosing.

Since:10.0
/** * Returns a view of a table where each value is transformed by a function. All other properties * of the table, such as iteration order, are left intact. * * <p>Changes in the underlying table are reflected in this view. Conversely, this view supports * removal operations, and these are reflected in the underlying table. * * <p>It's acceptable for the underlying table to contain null keys, and even null values provided * that the function is capable of accepting null input. The transformed table might contain null * values, if the function sometimes gives a null result. * * <p>The returned table is not thread-safe or serializable, even if the underlying table is. * * <p>The function is applied lazily, invoked when needed. This is necessary for the returned * table to be a view, but it means that the function will be applied many times for bulk * operations like {@link Table#containsValue} and {@code Table.toString()}. For this to perform * well, {@code function} should be fast. To avoid lazy evaluation when the returned table doesn't * need to be a view, copy the returned table into a new table of your choosing. * * @since 10.0 */
@Beta public static <R, C, V1, V2> Table<R, C, V2> transformValues( Table<R, C, V1> fromTable, Function<? super V1, V2> function) { return new TransformedTable<>(fromTable, function); } private static class TransformedTable<R, C, V1, V2> extends AbstractTable<R, C, V2> { final Table<R, C, V1> fromTable; final Function<? super V1, V2> function; TransformedTable(Table<R, C, V1> fromTable, Function<? super V1, V2> function) { this.fromTable = checkNotNull(fromTable); this.function = checkNotNull(function); } @Override public boolean contains(Object rowKey, Object columnKey) { return fromTable.contains(rowKey, columnKey); } @Override public V2 get(Object rowKey, Object columnKey) { // The function is passed a null input only when the table contains a null // value. return contains(rowKey, columnKey) ? function.apply(fromTable.get(rowKey, columnKey)) : null; } @Override public int size() { return fromTable.size(); } @Override public void clear() { fromTable.clear(); } @Override public V2 put(R rowKey, C columnKey, V2 value) { throw new UnsupportedOperationException(); } @Override public void putAll(Table<? extends R, ? extends C, ? extends V2> table) { throw new UnsupportedOperationException(); } @Override public V2 remove(Object rowKey, Object columnKey) { return contains(rowKey, columnKey) ? function.apply(fromTable.remove(rowKey, columnKey)) : null; } @Override public Map<C, V2> row(R rowKey) { return Maps.transformValues(fromTable.row(rowKey), function); } @Override public Map<R, V2> column(C columnKey) { return Maps.transformValues(fromTable.column(columnKey), function); } Function<Cell<R, C, V1>, Cell<R, C, V2>> cellFunction() { return new Function<Cell<R, C, V1>, Cell<R, C, V2>>() { @Override public Cell<R, C, V2> apply(Cell<R, C, V1> cell) { return immutableCell( cell.getRowKey(), cell.getColumnKey(), function.apply(cell.getValue())); } }; } @Override Iterator<Cell<R, C, V2>> cellIterator() { return Iterators.transform(fromTable.cellSet().iterator(), cellFunction()); } @Override Spliterator<Cell<R, C, V2>> cellSpliterator() { return CollectSpliterators.map(fromTable.cellSet().spliterator(), cellFunction()); } @Override public Set<R> rowKeySet() { return fromTable.rowKeySet(); } @Override public Set<C> columnKeySet() { return fromTable.columnKeySet(); } @Override Collection<V2> createValues() { return Collections2.transform(fromTable.values(), function); } @Override public Map<R, Map<C, V2>> rowMap() { Function<Map<C, V1>, Map<C, V2>> rowFunction = new Function<Map<C, V1>, Map<C, V2>>() { @Override public Map<C, V2> apply(Map<C, V1> row) { return Maps.transformValues(row, function); } }; return Maps.transformValues(fromTable.rowMap(), rowFunction); } @Override public Map<C, Map<R, V2>> columnMap() { Function<Map<R, V1>, Map<R, V2>> columnFunction = new Function<Map<R, V1>, Map<R, V2>>() { @Override public Map<R, V2> apply(Map<R, V1> column) { return Maps.transformValues(column, function); } }; return Maps.transformValues(fromTable.columnMap(), columnFunction); } }
Returns an unmodifiable view of the specified table. This method allows modules to provide users with "read-only" access to internal tables. Query operations on the returned table "read through" to the specified table, and attempts to modify the returned table, whether direct or via its collection views, result in an UnsupportedOperationException.

The returned table will be serializable if the specified table is serializable.

Consider using an ImmutableTable, which is guaranteed never to change.

Since:11.0
/** * Returns an unmodifiable view of the specified table. This method allows modules to provide * users with "read-only" access to internal tables. Query operations on the returned table "read * through" to the specified table, and attempts to modify the returned table, whether direct or * via its collection views, result in an {@code UnsupportedOperationException}. * * <p>The returned table will be serializable if the specified table is serializable. * * <p>Consider using an {@link ImmutableTable}, which is guaranteed never to change. * * @since 11.0 */
public static <R, C, V> Table<R, C, V> unmodifiableTable( Table<? extends R, ? extends C, ? extends V> table) { return new UnmodifiableTable<>(table); } private static class UnmodifiableTable<R, C, V> extends ForwardingTable<R, C, V> implements Serializable { final Table<? extends R, ? extends C, ? extends V> delegate; UnmodifiableTable(Table<? extends R, ? extends C, ? extends V> delegate) { this.delegate = checkNotNull(delegate); } @SuppressWarnings("unchecked") // safe, covariant cast @Override protected Table<R, C, V> delegate() { return (Table<R, C, V>) delegate; } @Override public Set<Cell<R, C, V>> cellSet() { return Collections.unmodifiableSet(super.cellSet()); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public Map<R, V> column(@Nullable C columnKey) { return Collections.unmodifiableMap(super.column(columnKey)); } @Override public Set<C> columnKeySet() { return Collections.unmodifiableSet(super.columnKeySet()); } @Override public Map<C, Map<R, V>> columnMap() { Function<Map<R, V>, Map<R, V>> wrapper = unmodifiableWrapper(); return Collections.unmodifiableMap(Maps.transformValues(super.columnMap(), wrapper)); } @Override public V put(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { throw new UnsupportedOperationException(); } @Override public void putAll(Table<? extends R, ? extends C, ? extends V> table) { throw new UnsupportedOperationException(); } @Override public V remove(@Nullable Object rowKey, @Nullable Object columnKey) { throw new UnsupportedOperationException(); } @Override public Map<C, V> row(@Nullable R rowKey) { return Collections.unmodifiableMap(super.row(rowKey)); } @Override public Set<R> rowKeySet() { return Collections.unmodifiableSet(super.rowKeySet()); } @Override public Map<R, Map<C, V>> rowMap() { Function<Map<C, V>, Map<C, V>> wrapper = unmodifiableWrapper(); return Collections.unmodifiableMap(Maps.transformValues(super.rowMap(), wrapper)); } @Override public Collection<V> values() { return Collections.unmodifiableCollection(super.values()); } private static final long serialVersionUID = 0; }
Returns an unmodifiable view of the specified row-sorted table. This method allows modules to provide users with "read-only" access to internal tables. Query operations on the returned table "read through" to the specified table, and attempts to modify the returned table, whether direct or via its collection views, result in an UnsupportedOperationException.

The returned table will be serializable if the specified table is serializable.

Params:
  • table – the row-sorted table for which an unmodifiable view is to be returned
Returns:an unmodifiable view of the specified table
Since:11.0
/** * Returns an unmodifiable view of the specified row-sorted table. This method allows modules to * provide users with "read-only" access to internal tables. Query operations on the returned * table "read through" to the specified table, and attempts to modify the returned table, whether * direct or via its collection views, result in an {@code UnsupportedOperationException}. * * <p>The returned table will be serializable if the specified table is serializable. * * @param table the row-sorted table for which an unmodifiable view is to be returned * @return an unmodifiable view of the specified table * @since 11.0 */
@Beta public static <R, C, V> RowSortedTable<R, C, V> unmodifiableRowSortedTable( RowSortedTable<R, ? extends C, ? extends V> table) { /* * It's not ? extends R, because it's technically not covariant in R. Specifically, * table.rowMap().comparator() could return a comparator that only works for the ? extends R. * Collections.unmodifiableSortedMap makes the same distinction. */ return new UnmodifiableRowSortedMap<>(table); } static final class UnmodifiableRowSortedMap<R, C, V> extends UnmodifiableTable<R, C, V> implements RowSortedTable<R, C, V> { public UnmodifiableRowSortedMap(RowSortedTable<R, ? extends C, ? extends V> delegate) { super(delegate); } @Override protected RowSortedTable<R, C, V> delegate() { return (RowSortedTable<R, C, V>) super.delegate(); } @Override public SortedMap<R, Map<C, V>> rowMap() { Function<Map<C, V>, Map<C, V>> wrapper = unmodifiableWrapper(); return Collections.unmodifiableSortedMap(Maps.transformValues(delegate().rowMap(), wrapper)); } @Override public SortedSet<R> rowKeySet() { return Collections.unmodifiableSortedSet(delegate().rowKeySet()); } private static final long serialVersionUID = 0; } @SuppressWarnings("unchecked") private static <K, V> Function<Map<K, V>, Map<K, V>> unmodifiableWrapper() { return (Function) UNMODIFIABLE_WRAPPER; } private static final Function<? extends Map<?, ?>, ? extends Map<?, ?>> UNMODIFIABLE_WRAPPER = new Function<Map<Object, Object>, Map<Object, Object>>() { @Override public Map<Object, Object> apply(Map<Object, Object> input) { return Collections.unmodifiableMap(input); } };
Returns a synchronized (thread-safe) table backed by the specified table. In order to guarantee serial access, it is critical that all access to the backing table is accomplished through the returned table.

It is imperative that the user manually synchronize on the returned table when accessing any of its collection views:


Table<R, C, V> table = Tables.synchronizedTable(HashBasedTable.<R, C, V>create());
...
Map<C, V> row = table.row(rowKey);  // Needn't be in synchronized block
...
synchronized (table) {  // Synchronizing on table, not row!
  Iterator<Entry<C, V>> i = row.entrySet().iterator(); // Must be in synchronized block
  while (i.hasNext()) {
    foo(i.next());
  }
 }

Failure to follow this advice may result in non-deterministic behavior.

The returned table will be serializable if the specified table is serializable.

Params:
  • table – the table to be wrapped in a synchronized view
Returns:a synchronized view of the specified table
Since:22.0
/** * Returns a synchronized (thread-safe) table backed by the specified table. In order to guarantee * serial access, it is critical that <b>all</b> access to the backing table is accomplished * through the returned table. * * <p>It is imperative that the user manually synchronize on the returned table when accessing any * of its collection views: * * <pre>{@code * Table<R, C, V> table = Tables.synchronizedTable(HashBasedTable.<R, C, V>create()); * ... * Map<C, V> row = table.row(rowKey); // Needn't be in synchronized block * ... * synchronized (table) { // Synchronizing on table, not row! * Iterator<Entry<C, V>> i = row.entrySet().iterator(); // Must be in synchronized block * while (i.hasNext()) { * foo(i.next()); * } * } * }</pre> * * <p>Failure to follow this advice may result in non-deterministic behavior. * * <p>The returned table will be serializable if the specified table is serializable. * * @param table the table to be wrapped in a synchronized view * @return a synchronized view of the specified table * @since 22.0 */
public static <R, C, V> Table<R, C, V> synchronizedTable(Table<R, C, V> table) { return Synchronized.table(table, null); } static boolean equalsImpl(Table<?, ?, ?> table, @Nullable Object obj) { if (obj == table) { return true; } else if (obj instanceof Table) { Table<?, ?, ?> that = (Table<?, ?, ?>) obj; return table.cellSet().equals(that.cellSet()); } else { return false; } } }