Cześć! Na ostatnim wykładzie zapoznaliśmy się z klasą ArrayList , a także dowiedzieliśmy się, jak wykonywać na niej najczęstsze operacje. Ponadto zidentyfikowaliśmy sporo różnic między ArrayList a zwykłą tablicą. Przyjrzyjmy się teraz usunięciu elementu z listy ArrayList. Powiedzieliśmy już, że usuwanie elementów zwykłej tablicy nie jest zbyt wygodne. Ponieważ nie możemy usunąć samej komórki, możemy jedynie „wyzerować” jej wartość:
public class Cat {
private String name;
public Cat(String name) {
this.name = name;
}
public static void main(String[] args) {
Cat[] cats = new Cat[3];
cats[0] = new Cat("Tomasz");
cats[1] = new Cat("Hipopotam");
cats[2] = new Cat(„Filip Markowicz”);
cats[1] = null;
System.out.println(Arrays.toString(cats));
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
Wniosek:
[Cat{name='Томас'}, null, Cat{name='ФLubпп Маркович'}]
Ale po zresetowaniu w tablicy pozostaje „dziura”. Nie usuwamy komórki, a jedynie jej zawartość. Wyobraź sobie, co się stanie, jeśli będziemy mieli tablicę 50 kotów, z których 17 usunęliśmy w ten sposób. Będziemy mieli tablicę z 17 dołkami i będziemy się nimi opiekować! Zapamiętywanie na pamięć liczby pustych komórek, w których można zapisać nowe wartości, jest nierealne. Jeśli popełnisz raz błąd, nadpiszesz komórkę żądanym odniesieniem do obiektu. Jest oczywiście możliwość zrobienia tego nieco ostrożniej: po usunięciu należy przenieść elementy tablicy na początek, tak aby „dziura” znalazła się na końcu:
public static void main(String[] args) {
Cat[] cats = new Cat[4];
cats[0] = new Cat("Tomasz");
cats[1] = new Cat("Hipopotam");
cats[2] = new Cat(„Filip Markowicz”);
cats[3] = new Cat("Puch");
cats[1] = null;
for (int i = 2; i < cats.length-1; i++) {
//przenieś elementy na początek, tak aby pusta komórka znajdowała się na końcu
cats[i-1] = cats[i];
cats[i] = null;
}
System.out.println(Arrays.toString(cats));
}
Wniosek:
[Cat{name='Томас'}, Cat{name='ФLubпп Маркович'}, Cat{name='Пушок'}, null]
Teraz wydaje się, że wygląda to lepiej, ale trudno to nazwać stabilnym rozwiązaniem. Przynajmniej, ponieważ będziemy musieli pisać ten kod ręcznie za każdym razem, gdy usuniemy element z tablicy! Zła opcja. Możesz pójść w drugą stronę i stworzyć osobną metodę:
public void deleteCat(Cat[] cats, int indexToDelete) {
//...usuń kota według indeksu i przesuń elementy
}
Ale to też jest mało przydatne: ta metoda może działać tylko z obiektami Cat
, ale nie może działać z innymi. Oznacza to, że jeśli w programie jest jeszcze 100 klas, z którymi chcemy skorzystać z tablic, to w każdej z nich będziemy musieli napisać tę samą metodę z dokładnie tą samą logiką. To kompletna porażka -_- Ale w klasie ArrayList problem ten został pomyślnie rozwiązany! Implementuje specjalną metodę usuwania elementów - remove()
:
public static void main(String[] args) {
ArrayList<Cat> cats = new ArrayList<>();
Cat thomas = new Cat("Tomasz");
Cat behemoth = new Cat("Hipopotam");
Cat philipp = new Cat(„Filip Markowicz”);
Cat pushok = new Cat("Puch");
cats.add(thomas);
cats.add(behemoth);
cats.add(philipp);
cats.add(pushok);
System.out.println(cats.toString());
cats.remove(1);
System.out.println(cats.toString());
}
Przekazaliśmy do metody indeks naszego obiektu, który został usunięty (tak jak w tablicy). Metoda remove()
ma dwie cechy. Po pierwsze , nie pozostawia „dziur”. Implementuje już logikę przesuwania elementów przy usuwaniu elementu ze środka, którą wcześniej pisaliśmy ręcznie. Spójrz na wynik poprzedniego kodu w konsoli:
[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='ФLubпп Маркович'}, Cat{name='Пушок'}]
[Cat{name='Томас'}, Cat{name='ФLubпп Маркович'}, Cat{name='Пушок'}]
Usunęliśmy jednego kota ze środka, a pozostałe przesunęliśmy tak, żeby nie było żadnych przerw. Po drugie , może usunąć obiekt nie tylko według indeksu (jak zwykła tablica), ale także poprzez odniesienie do obiektu :
public static void main(String[] args) {
ArrayList<Cat> cats = new ArrayList<>();
Cat thomas = new Cat("Tomasz");
Cat behemoth = new Cat("Hipopotam");
Cat philipp = new Cat(„Filip Markowicz”);
Cat pushok = new Cat("Puch");
cats.add(thomas);
cats.add(behemoth);
cats.add(philipp);
cats.add(pushok);
System.out.println(cats.toString());
cats.remove(philipp);
System.out.println(cats.toString());
}
Wniosek:
[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='ФLubпп Маркович'}, Cat{name='Пушок'}]
[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Пушок'}]
Może to być bardzo wygodne, jeśli nie chcesz zawsze mieć w głowie indeksu żądanego obiektu. Wygląda na to, że uporaliśmy się ze zwykłym usuwaniem. Teraz wyobraźmy sobie taką sytuację: chcemy iterować po naszej liście elementów i usunąć kota o określonym imieniu. W tym celu używamy specjalnego operatora pętli for
- for each
. Więcej na ten temat dowiesz się z tego wykładu .
public static void main(String[] args) {
ArrayList<Cat> cats = new ArrayList<>();
Cat thomas = new Cat("Tomasz");
Cat behemoth = new Cat("Hipopotam");
Cat philipp = new Cat(„Filip Markowicz”);
Cat pushok = new Cat("Puch");
cats.add(thomas);
cats.add(behemoth);
cats.add(philipp);
cats.add(pushok);
for (Cat cat: cats) {
if (cat.name.equals("Hipopotam")) {
cats.remove(cat);
}
}
System.out.println(cats);
}
Kod wydaje się wyglądać całkiem logicznie. Jednak wynik może Cię zaskoczyć:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at Cat.main(Cat.java:25)
Jakiś błąd i nie jest jasne, dlaczego nagle się pojawił. W tym procesie istnieje wiele niuansów, którymi należy się zająć. Ogólna zasada, o której musisz pamiętać: Nie możesz iterować po kolekcji i jednocześnie zmieniać jej elementów. Tak, tak, dokładnie zmiana, a nie tylko usunięcie. Jeśli spróbujesz w naszym kodzie zastąpić usuwanie kotów wstawieniem nowych, efekt będzie taki sam:
for (Cat cat: cats) {
cats.add(new Cat(„Salem Saberhegen”));
}
System.out.println(cats);
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at Cat.main(Cat.java:25)
Zmieniliśmy jedną operację na drugą, ale wynik się nie zmienił: ten sam błąd ConcurrentModificationException
. Dzieje się tak właśnie wtedy, gdy podczas iteracji po niej próbujemy złamać regułę i zmienić listę. W Javie, aby usunąć elementy podczas iteracji, należy użyć specjalnego obiektu - iteratora (klasy Iterator
). Zadaniem klasy Iterator
jest bezpieczne przejście przez listę elementów. Jest to dość proste, ponieważ ma tylko 3 metody:
hasNext()
- zwracatrue
albofalse
w zależności od tego, czy na liście znajduje się kolejny element, czy też dotarliśmy już do ostatniego.next()
- zwraca kolejny element listyremove()
- usuwa element z listy
Iterator<Cat> catIterator = cats.iterator();//utwórz iterator
while(catIterator.hasNext()) {// dopóki na liście znajdują się elementy
Cat nextCat = catIterator.next();// pobierz następny element
System.out.println(nextCat);// wypisz to na konsoli
}
Wniosek:
Cat{name='Томас'}
Cat{name='Бегемот'}
Cat{name='ФLubпп Маркович'}
Cat{name='Пушок'}
Jak widać, klasa ArrayList
implementuje już specjalną metodę tworzenia iteratora - iterator()
. Należy również pamiętać, że tworząc iterator, określamy klasę obiektów, z którymi będzie on współpracował ( <Cat>
). Ostatecznie możemy łatwo rozwiązać nasz pierwotny problem za pomocą iteratora. Na przykład usuńmy kota o imieniu „Philip Markovich”:
Iterator<Cat> catIterator = cats.iterator();//utwórz iterator
while(catIterator.hasNext()) {// dopóki na liście znajdują się elementy
Cat nextCat = catIterator.next();// pobierz następny element
if (nextCat.name.equals(„Filip Markowicz”)) {
catIterator.remove();//usuń kota o żądanej nazwie
}
}
System.out.println(cats);
Wniosek:
[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Пушок'}]
Być może zauważyłeś, że w metodzie iteratora nie określiliśmy ani indeksu elementu, ani nazwy zmiennej referencyjnej remove()
! Iterator jest mądrzejszy niż mogłoby się wydawać: metoda remove()
usuwa ostatni element zwrócony przez iterator. Jak widać, zadziałało dokładnie tak, jak trzeba :) To w zasadzie wszystko, co musisz wiedzieć o usuwaniu elementów z ArrayList
. Dokładniej – prawie wszystko. Na kolejnym wykładzie zajrzymy do „wnętrza” tej klasy i zobaczymy, co się tam dzieje podczas operacji :) Do zobaczenia!
GO TO FULL VERSION