public class ModifyUnmodifiableListTest { final List<String> exampleList = new ArrayList<String>(); @Before public void setUp() { exampleList.add("text 1"); exampleList.add("text 2"); exampleList.add("text 3"); } @Test(expected = UnsupportedOperationException.class) public void shouldThrowExceptionWhenModifyUnmodifiableList() { List<String> unmodifiableList = Collections .unmodifiableList(exampleList); unmodifiableList.clear(); }
Operacja clear() wyrzuca UnsupportedOperationException zgodnie z oczekiwaniem. Poniżej kod który mnie zadziwił:
@Test public void shouldModifyUnmodifiableList() { // given List<String> unmodifiableList = Collections .unmodifiableList(exampleList); // when exampleList.clear(); // then assertEquals(0, unmodifiableList.size()); }
Test przechodzi, czyli na nowoutworzonej liście nie ma elementów!
Co się stało? Otóż metoda jak można przeczytać w dokumentacji:
Returns an unmodifiable view of the specified listZwraca nam widok (projekcję) na oryginalną listę. Czyli źródłową kolekcje można dalej modyfikować!
Jak można więc stworzyć niezależną kopię kolekcji? A no znajdzie się kilka sposobów. Jednym z prostszych jest skonwertowanie kolekcji do tablicy:
@Test public void shouldCloneUnmodifiableListToArray() { // given String[] tab = exampleList.toArray( new String[exampleList.size()]); // when exampleList.clear(); // then assertEquals(3, tab.length); assertEquals("text 1", tab[0]); }
Jak komuś koniecznie potrzebna lista lub inna kolekcja, to można z powrotem z tablicy zrobić kolekcje.
Inną możliwością jest metoda na piechotę, czyli ręczne dodanie elementów kolekcji do nowej kolekcji.
@Test public void shouldCloneUnmodifiableListInForLoop() { // given List<String> list = new ArrayList<String>(); for (String s : exampleList) { list.add(s); } // when exampleList.clear(); // then assertEquals(3, list.size()); assertEquals("text 1", list.get(0)); }
Można jeszcze próbować walczyć z Collections.copy(...) ale tutaj lista docelowa musi mieć co najmniej tyle samo elementów, co lista źródłowa.
Rozwiązanie jakie mi jeszcze wpadło do głowy to wykorzystanie Apache Commons a dokładniej SerializationUtils.clone(...).
Jest to już bardziej uniwersalna metoda robienia głębokiej kopii, która działa na wszystkim co jest serializowane.
A jakie jest rozwiązanie najprostsze? Po prostu skorzystać z konstruktora docelowej kolekcji:
@Test public void shouldCreateNewListBasedOnList() { // given List<String> list = new ArrayList<String>(exampleList); // when exampleList.clear(); // then assertEquals(3, list.size()); assertEquals("text 1", list.get(0)); }
To tyle. Cały kod to pobawienia się dostępny jest na githubie: ModifyUnmodifiableList.
Guava daje kilka metod tworzenia niemodyfikowalnych kolekcji na podstawie innej kolekcji.
OdpowiedzUsuńPrzykład:
List myList = new ArrayList();
myList.add("text 1");
List myImmutableList = ImmutableList.copyOf(myList);
Łatwo i przyjemnie a w dodatku bardzo wydajnie nawet przy kopiowaniu list o dużej ilości elementów.
W tym przypadku mamy kopię listy i do tego niemodyfikowalną :)
Usuń