1

I'm having trouble making a class in Java immutable. I know that I have some problems going on with my constructor and probably my getter methods as well. From what I've read, it sounds like that I need to make new objects that aren't referencing the original object.

I'd like some help, if possible, leading me to how I can make this class immutable.

    public class FMatrix implements Matrix {

  private final int rows, columns;
  private final int[][] matrix;
  //TODO: implement interface.

  public FMatrix(int[][] matrix) {
    int[][] matrix1;
    if (matrix.length == 0) {
      rows = 0;
      columns = 0;
    } else {
      rows = matrix.length;
      columns = matrix[0].length;
    }
    this.matrix = new int[rows][columns];
    //this.matrix = matrix;
  }

  /**
   * Returns the element at particular point in the matrix.
   * @param y y position
   * @param x x position
   * @return element
   */
  @Override
  public int getElement(int y, int x) {
    return matrix[y][x];
  }

  /**
   * Returns the number of rows in the matrix.
   * @return rows
   */
  @Override
  public int getRows() {
    return rows;
  }

  /**
   * Returns the number of columns in the matrix.
   * @return columns
   */
  @Override
  public int getColumns() {
    return columns;
  }

  /**
   * Returns this matrix scaled by a factor. That is, computes kA where k is a
   * constant and A is a matrix (this object).
   *
   * @param scalar scalar
   * @return matrix
   */
  @Override
  public Matrix scale(int scalar) {

    int value;
    int[][] mat1;
    int newRows = getRows();
    int newColumns = getColumns();

    mat1 = new int[newRows][newColumns];
    //this.matrix = new int[newRows][newColumns];
    FMatrix newMatrix = new FMatrix(mat1);
    for (int i = 0; i < getRows(); i++) {
      for (int j = 0; j < getColumns(); j++) {
        value = getElement(i, j) * scalar;
        mat1[i][j] = value;
      }
    }

    return newMatrix;
  }

  /**
   * Returns this matrix added with another matrix. That is, computes A+B where
   * A and B are matrices (this object, and another respectively).
   * @param other addend
   * @return matrix
   * @throws RuntimeException if matrices do not have matching dimensions.
   */
  @Override
  public Matrix plus(Matrix other) {
    int value;
    int newRows = getRows();
    int newColumns = getColumns();

    if (newRows != other.getRows() || newColumns != other.getColumns()) {
      throw new RuntimeException();
    }
    FMatrix newMatrix = new FMatrix(matrix);
    for (int i = 0; i < newRows; i++) {
      for (int j = 0; j < newColumns; j++) {
        value = getElement(i, j) + other.getElement(i, j);
        this.matrix[i][j] = value;
      }
    }
    return newMatrix;
  }

  /**
   * Returns this matrix subtracted by another matrix. That is, computes A-B
   * where A and B are matrices (this object, and another respectively).
   * @param other subtrahend
   * @return matrix
   * @throws RuntimeException if matrices do not have matching dimensions.
   */
  @Override
  public Matrix minus(Matrix other) {
    int value;
    int newRows = getRows();
    int newColumns = getColumns();

    //throw exception if dimensions do not match
    if (newRows != other.getRows() || newColumns != other.getColumns()) {
      throw new RuntimeException("Dimensions do not match");
    }
    FMatrix newMatrix = new FMatrix(matrix);
    for (int i = 0; i < newRows; i++) {
      for (int j = 0; j < newColumns; j++) {
        value = getElement(i, j) - other.getElement(i, j);
        this.matrix[i][j] = value;
      }
    }
    return newMatrix;

  }

  /**
   * Returns true if this matrix matches another matrix.
   * @param other another matrix
   * @return equality
   */
  @Override
  public boolean equals(Object other) {
    if (other == this) {
      return true;
    } else {
      return false;
    }

  }

  /**
   * Returns a string representation of this matrix. A new line character will
   * separate each row, while a space will separate each column.
   * @return string representation
   */
  @Override
  public String toString() {
    String str = "";
    for (int i = 0; i < rows; i++) {
      for (int j = 0; j < columns; j++) {
        str += getElement(i, j) + " ";
        if (j == columns - 1) {
          str += "\n";            //add a new line for next row
        }
      }
    }
    return str;
  }

Main Method

public static void main(String[] args) {

int[][] data1 = new int[0][0];
int[][] data2 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int[][] data3 = {{1, 4, 7}, {2, 5, 8}, {3, 6, 9}};

Matrix m1 = new FMatrix(data1);
Matrix m2 = new FMatrix(data2);
Matrix m3 = new FMatrix(data3);

System.out.println("m1 --> Rows: " + m1.getRows() + " Columns: " + m1.getColumns());
System.out.println("m2 --> Rows: " + m2.getRows() + " Columns: " + m2.getColumns());
System.out.println("m3 --> Rows: " + m3.getRows() + " Columns: " + m3.getColumns());

//check for reference issues
System.out.println("m2 -->\n" + m2);
data2[1][1] = 101;
System.out.println("m2 -->\n" + m2);

//test equals
System.out.println("m2==null: " + m2.equals(null));             //false
System.out.println("m3==\"MATRIX\": " + m2.equals("MATRIX"));   //false
System.out.println("m2==m1: " + m2.equals(m1));                 //false
System.out.println("m2==m2: " + m2.equals(m2));                 //true
System.out.println("m2==m3: " + m2.equals(m3));                 //false

//test operations (valid)
System.out.println("2 * m2:\n" + m2.scale(2));
System.out.println("m2 + m3:\n" + m2.plus(m3));
System.out.println("m2 - m3:\n" + m2.minus(m3));

//test operations (invalid)
//System.out.println("m1 + m2" + m1.plus(m2));
//System.out.println("m1 - m2" + m1.minus(m2));

}

Here's the output from the tests in main. The problem is that the m2 object shouldn't be changed after .scale() method is invoked.

m1 --> Rows: 0 Columns: 0
m2 --> Rows: 3 Columns: 3
m3 --> Rows: 3 Columns: 3
m2 -->
1 2 3 
4 5 6 
7 8 9 

m2 -->
1 2 3 
4 101 6 
7 8 9 

m2==null: false
m3=="MATRIX": false
m2==m1: false
m2==m2: true
m2==m3: false
2 * m2:
2 4 6 
8 202 12 
14 16 18 

m2 + m3:
2 6 10 
6 106 14 
10 14 18 

m2 - m3:
1 2 3 
4 101 6 
7 8 9 
Michael F
  • 13
  • 3

1 Answers1

0

You appear to have a bug in your manipulation methods. Consider scale

FMatrix newMatrix = new FMatrix(mat1);
for (int i = 0; i < getRows(); i++) {
  for (int j = 0; j < getColumns(); j++) {
    value = getElement(i, j) * scalar;
    mat1[i][j] = value;
  }
}

return newMatrix;

This is great because your assignment to [i][j] is on mat1. But look what you do in plus!!

FMatrix newMatrix = new FMatrix(matrix);
for (int i = 0; i < newRows; i++) {
  for (int j = 0; j < newColumns; j++) {
    value = getElement(i, j) + other.getElement(i, j);
    this.matrix[i][j] = value;
  }
}
return newMatrix;

Oh dear, you're changing this.matrix[i][j] - your class is no longer immutable. But more importantly, the value you return is ALSO busted. So I believe you simply need to fix this bug and your class will actually be immutable.

Other bugs:

  • You never assign values from the constructor. You need to copy the values from the parameter into your internal arrays.

The copy is necessary so you can't do this

int[][] matrix = new int[3][3];
FMatrix f = new FMatrix(matrix);
matrix[1][1] = 42; // broken immutability

Right now it isn't a problem, because you never assign the values from the parameter to your matrix... so no worries.

public FMatrix(int[][] matrix) {
  if (matrix.length == 0) {
    rows = 0;
    columns = 0;
  } else {
    rows = matrix.length;
    columns = matrix[0].length;
  }
  this.matrix = new int[rows][0];
  for(int row = 0; row < rows; row++) {
      this.matrix[rows] = matrix[rows].clone();
  }
}
corsiKa
  • 81,495
  • 25
  • 153
  • 204
  • Ahhhhh I see what you mean. I made those fixes to my plus and minus methods and the results are getting better! Now the only bug I'm facing is from the line "data2[1][1] = 101;" since m2 is now referencing that value at that position. Thank you for your help! – Michael F Aug 28 '17 at 20:13
  • Michael as I mentioned in "other bugs" you have to copy the values from the constructor. I will post an example. – corsiKa Aug 28 '17 at 20:28
  • @MichaelF Example added at the end. – corsiKa Aug 28 '17 at 20:31