Как правильно работать с полями при создании интерфейса?

У меня есть интерфейс IMatrix:

public interface IMatrix {

    int[][] data = null;
    public int rows = 0;
    public int cols = 0;
        
    int getElement(int row, int column);
    void setElement(int row, int column, int value);
        
    IMatrix sum(IMatrix m1, IMatrix m2);
    IMatrix product(IMatrix other);
        
    boolean equals(IMatrix other);
    String toString();
        
}

К примеру, здесь есть метод sum(), который я хочу реализовать в каждом из классов по-разному, но при обращении к полям rows и cols Eclipse выдает предупреждение:

The static field IMatrix.cols should be accessed in a static way

Не совсем понимаю, как правильно реализовывать методы у классов, имплементирующих интерфейс.

Вот как я попытался реализовать этот метод в классе UsualMatrix, реализующем IMatrix:

@Override
public IMatrix sum(IMatrix m1, IMatrix m2) {
            
    if ((m1.rows != m2.rows) || (m1.cols != m2.cols)) {
        throw new MatrixOperationException("Matrices must be the same size to be added together.");
    }
            
    UsualMatrix mtxFinal = new UsualMatrix(rows, cols);
            
    for (int i = 0; i < this.rows; i++) {
        for (int j = 0; j < this.cols; j++) {
            mtxFinal.data[i][j] = m1.data[i][j] + m2.data[i][j];
        }
    }
            
    return mtxFinal;
}

Ответы (2 шт):

Автор решения: talex

Поля интерфейсов вседа статические. Тебе нужен абстрактный класс.

→ Ссылка
Автор решения: Nowhere Man

Поскольку главный вопрос сформулирован Как правильно работать с полями при создании интерфейса?, ответом может быть следующее:

  • Интерфейс описывает поведение некоторого объекта, то есть классический интерфейс сводится к указанию сигнатур методов
  • Поля (экземпляра) определяют параметры состояния этого объекта, и они вообще НЕ могут использоваться при создании интерфейса.

В предыдущих ответах указано, что "поля" в интерфейсе являются статическими (т.е. относятся ко всем экземплярам, реализующим данный интерфейс), но они также являются финальными (константными), даже если пропущен соответствующий модификатор final.

Определение их в интерфейсе и присвоение им неких начальных значений бессмысленно -- для всех экземпляров, реализующих IMatrix, data будет всегда равно null, а rows, cols соответственно 0, и это нельзя будет изменить.

То, что проявилось только предупреждение от Eclipse в реализации метода sum, говорит лишь о том, что в классе UsualMatrix либо была написана заглушка вместо конструктора, либо уже были добавлены аналогичные поля экземпляра, иначе возникли бы ошибки компиляции при попытке присвоить значение финальной переменной:

// class UsualMatrix
// поля экземпляра
private int rows, cols; // без этого объявления была бы попытка изменить константы в интерфейсе
private int[][] data;   // аналогично 

public UsualMatrix(int r, int c) {
    this.rows = r;
    this.cols = c;
    this.data = new int[rows][cols];
}

К сожалению, Java допускает подобные омонимы при объявлении статических переменных и переменных экземпляра.

Далее, рассмотрим реализацию метода sum.

Очевидно, при попытке выполнить этот метод будет выброшено исключение NullPointerException при попытке обратиться напрямую к переменной data для переменных m1 (m2):

mtxFinal.data[i][j] = m1.data[i][j] + m2.data[i][j]; // m1.data и m2.data - null! 

так как они объявлены с типом IMatrix, в контексте которого существует только статическая константа data, равная null!


Исходя из вышесказанного, для "правильной" работы с полями их следует полностью убрать из интерфейса IMatrix, например, перенести в некий базовый (абстрактный) класс, как указано в более ранних ответах @talex и @RomanC, а в интерфейсе оставить только геттеры для размеров матрицы и методы для чтения/записи элементов.

В этом же базовом / абстрактном классе следует реализовать методы класса Object equals (чтобы корректно вызвать перегруженный вариант equals(IMatrix that)), соответственно, hashCode и/или toString.

Методы для операций над матрицей можно вынести в отдельный класс, инкапсулирующий алгоритмы работы с матрицами.

Пример реализации:

public interface IMatrix {
    int rows();
    int cols();

    int getElement(int row, int column);
    void setElement(int row, int column, int value);
}

При необходимости можно реализовать конструкторы для копирования исходного массива или обычного присваивания ссылки на массив и т.д.

public class UsualMatrix implements IMatrix {
    private final int rows, cols;
    private final int[][] data;

    public UsualMatrix(int rows, int cols) {
        this.rows = rows;
        this.cols = cols;
        this.data = new int[rows][cols];
    }

    @Override public int rows() { return this.rows; }
    @Override public int cols() { return this.cols; }

    @Override public int getElement(int row, int column) {
        return this.data[row][column];
    }
    @Override public void setElement(int row, int column, int value) {
        this.data[row][column] = value;
    }

    // TODO
    // @Override public boolean equals(Object o) {...}
    // @Override public int hashCode() {...} // вместе с equals
    // @Override public String toString() {...}
}

В данной реализации алгоритмов обработки матриц используются только соответствующие методы интерфейса IMatrix, без "прямого" доступа к полям экземпляра.

public class IMatrixOperations {
    public static IMatrix sum(IMatrix mx1, IMatrix mx2) {
        int r = mx1.rows();
        int c = mx1.cols();
        if((r != mx2.rows()) || (c != mx2.cols())) {
            throw new IllegalArgumentException("Matrices must be the same size to be added together.");
        }

        IMatrix result = new UsualMatrix(r, c);
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                result.setElement(i, j, mx1.getElement(i, j) + mx2.getElement(i, j));
            }
        }
        
        return result;
    }
    //....
}
→ Ссылка