Problem: Mam listę klasy ArrayList, na której znajdują się powielone obiekty. Jak uzyskać z takiej listy listę unikalnych obiektów?
Po wrzuceniu takiego hasła do wyszukiwarki, jednym z wyników jest strona
https://www.baeldung.com/java-remove-duplicates-from-list
W przykładzie wykorzystam klasę HashSet, która identyfikuje obiekty poprzez ich hashCode (jeżeli kolejność obiektów ma być zachowana, należy użyć klasy LinkedHashSet).
Dla obiektów klasy Integer implementacja rozwiązania może wyglądać tak:
public class IntegerUniqueList { public void uniqueList() { ArrayList<Integer> list = new ArrayList(Arrays.asList(1,2,3,2,4,5,3,6,7)); printList(list); ArrayList<Integer> uniqueList = new ArrayList(new HashSet<>(list)); printList(uniqueList); } private void printList(ArrayList<Integer> list1) { System.out.println(""); list1.forEach(e -> { System.out.println(e); }); } }
Wynik działania powyższego kodu wygląda tak
list with duplicates 1 2 3 2 4 5 3 6 7 list without duplicates 1 2 3 4 5 6 7
Kolejny przykład to lista z obiektami zdefiniowanymi przez nas. Deklaruję własną klasę z dwoma polami: myClassId typu Integer oraz myText typu String.
public class MyClass { private Integer myClassId; private String myText; public MyClass(Integer myClassId, String myText) { this.myClassId = myClassId; this.myText = myText; } public Integer getMyClassId() { return myClassId; } public void setMyClassId(Integer myClassId) { this.myClassId = myClassId; } public String getMyText() { return myText; } public void setMyText(String myText) { this.myText = myText; } }
Poniżej metoda, której użyję do testów. Tworzę w niej pięć obiektów klasy MyClass, wśród których dwa obiekty mają w konstruktorze takie same parametry. Zakładam, że dwa obiekty są sobie równe, jeżeli wartości pól myClassId w tych obiektach są identyczne.
public class MyClassUniqueList { public void uniqueList() { ArrayList<MyClass> list = new ArrayList<>(); list.add(new MyClass(1, "value1")); list.add(new MyClass(2, "value2")); list.add(new MyClass(3, "value3")); list.add(new MyClass(2, "value2")); list.add(new MyClass(1, "value1")); printList("list with duplicates", list); ArrayList<MyClass> uniqueList = new ArrayList(new HashSet(list)); printList("list without duplicates", uniqueList); } private void printList(String info, ArrayList<MyClass> list) { System.out.println(info); list.forEach(e -> { System.out.println(e); }); } }
Wynik działania powyższego kodu wygląda tak
list with duplicates MyClass{myClassId=1, myText=value1} MyClass{myClassId=2, myText=value2} MyClass{myClassId=3, myText=value3} MyClass{myClassId=2, myText=value2} MyClass{myClassId=1, myText=value1} list without duplicates MyClass{myClassId=3, myText=value3} MyClass{myClassId=2, myText=value2} MyClass{myClassId=1, myText=value1} MyClass{myClassId=1, myText=value1} MyClass{myClassId=2, myText=value2}
Jak widać powyżej w tym przypadku to nie działa poprawnie (będąc bardzie precyzyjnym, program działa poprawnie, ale niezgodnie z naszymi oczekiwaniami). Powodem tego jest sposób porównywania obiektów w HashSet przy użyciu wartości hash danego obiektu. O tym, czym jest ta wartość można poczytać np. na stronie
https://javastart.pl/baza-wiedzy/programowanie-obiektowe/metoda-hashcode
W klasie MyClass do metody toString() dodam wyświetlanie hasha danego obiektu.
@Override public String toString() { return "MyClass{hashCode=" +hashCode()+"," + "myClassId=" + myClassId + ", myText=" + myText + '}'; }
Wynik działania metody uniqueList z klasy MyClassUniqueList wygląda teraz tak:
list with duplicates MyClass{hashCode=789451787,myClassId=1, myText=value1} MyClass{hashCode=1418481495,myClassId=2, myText=value2} MyClass{hashCode=303563356,myClassId=3, myText=value3} MyClass{hashCode=135721597,myClassId=2, myText=value2} MyClass{hashCode=142257191,myClassId=1, myText=value1} list without duplicates MyClass{hashCode=303563356,myClassId=3, myText=value3} MyClass{hashCode=789451787,myClassId=1, myText=value1} MyClass{hashCode=1418481495,myClassId=2, myText=value2} MyClass{hashCode=135721597,myClassId=2, myText=value2} MyClass{hashCode=142257191,myClassId=1, myText=value1}
Jak widać powyżej obiekty które mają takie same wartości pól mają różne hashe, dlatego program nie rozpoznał duplikatów.
W tym miejscu musimy sami zdecydować, kiedy obiekty klasy MyClass są identyczne. Jak wspomniałem wcześniej, przyjąłem, że wartość myClassId będzie określać, czy obiekty są takie same. Dlatego, aby to zrobić poprawnie, w klasie MyClass nadpiszę metodę hashCode(), aby zwracała ona wartość myClassId.
@Override public int hashCode() { return myClassId; }
Wynik działania jest teraz następujący
list with duplicates MyClass{hashCode=1,myClassId=1, myText=value1} MyClass{hashCode=2,myClassId=2, myText=value2} MyClass{hashCode=3,myClassId=3, myText=value3} MyClass{hashCode=2,myClassId=2, myText=value2} MyClass{hashCode=1,myClassId=1, myText=value1} list without duplicates MyClass{hashCode=1,myClassId=1, myText=value1} MyClass{hashCode=1,myClassId=1, myText=value1} MyClass{hashCode=2,myClassId=2, myText=value2} MyClass{hashCode=2,myClassId=2, myText=value2} MyClass{hashCode=3,myClassId=3, myText=value3}
Jak widać wartości hashCode dla obiektów odpowiadają wartościom myClassId, ale wciąż mam zduplikowane wpisy. Aby program zadziałał poprawnie, muszę jeszcze nadpisać metodę equals(…) dla klasy MyClass. NetBeans potrafi wygenerować taki kod dla tej metody
@Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final MyClass other = (MyClass) obj; if (!Objects.equals(this.myClassId, other.myClassId)) { return false; } return true; }
Po nadpisaniu w klasie MyClass metod hashCode() oraz equals(), wynik działania programu jest następujący:
list with duplicates MyClass{hashCode=1,myClassId=1, myText=value1} MyClass{hashCode=2,myClassId=2, myText=value2} MyClass{hashCode=3,myClassId=3, myText=value3} MyClass{hashCode=2,myClassId=2, myText=value2} MyClass{hashCode=1,myClassId=1, myText=value1} list without duplicates MyClass{hashCode=1,myClassId=1, myText=value1} MyClass{hashCode=2,myClassId=2, myText=value2} MyClass{hashCode=3,myClassId=3, myText=value3}
Jak widać otrzymałem listę bez zduplikowanych obiektów.
Kod z przykładów jest dostępny pod adresem