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

я начинаю с 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 ответов


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

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

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

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


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

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

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


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

методы, как:

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

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

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

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


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

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

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


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

private A[] items;

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

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

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

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


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

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

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

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

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

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


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

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