Data Structures, Algorithms, & Applications in Java
Chapter 8, Exercise 33

We use the one-dimensional array mapping used for a lower triangular matrix (see the class LowerTriangularMatrix). When accessing an element (i,j) with i >= j, the mapping formula for an element in the lower triangle of a triangular matrix is used. When i < j, we are accessing an element in the upper triangle. Since the matrix is symmetric, we access the equivalent element (j,i).

The code is given below. The complexity of each method is readily seen to be Theta(1).
public class LowerSymmetricMatrix
{
   // data members
   int rows;            // matrix dimension
   Object zero;         // zero element
   Object [] element;   // element array

   // constructor
   public LowerSymmetricMatrix(int theRows, Object theZero)
   {
      // validate theRows
      if (theRows < 1)
         throw new IllegalArgumentException
               ("number of rows must be > 0");
   
      // create and initialize the matrix
      rows = theRows;
      zero = theZero;
      element = new Object [rows * (rows + 1) / 2];
      for (int i = 0; i <  rows * (rows + 1) / 2; i++)
         element[i] = zero;
   }
   
   // methods
   /** throws IndexOutOfBoundsException when i < 1
     * or j < 1 or i > rows or j > rows */
   void checkIndex(int i, int j)
   {
      if (i < 1 || j < 1 || i > rows || j > rows)
         throw new IndexOutOfBoundsException
                   ("i = " + i + " j = " + j +
                    " rows = " + rows + " cols = " + rows);
   }

   /** return the element this(i,j)
     * throws IndexOutOfBoundsException when i or j invalid */
   public Object get(int i, int j)
   {
      checkIndex(i, j);

      if (i >= j)
         return element[i * (i - 1) / 2 + j - 1];
      else
         // interchange roles of i and j
         return element[j * (j - 1) / 2 + i - 1];
   }
   
   /** set this(i,j) = newValue
     * throws IndexOutOfBoundsException when i or j invalid */
   public void set(int i, int j, Object newValue)
   {
      checkIndex(i, j);

      // store newValue
      if (i >= j)
         element[i * (i - 1) / 2 + j - 1] = newValue;
      else
         // interchange roles of i and j
         element[j * (j - 1) / 2 + i - 1] = newValue;
   }
}