Powiedzmy, że piszę metodę, która powinna zwrócić Map . Na przykład:
public Map<String, Integer> foo() {
return new HashMap<String, Integer>();
}
Po chwili zastanowienia zdecydowałem, że nie ma powodu, aby modyfikować tę mapę po jej utworzeniu. Dlatego chciałbym zwrócić ImmutableMap .
public Map<String, Integer> foo() {
return ImmutableMap.of();
}
Czy powinienem pozostawić zwracany typ jako mapę ogólną, czy powinienem określić, że zwracam ImmutableMap?
Z jednej strony, właśnie po to stworzono interfejsy; aby ukryć szczegóły implementacji.
Z drugiej strony, jeśli zostawię to w ten sposób, inni programiści mogą przegapić fakt, że ten obiekt jest niezmienny. Dlatego nie osiągnę głównego celu, jakim są niezmienne obiekty; aby uczynić kod bardziej przejrzystym, minimalizując liczbę obiektów, które mogą się zmieniać. Co gorsza, po chwili ktoś może spróbować zmienić ten obiekt, a to spowoduje błąd w czasie wykonywania (kompilator nie ostrzeże o tym).
return ImmutableMap.of(somethingElse)
. Zamiast tego należy zapisaćImmutableMap.of(somethingElse)
jako pole i zwrócić tylko to pole. Wszystko to wpływa na projekt tutaj.Odpowiedzi:
Jeśli piszesz publicznie dostępny interfejs API i niezmienność jest ważnym aspektem twojego projektu, zdecydowanie przedstawiłbym to wyraźnie, albo mając nazwę metody wyraźnie oznaczającą, że zwrócona mapa będzie niezmienna, albo zwracając konkretny typ Mapa. Wspominanie o tym w javadoc moim zdaniem nie wystarczy.
Ponieważ najwyraźniej używasz implementacji Guava, spojrzałem na dokument i jest to klasa abstrakcyjna, więc daje ci trochę elastyczności w rzeczywistym, konkretnym typie.
Jeśli piszesz wewnętrzne narzędzie / bibliotekę, o wiele bardziej akceptowalne jest zwrócenie zwykłego pliku
Map
. Ludzie będą wiedzieli o wewnętrznych elementach kodu, do którego dzwonią, lub przynajmniej będą mieli do niego łatwy dostęp.Mój wniosek byłby taki, że wyraźne jest dobre, nie pozostawiaj rzeczy przypadkowi.
źródło
Map
typ interfejsu lubImmutableMap
typ implementacji.ImmutableMap
do guawy, rada zespołu Guava jest taka, aby rozważyćImmutableMap
interfejs z gwarancjami semantycznymi i zwrócić ten typ bezpośrednio.Powinieneś mieć
ImmutableMap
jako swój typ zwrotu.Map
zawiera metody, które nie są obsługiwane przez implementacjęImmutableMap
(np.put
) i są oznaczone@deprecated
wImmutableMap
.Używanie przestarzałych metod spowoduje ostrzeżenie kompilatora, a większość IDE ostrzeże, gdy ludzie spróbują użyć przestarzałych metod.
To zaawansowane ostrzeżenie jest lepsze niż stosowanie wyjątków środowiska wykonawczego jako pierwszej wskazówki, że coś jest nie tak.
źródło
Collections.immutableMap
wtedy?List
, nie ma gwarancji, żeList::add
jest ważna. SpróbujArrays.asList("a", "b").add("c");
. Nic nie wskazuje na to, że zakończy się niepowodzeniem w czasie wykonywania, ale tak się dzieje. Przynajmniej Guava cię ostrzega.Należy o tym wspomnieć w pliku javadocs. Wiesz, programiści je czytają.
Żaden programista nie publikuje nieprzetestowanego kodu. A kiedy to przetestuje, zostaje wyrzucony wyjątek, w którym nie tylko widzi przyczynę, ale także plik i wiersz, w którym próbował napisać na niezmiennej mapie.
Zwróć jednak uwagę, że tylko on
Map
sam będzie niezmienny, a nie obiekty, które zawiera.źródło
To prawda, ale inni programiści powinni przetestować swój kod i upewnić się, że jest on objęty ochroną.
Niemniej jednak masz jeszcze 2 opcje rozwiązania tego problemu:
Użyj Javadoc
@return a immutable map
Wybierz opisową nazwę metody
public Map<String, Integer> getImmutableMap() public Map<String, Integer> getUnmodifiableEntries()
Dla konkretnego przypadku użycia możesz nawet lepiej nazwać metody. Na przykład
public Map<String, Integer> getUnmodifiableCountByWords()
Co jeszcze możesz zrobić?!
Możesz zwrócić
Kopiuj
private Map<String, Integer> myMap; public Map<String, Integer> foo() { return new HashMap<String, Integer>(myMap); }
To podejście powinno być stosowane, jeśli spodziewasz się, że wielu klientów będzie modyfikować mapę i jeśli mapa zawiera tylko kilka wpisów.
CopyOnWriteMap
kolekcje copy on write są zwykle używane, gdy masz do czynienia ze
współbieżnością. Ale koncepcja pomoże ci również w twojej sytuacji, ponieważ CopyOnWriteMap tworzy kopię wewnętrznej struktury danych podczas operacji mutacyjnej (np. Dodaj, usuń).
W takim przypadku potrzebujesz cienkiej otoki wokół mapy, która deleguje wszystkie wywołania metod do podstawowej mapy, z wyjątkiem operacji mutacyjnych. W przypadku wywołania operacji mutacyjnej tworzy ona kopię mapy bazowej, a wszystkie dalsze wywołania zostaną delegowane do tej kopii.
Takie podejście należy zastosować, jeśli spodziewasz się, że niektórzy klienci zmodyfikują mapę.
Niestety java nie ma takiego pliku
CopyOnWriteMap
. Ale możesz znaleźć firmę zewnętrzną lub wdrożyć ją samodzielnie.W końcu powinieneś pamiętać, że elementy na twojej mapie mogą nadal być zmienne.
źródło
Zdecydowanie zwróć ImmutableMap, z uzasadnieniem:
źródło
To zależy od samej klasy. Guawa
ImmutableMap
nie ma być niezmiennym widokiem na zmienną klasę. Jeśli Twoja klasa jest niezmienna i ma jakąś strukturę, która jest w zasadzie typu anImmutableMap
, utwórz typ zwracanyImmutableMap
. Jeśli jednak twoja klasa jest zmienna, nie rób tego. Jeśli masz to:public ImmutableMap<String, Integer> foo() { return ImmutableMap.copyOf(internalMap); }
Guawa za każdym razem skopiuje mapę. To jest powolne. Ale jeśli
internalMap
już byłImmutableMap
, to jest w porządku.Jeśli nie ograniczysz swojej klasy do powrotu
ImmutableMap
, możesz zamiast tego wrócić w następującyCollections.unmodifiableMap
sposób:public Map<String, Integer> foo() { return Collections.unmodifiableMap(internalMap); }
Zauważ, że jest to niezmienny widok na mapę. Jeśli się
internalMap
zmieni, tak samo będzie z buforowaną kopiąCollections.unmodifiableMap(internalMap)
. Jednak nadal wolę to dla getterów.źródło
unmodifiableMap
jest niemodyfikowalny tylko przez posiadacza odniesienia - jest to widok, a posiadacz mapy podkładowej może modyfikować mapę podkładową, a następnie widok się zmienia. To ważna kwestia.Collections.unmodifiableMap
. Ta metoda zwraca widok oryginalnej mapy zamiast tworzenia oddzielnego, nowego obiektu mapy. Tak więc każdy inny kod odwołujący się do oryginalnej mapy może ją zmodyfikować, a następnie posiadacz rzekomo niemodyfikowalnej mapy dowiaduje się na własnej skórze, że mapa rzeczywiście może być modyfikowana pod nimi. Sam zostałem ugryziony przez ten fakt. Nauczyłem się albo zwracać kopię mapy, albo używać guawyImmutableMap
.Nie jest to dokładna odpowiedź na pytanie, ale nadal warto się zastanowić, czy mapa w ogóle powinna zostać zwrócona. Jeśli mapa jest niezmienna, podstawowa metoda, która ma być dostarczona, opiera się na get (klucz):
public Integer fooOf(String key) { return map.get(key); }
To sprawia, że API jest znacznie mocniejsze. Jeśli mapa jest rzeczywiście wymagana, można to pozostawić klientowi API, dostarczając strumień wpisów:
public Stream<Map.Entry<String, Integer>> foos() { map.entrySet().stream() }
Następnie klient może utworzyć własną niezmienną lub zmienną mapę według potrzeb lub dodać wpisy do własnej mapy. Jeśli klient chce wiedzieć, czy wartość istnieje, można zamiast tego zwrócić opcjonalne:
public Optional<Integer> fooOf(String key) { return Optional.ofNullable(map.get(key)); }
źródło
Niezmienna mapa to rodzaj mapy. Więc pozostawienie zwracanego typu mapy jest w porządku.
Aby upewnić się, że użytkownicy nie modyfikują zwracanego obiektu, dokumentacja metody może opisywać cechy zwracanego obiektu.
źródło
Jest to prawdopodobnie kwestia opinii, ale lepszym pomysłem jest tutaj użycie interfejsu dla klasy map. Ten interfejs nie musi wyraźnie mówić, że jest niezmienny, ale komunikat będzie taki sam, jeśli nie ujawnisz żadnej z metod ustawiających klasy nadrzędnej w interfejsie.
Spójrz na następujący artykuł:
andy gibson
źródło
Map
interfejsu jest to, że nie można go przekazać do żadnej funkcji API, która oczekuje aMap
bez rzutowania. Obejmuje to wszelkiego rodzaju konstruktory kopiujące innych typów map. Oprócz tego, jeśli zmienność jest tylko „ukryta” przez interfejs, użytkownicy mogą zawsze obejść ten problem, przesyłając i niszcząc kod w ten sposób.