Как инкапсулировать массив в Java - PullRequest
8 голосов
/ 05 января 2010

Я начинаю с Java и изучаю сеттеры, геттеры и инкапсуляцию. У меня очень простая программа, два класса:

  • Container имеет собственный массив int (numArray) со своим установщиком и получателем.

  • Main создает объект Container и использует его в методе totalArray.


public class Container {
    private int numArray[]= {0,0,0};
    public int[] getNumArray() {
        return numArray;
    }
    public void setNumArray(int index, int value){
        numArray[index] = value;
    }    
}

public class Main {
    public static void main(String[] args) {
        Container conte = new Container();
        System.out.println(totalArray(conte.getNumArray()));
        conte.getNumArray()[2]++;
        System.out.println(totalArray(conte.getNumArray()));
    }
    private static int totalArray (int v[]){
        int total=0;
        for (int conta =0; conta<v.length;conta++){
            total+=v[conta];
        }
        return total;
    }
}

Проблема: я могу изменить приватный массив int через геттер, я знаю, это потому, что getNumArray возвращает ссылку на numArray, а не сам массив. Если бы меня интересовал один элемент массива, я бы создал метод получения со значением индекса, но мне нужен весь массив для метода totalArray.

Как я могу предотвратить модификацию numArray вне его класса?

Ответы [ 8 ]

8 голосов
/ 05 января 2010

Все, что вы можете сделать, чтобы люди не могли изменить ваш массив, это предоставить его копию в геттере.

public int[] getArray() {
    return Arrays.copyOf(numArray, numArray.length);
}

Таким образом, другие методы могут изменять свою собственную копию массива, но когда они снова вызывают геттер, они получают исходную версию без изменений. Только предоставленный вами setNumArray() может фактически изменить ваш внутренний массив.

В противном случае, если вы хотите полностью заблокировать контейнер, вы должны удалить массивы и использовать неизменяемый объект. Некоторые библиотеки предоставляют неизменяемые списки или используют Collections.unmodifiableList .

6 голосов
/ 05 января 2010

Если вы хотите вернуть массив, вы бы клонировали его:

  public int[] getArray() {
       return (int[]) numArray.clone();
  }

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

3 голосов
/ 05 января 2010

Обычно вы смотрите на интерфейс, который вы пытаетесь предоставить абонентам вашего класса.

Методы, такие как:

void addItem(Object item);
Object getItem(int index);
int getSize();

- это то, что вы предоставляете в своем классе контейнера. Затем они взаимодействуют с приватным массивом от имени вызывающей стороны.

Если вы хотите вернуть весь массив без внесения изменений, вы можете в геттере скопировать массив в новый и вернуть копию.

В качестве альтернативы, если вы используете классы коллекции Java вместо примитивного массива, они предоставляют метод unmodifiableXXX () (например, Collections.unmodifiableList(myList)), который обеспечивает оболочку только для чтения вокруг коллекции.

0 голосов
/ 14 октября 2017

Эффективный способ памяти это сделать ...

package com.eric.stackoverflow.example;

import java.util.List;

import com.google.common.collect.ImmutableList;

public class Container {
    private int numArray[] = {0, 0, 0};
    public int[] getNumArray() {
        return ImmutableList.copyOf(numArray);
    }
    public void setNumArray(int index, int value) {
        numArray[index] = value;
    }    
}

Это позволяет ImmutableList установить правильное количество элементов в массиве поддержки List и уменьшает количество создаваемых промежуточных объектов.

0 голосов
/ 14 октября 2017

Как инкапсулировать массив в Java

Вопрос может быть достаточно ясным, однако я предполагаю, что смысл OOP состоит в том, чтобы спроектировать класс как сущность , которая управляет этим массивом и извлекает его собственный API.

Если вы настаиваете, то верните клон массива / защитная копия - это путь для простых случаев .

0 голосов
/ 14 октября 2017

Я хотел бы предложить другой подход из всех ответов, которые я видел здесь. Когда вы думаете об инкапсуляции, полезно также подумать о правиле: «Не запрашивайте у объекта данные, попросите объект работать с его данными».

Вы не дали мне использовать numArray, я собираюсь притвориться, что вашей целью было создание числового «вектора» из математики (не Java-вектора) в качестве примера.

Итак, вы создаете класс NumericVector, который содержит массив значений типа double. Ваш NumericVector будет иметь методы, такие как multiplyByScalar(double scalar) и addVector (NumericVector secondVector) для добавления элементов.

Ваш внутренний массив полностью инкапсулирован - он никогда не уходит. Любая операция, выполняемая над ним, выполняется в вашем классе NumericVector с помощью этих «бизнес-методов». Как вы отображаете его после операции на нем? Имейте переопределение NumericVector NumericVector.toString(), чтобы оно печаталось правильно, или, если у вас есть графический интерфейс, напишите класс «Controller» для передачи данных из вашей модели (NumbericVector) в ваше представление (GUI). Для этого может потребоваться способ потоковой передачи элементов из NumericVector.

Это также указывает на несколько вещей, которых следует избегать: не создавайте автоматически сеттеры и геттеры, они нарушают вашу инкапсуляцию. Вам часто нужны геттеры, но, как уже говорили другие, вы должны заставить свой геттер возвращать неизменную версию массива. Также постарайтесь сделать ваши классы неизменяемыми, где это возможно. Этот метод, о котором я упоминал ранее numericVector.addVector(NumericVector secondVector), вероятно, не должен изменять numericVector, а возвращать новый NumericVector с решением.

Случай, когда этот (и OO в целом) часто терпит неудачу, - это библиотеки - когда вы действительно хотите добавить немного функциональности в ваш массив, но все еще оставляете его как массив общего назначения. В этом случае Java обычно вообще не беспокоится об инкапсуляции, она просто добавляет вспомогательный метод / класс, который может что-то делать с коллекцией / массивом (посмотрите на объект "Arrays" для множества отличных примеров).

0 голосов
/ 16 июня 2012

Существует еще один способ, который не делает копию массива, но имеет другие недостатки. Я бы использовал его для очень больших массивов:

private A[] items;

public List<A> getItems() {
    return Collections.unmodifiableList(Arrays.asList(items));
}
0 голосов
/ 05 января 2010

Инкапсуляция - это процесс сокрытия реализации. Хранит ли коллекция свои данные в массиве или нет - это деталь реализации; если бы он был инкапсулирован, вы бы хотели изменить его на другой тип хранилища.

Тот факт, что вы выставляете состояние (или производное состояние) как методы получения и установки, нарушает инкапсуляцию и подразумевает, что вы реализуете абстрактный тип данных, а не настоящий объектно-ориентированный класс. Например, ArrayList - это тип данных, который не представляет никакой истинной инкапсуляции поведения в приложении. То, что вам нужно, зависит от того, как и где будет использоваться тип.

Я бы хотел либо Container реализовать Iterable<Integer> для внешнего взаимодействия, если это просто тип данных контейнера, либо предоставить внутренний метод итератора, которому вы передаете посетителя, если он предназначен для инкапсулированного класса. Если это абстрактный тип данных, настоятельно рекомендуется использовать вместо него встроенные, например int[] или List<Integer>.

...