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ń