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

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

  • Container имеет частный массив целых чисел (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 вне его класса?


person Setwarn    schedule 05.01.2010    source источник


Ответы (8)


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

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

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

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

person glmxndr    schedule 05.01.2010
comment
«Некоторые», как в стандартной библиотеке Java. - person Pete Kirkham; 05.01.2010

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

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

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

person Yishai    schedule 05.01.2010

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

Такие методы, как:

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

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

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

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

person Paolo    schedule 05.01.2010
comment
Пожалуйста, исправьте имена методов. Методы Java начинаются со строчных букв, а Object — это класс (верхний регистр). - person Aaron Digulla; 05.01.2010
comment
Спасибо, это то, что происходит, когда вы проводите день, глядя на C #...;) - person Paolo; 05.01.2010
comment
+1: инкапсулируйте свой массив, вообще не раскрывая существование массива! Сделайте так, чтобы у контейнера был богатый интерфейс, представляющий фактические функции этого класса, а не просто возвращающий его внутренние данные-члены вызывающим объектам, чтобы они могли делать с ними то, что они хотят. - person Andrzej Doyle; 05.01.2010

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

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

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

person Pete Kirkham    schedule 05.01.2010

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

private A[] items;

public List<A> getItems() {
    return Collections.unmodifiableList(Arrays.asList(items));
}
person sinharaj    schedule 16.06.2012

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

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

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

person MrNobody    schedule 14.10.2017

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

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

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

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

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

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

person Bill K    schedule 13.10.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 и уменьшает количество создаваемых промежуточных объектов.

person Eric    schedule 14.10.2017