Доступ к частным полям коллекции в Java - PullRequest
3 голосов
/ 01 октября 2009

У одного из моих классов есть поле, которое содержит Набор. Это поле заполняется только в конструкторе, а затем читается другими классами. Изначально у меня было что-то вроде этого:

public class Foo {
    public final Set<String> myItems;
    public Foo(Collection<String> theirItems) {
        this.myItems = new LinkedHashSet<String>(theirItems);
    }
}

Но это идет вразрез с передовой практикой ОО, согласно которой myItems должен быть закрытым и доступным только через сеттеры и геттеры. Итак, я изменил его на:

public class Foo {
    private final Set<String> myItems;
    public Foo(Collection<String> theirItems) {
        this.myItems = new LinkedHashSet<String>(theirItems);
    }
    public Set<String> getItems() {
        return myItems;
    }
}

Теперь myItems является приватным, но тот, кто вызывает getItems (), может добавлять / удалять элементы по своему желанию, что по сути та же ситуация, что и раньше. (На самом деле я не беспокоюсь о том, чтобы кто-то изменил содержимое предмета, это скорее теоретический вопрос)

Итак, я изменил getItems (), чтобы он возвращал массив:

public String[] getItems() {
    return myItems.toArray(new String[myItems.size()]);
}

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

public Set<String> getItems() {
    return new LinkedHashSet<String>(myItems);
}

Это дает звонящему то, что он хочет, но создает новый Набор при каждом доступе.

Что вы делаете в подобной ситуации - сохранить конфиденциальность любой ценой и принять преобразование / копирование исходной структуры или пожертвовать контролем над содержимым коллекции и полагаться на ответственных абонентов?

Ответы [ 7 ]

12 голосов
/ 01 октября 2009

Верните неизменяемый вид на ваш набор:

public Set<String> getItems() {
    return Collections.unmodifiableSet(myItems);
}

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

Одним из важных моментов является документирование того, что вы выберете, чтобы вызывающий абонент не получил никаких неприятных сюрпризов. Во многих отношениях я думаю, что это на самом деле самая важная вещь в большинстве приложений, где вызывающий на самом деле не злонамеренный. Пока ясно, каковы будут последствия, защита в большинстве случаев не так важна. Конечно, если ваш вызывающий может быть ненадежным кодом, а ваш набор жизненно важен для безопасности и т. Д., Вы попадаете в другую ситуацию.

8 голосов
/ 01 октября 2009

Это зависит от контекста. Есть несколько вариантов:

  1. Верните коллекцию. Вызывающая сторона может изменить коллекцию по своему усмотрению, но не может назначить новую коллекцию. Это дешевле, но предлагает наименьшую защиту.
  2. Возвращает представление (используя фабрики unmodifiableXXX класса Collections). Вызывающая сторона не может изменить коллекцию, но обновления будут видны для вызывающей стороны. Обычно это относительно дешево, потому что хранилище элементов не выделено; создается только обертка.
  3. Возвращает снимок коллекции, используя конструктор копирования соответствующей коллекции. Здесь звонящий получает копию коллекции. Они могут изменять копию, но оригинал не обновляется, а обновления оригинала не видны в копии. Это самый дорогой.
3 голосов
/ 01 октября 2009

Насколько "безопасным" вам нужно быть? Массив, который вы возвращаете, содержит ссылки на те же объекты, что и ваш набор. Если у этих объектов есть сеттеры, вы позволяете вызывающей стороне изменять содержимое вашего набора ... это нормально?

Можно было бы клонировать набор или предоставить неизменный интерфейс для доступа к набору. Это суждение. Если бы я разрабатывал фреймворковый код, я бы склонялся к ошибкам в плане безопасности и клонирования. Я склонен быть менее консервативным, когда клиент более тесно связан, скажем, связанный класс в том же пакете.

1 голос
/ 01 октября 2009

Я либо делаю копию набора, либо использую Collections.unmodifiableSet ().

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

1 голос
/ 01 октября 2009

Я бы выбрал ваш последний вариант:

public Set<String> getItems() {
    return new LinkedHashSet<String>(myItems);
}
0 голосов
/ 01 октября 2009

В качестве альтернативы Collections.unmodifiableSet (java.util.Set) проект под названием Библиотека коллекций Google имеет com.google.common.collect.ImmutableSet .

0 голосов
/ 01 октября 2009

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

...