Что делает Collections.unmodifiableSet () в Java? - PullRequest
30 голосов
/ 10 марта 2010

Я вижу, что Collections.unmodifiableSet возвращает неизменяемое представление данного набора, но я не понимаю, почему мы не можем просто использовать модификатор final для этого.

В моем понимании final объявляет константу: нечто, что нельзя изменить. Таким образом, если набор объявлен как константа, он не может быть изменен: ничто не может быть удалено из набора, и ничто не может быть добавлено.

Зачем нам нужно Collections.unmodifiableSet?

Ответы [ 5 ]

62 голосов
/ 10 марта 2010

final объявляет ссылку на объект, который не может быть изменен, например,

private final Foo something = new Foo();

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

Это не предотвращает изменение внутреннего состояния объекта. Я все еще могу вызывать любые методы на Foo, доступные для соответствующей области. Если один или несколько из этих методов изменят внутреннее состояние этого объекта, final не предотвратит это.

Таким образом, следующее:

private final Set<String> fixed = new HashSet<String>();

не не создает Set, который не может быть добавлен или иным образом изменен; это просто означает, что fixed будет ссылаться только на этот экземпляр.

В отличие от этого:

private Set<String> fixed = Collections.unmodifiableSet( new HashSet<String>() );

создает экземпляр Set, который выдаст UnsupportedOperationException, например, если попытаться вызвать fixed.add() или fixed.remove(), - сам объект защитит свое внутреннее состояние и предотвратит его изменение.

Ради полноты:

private final Set<String> fixed = Collections.unmodifiableSet( new HashSet<String>() );

создает экземпляр Set, который не позволяет изменять его внутреннее состояние, а также означает, что fixed будет указывать только на экземпляр этого набора.

Причина, по которой final может использоваться для создания констант примитивов, основана на том факте, что значение не может быть изменено. Помните, что fixed выше было просто ссылкой - переменной, содержащей адрес, который нельзя изменить. Ну, для примитивов, например

private final int ANSWER = 42;

значение ANSWER равно 42. Поскольку ANSWER нельзя изменить, оно будет иметь только значение 42.

Пример, который стирает все строки, будет следующим:

private final String QUESTION = "The ultimate question";

Согласно приведенным выше правилам, QUESTION содержит адрес экземпляра String, который представляет собой «окончательный вопрос», и этот адрес не может быть изменен. Здесь следует помнить, что String сам по себе неизменен - ​​вы ничего не можете сделать с экземпляром String, который его изменяет, и с любыми другими операциями, которые могли бы сделать это (например, replace, substring и т.д.) возвращать ссылки на совершенно разные экземпляры String.

15 голосов
/ 10 марта 2010

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

final Set s = new Set(); просто гарантирует, что вы не можете сделать s = new Set(); снова. Это не делает набор неизменным, если вы не можете ничего добавить к нему для начала. Таким образом, чтобы прояснить ситуацию, final влияет только на переменную reference , а не на объект, на который указывает ссылка.

Я могу сделать следующее:

final List<String> l = new ArrayList<String>();
l.add("hello");
l.add("world");
l.remove(0);

но я не могу этого сделать.

l = new ArrayList<String>();

снова из-за final Я не могу изменить то, на что указывает переменная l.

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

java.util.Collections.syncronizedXXX();

или

java.util.Collections.unmodifiableXXX();

или используйте один из подходящих контейнеров от java.util.concurrency.* package.

если у меня был объект Person и я сделал final Person p = new Person("me");, это означает, что я не могу переназначить p, чтобы указать на другой объект Person. Я все еще могу сделать p.setFirstName("you");

Смущает ситуация то, что

final int PI = 3.14;
final String greeting = "Hello World!";

выглядит как const в C ++, когда фактически объекты, на которые они указывают, являются неизменяемыми / неизменяемыми по своей природе. Контейнеры или объекты с методами-мутаторами, которые могут изменять внутреннее состояние объекта, не являются const, просто ссылкой на эти объекты final и не могут быть переназначены на ссылку другой объект.

3 голосов
/ 20 марта 2014

Collections.unmodifiableSet(Set<? extends T>) создаст обертку на исходном наборе. Этот набор оберток не может быть изменен. но оригинальный набор можно изменить.

Пример:

 Set<String> actualSet=new HashSet<String>(); //Creating set

Добавление некоторых элементов

actualSet.add("aaa");
actualSet.add("bbb");

Печать добавленных элементов

System.out.println(actualSet);   //[aaa, bbb]

Поместите actualSet в неизменяемый набор и назначьте для новой ссылки (wrapperSet).

Set<String> wrapperSet=Collections.unmodifiableSet(orginalSet);

Распечатать упаковщик. поэтому он будет иметь actualSet значений

System.out.println(wrapperSet);   //[aaa, bbb]

Давайте попробуем удалить / добавить один элемент в wrapperSet.

wrapperSet.remove("aaa");   //UnSupportedOperationException 

Добавить еще один элемент в actualSet

    actualSet .add("ccc");

Печать actualSet и wrapperSet. оба набора значений одинаковы. поэтому, если вы добавите / удалите какие-либо элементы в фактическом наборе, изменения также будут отражены в наборе оберток.

    System.out.println(actualSet);  //[aaa, ccc, bbb]
    System.out.println(wrapperSet);  // [aaa, ccc, bbb]

Использование:

Этот Collections.unmodifiableSet(Set<? extends T>) используется для предотвращения изменения метода-получателя Set любого объекта. скажем

public class Department{

    private Set<User> users=new HashSet<User>();

    public Set<User> getUsers(){
        return Collections.unmodifiableSet(users); 
    }
}
3 голосов
/ 10 марта 2010

final не (стиль C ++) const. В отличие от C ++, Java не имеет const -методов или чего-либо подобного, и методы, которые могут изменить объект, могут быть вызваны через ссылку final.

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

0 голосов
/ 13 июля 2018

Подводя итог, что мы можем сделать и не можем:

Приготовление:

private Set<String> words = new HashSet<>(Arrays.asList("existing word"));

Финал по ссылке

private final Set<String> words = new HashSet<>();

может :

words.add("new word");

не может :

words = new HashSet<>(); //compilation error

Окончательно по ссылке и не подлежит изменению в коллекции.

приватный финал Set words = Collections.unmodifiableSet (words);

может :

String word = words.iterator().next();

не могу :

words = new HashSet<>(); // compilation error
words.add("new word"); // runtime error UnsupportedOperationException

Окончательный по ссылке и неизменяемый по коллекции, но взаимный как объект коллекции.

Но если у вас есть коллекция с взаимными объектами, вы можете ИЗМЕНИТЬ внутреннее состояние этого объекта.

 class A {
       public int a; //mutable field. I can change it after initialization
       public A(int a) {this.a = a;}
     }

 private final Set<A> set = Collections.unmodifiableSet(Arrays.asList(new A(25)));

Все еще не могу

set = new HashSet<>(); // compilation error
set.add(new A(777)); // runtime error UnsupportedOperationException

Но может

 A custom = words.iterator().next(); //here custom.a = 25;
 custom.a = 777; //here first element of **final, unmodifible** collection 
    //was changed from 25 to 777
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...