Czy istnieje dobry powód do korzystania z interfejsu Java Collection?

11

Słyszałem argument, że powinieneś używać najbardziej ogólnego interfejsu, abyś nie był przywiązany do konkretnej implementacji tego interfejsu. Czy ta logika ma zastosowanie do interfejsów takich jak java.util.Collection ?

Wolałbym raczej zobaczyć coś takiego:

List<Foo> getFoos()

lub

Set<Foo> getFoos()

zamiast

Collection<Foo> getFoos()

W ostatnim przypadku nie wiem, z jakim zestawem danych mam do czynienia, natomiast w pierwszych dwóch przypadkach mogę poczynić pewne założenia dotyczące kolejności i wyjątkowości. Czy java.util.Collection ma użyteczność poza byciem logicznym rodzicem zarówno dla zestawów, jak i list?

Jeśli natrafiłeś na kod, który używał Collection podczas przeglądania kodu, w jaki sposób ustaliłbyś, czy jego użycie jest uzasadnione, i jakie sugestie sugerowałbyś, aby zastąpić go bardziej szczegółowym interfejsem?

Fil
źródło
3
Czy zauważyłeś w java.security.cert, że jest jeden typ zwrotu Collection<List<?>>? Mów o kodowaniu horroru!
Macneil
1
@Macneil Nie wiem, do której klasy się odwołujesz, ale taki typ zwrotu może być całkiem rozsądny. Mówi w zasadzie, że masz kolekcję (tj. Zawierającą kilka rzeczy, które nie mają rozsądnego porządku) list (tj. Zawierającą rzeczy, które mają rozsądną kolejność) obiektów (tj. Przedmiotów, których typów nie znamy statycznie jakikolwiek powód). Nie wydaje mi się to nierozsądne.
Zero3

Odpowiedzi:

13

Abstrakcje żyją dłużej niż implementacje

Ogólnie rzecz biorąc, im bardziej abstrakcyjny jest projekt, tym dłużej będzie on przydatny. Ponieważ kolekcja jest bardziej abstrakcyjna niż interfejsy, projekt interfejsu API oparty na kolekcji jest bardziej przydatny niż projekt oparty na liście.

Nadrzędną zasadą jest jednak stosowanie najbardziej odpowiedniej abstrakcji . Więc jeśli twoja kolekcja musi obsługiwać uporządkowane elementy, to upoważnij Listę, jeśli nie ma duplikatów, upoważnij Zestaw i tak dalej.

Uwaga na temat ogólnego projektu interfejsu

Ponieważ interesuje Cię korzystanie z interfejsu Collection w połączeniu z lekami generycznymi, możesz skorzystać z następujących wskazówek. Efektywna Java autorstwa Joshua Bloch zaleca następujące podejście przy projektowaniu interfejsu, który będzie polegał na rodzajach ogólnych: Producenci Rozszerz, Konsumenci Super

Jest to również znane jako reguła PECS . Zasadniczo, jeśli ogólne zbiory produkujące dane są przekazywane do twojej klasy, podpis powinien wyglądać następująco:

public void pushAll(Collection<? extends E> producerCollection) {}

Zatem typem wejściowym może być E lub dowolna podklasa E (E jest zdefiniowana jako zarówno sama superklasa, jak i podklasa w języku Java).

I odwrotnie, ogólna kolekcja przekazywana do konsumpcji danych powinna mieć taką sygnaturę:

public void popAll(Collection<? super E> consumerCollection) {}

Metoda będzie prawidłowo radzić sobie z każdym nadklasie E. Ogólnie, stosując takie podejście sprawi, że interfejs mniej zaskakujące dla użytkowników, ponieważ będziesz w stanie przejść w Collection<Number>a Collection<Integer>i mieć je traktować poprawnie.

Gary Rowe
źródło
6

CollectionInterfejs, a najbardziej liberalna forma Collection<?>, doskonale nadaje się do parametrów , które akceptują. Oparte na zależności od zastosowania w samej bibliotece Java jest bardziej powszechny jako typ parametru niż typ zwracany.

W przypadku typów zwrotów uważam, że twój punkt jest poprawny: jeśli ludzie mają uzyskać dostęp do niego, powinni znać kolejność (w sensie Big-O) wykonywanej operacji. Chciałbym powtórzyć Collectionzwrócony i dodać go do innej kolekcji, ale zadzwonienie wydaje się nieco szalonecontains , nie wiedząc, czy jest to operacja O (1), O (log n), czy O (n). Oczywiście, tylko dlatego, że masz Set, nie oznacza, że ​​jest to zbiór mieszany lub posortowany, ale w pewnym momencie przyjmiesz założenia, że ​​interfejs został racjonalnie zaimplementowany (a następnie będziesz musiał przejść do planu B, jeśli twoje założenie jest niepoprawny).

Jak wspomina Tom, czasami trzeba zwrócić a Collection, aby zachować enkapsulację: Nie chcesz, aby wyciekły szczegóły implementacji, nawet jeśli możesz zwrócić coś bardziej szczegółowego. Lub, w przypadku, o którym wspomniał Tom, możesz zwrócić bardziej konkretny pojemnik, ale wtedy musisz go zbudować.

Macneil
źródło
2
Myślę, że ten drugi punkt jest trochę słaby. Nie wiesz, jak będzie wyglądać kolekcja bez względu na to, czy jest to Kolekcja czy Lista - ponieważ są to tylko abstrakcje. Jeśli nie masz konkretnej klasy końcowej, tak naprawdę nie możesz powiedzieć.
Mark H
Ponadto, jeśli wiesz tylko, że coś jest kolekcją, nie masz pojęcia, czy może zawierać duplikaty, czy nie. Odwracając to, raz przypadek, w którym zwracanie Kolekcja może być odpowiednia, ma miejsce, jeśli masz kolekcję, która nie zawiera duplikatów i nie ma znaczącej kolejności (która oczywiście byłaby zestawem), ale gdzie z jakiegoś dobrego powodu wdrożenie metody zwracania używa listy. Nie chciałbyś zwracać Listy, ponieważ oznacza to znaczną kolejność, ale nie możesz zwrócić Zestawu, nie przechodząc rygoru tworzenia takiego. Więc oddajesz kolekcję.
Tom Anderson
@Tom: Dobra uwaga!
Macneil
5

Spojrzałbym na to z zupełnie przeciwnego punktu widzenia i zapytałem:

Jeśli podczas sprawdzania kodu natknąłeś się na kod, który wykorzystywał List <>, jak ustaliłbyś, czy jego użycie jest uzasadnione?

Bardzo łatwo to uzasadnić. Korzystasz z Listy, gdy potrzebujesz funkcji, która nie jest oferowana przez Kolekcję. Jeśli nie potrzebujesz tej dodatkowej funkcjonalności - jakie masz uzasadnienie? (I nie kupię „Wolałbym to zobaczyć”)

Istnieje wiele przypadków, w których będziesz używać kolekcji do celów tylko do odczytu, zapełniając ją naraz, iterując ją całkowicie - czy potrzebujesz indeksować ją ręcznie?

Dać prawdziwy przykład. Powiedzmy, że wykonuję proste zapytanie w bazie danych. ( SELECT id,name,rep FROM people WHERE name LIKE '%squared') Odbieram odpowiednie dane, wypełniam obiekty Person i umieszczam je w PersonList)

  • Czy muszę uzyskiwać dostęp według indeksu? - Byłoby bez znaczenia. Nie ma mapowania między indeksem a identyfikatorem.
  • Czy muszę wstawiać indeks? - Nie, DBMS zdecyduje, gdzie to umieścić, jeśli w ogóle dodam.

Jakie miałbym więc uzasadnienie dla tych dodatkowych metod? (które i tak nie zostałyby wprowadzone do mojej Listy Personalnej)

Mark H.
źródło
Uczciwy punkt. Przypuszczam, że moje pytanie odnosi się do konkretnego przypadku, w którym podczas przeglądu kodu ciągle widzę DAO zwracające Kolekcje, ale wiem, że te wywołania DAO zawsze będą zwracały zestawy jednostek; uważam, że w takich przypadkach typ zwracanego znaku wskazuje na wyjątkowość, a ta informacja jest pomocna dla każdego, kto musi użyć tej metody (np. nie muszę sprawdzać duplikatów).
Fil
Jeśli sprawdziłeś bazę danych, porównanie 2 wynikowych obiektów z equals () nie powinno w ogóle dać wartości true - więc potrzebujesz innego sposobu porównywania obiektów do duplikacji (np. Czy mają one tę samą nazwę, ten sam identyfikator, obie?). Musisz powiedzieć swojemu DAO, jak je porównać, jeśli ma usunąć same duplikaty - ale ponieważ to Ty decydujesz, czy duplikaty istnieją - łatwiej jest to po prostu zrobić z kolekcją z kodu wywołującego. (Aby uniknąć większej liczby warstw abstrakcji, aby poinformować DAO, jak wykonać każdą możliwą kontrolę równości na planecie).
Mark H
Zgodziliśmy się, ale używamy Hibernacji i upewniamy się, że nasze podmioty implementują equals (). Kiedy więc DAO zwraca jednostki, możemy bardzo szybko wykonać nową funkcję HashSet (). AddAll (wyniki) i zwrócić ją z powrotem do wywoływanej metody.
Fil