Jest jeszcze jedna rzecz, dotycząca klas. Czasem używając klasy pokazujesz coś więcej, niż tylko interfejs. Dajesz jakieś mocniejsze obietnice, niż to się może wydawać. Bo każda implementacja ma swoje "zachowanie". A nie tylko implementowany interfejs. Czasem problem wyskakuje nawet wtedy, gdy nie chcesz zmienić typów, a tylko "cokolwiek poprawić". Podam przykład, akurat z realnego życia, żeby wyjaśnić, co mam na myśli. I też map dotyczy.
Oprócz HashMap istnieje również LinkedHashMap. Klasa LinkedHashMap
nie dodaje nowych metod publicznych, składa jednak obietnicę: zachowuje kolejność wstawiania elementów. Pisząc funkcję foo
dajesz znać, że używający mogą polegać na kolejności elementów w mapie.
LinkedHashMap<?,?> foo() {
LinkedHashMap result = ...
// ...
return result;
}
I nagle się okazuje, że zmieniając cokolwiek w funkcji foo
, robiąc po prostu coś w innej kolejności, rozsypujesz program w innych miejscach. Gdyby funkcja zwracała po prostu Map
, to nikomu by nie przyszło do głowy polegać na kolejności kluczy. Wypuszczając w świat LinkedHashMap
dałeś taką obietnicę, że teraz nawet ciężko jest namierzyć fragmenty programu, które mogą się zepsuć. W żaden sposób kompilator nie pomoże, bo ta obietnica ("zachowuje kolejność kluczy") nie jest formalnie wcale opisana.
Być może wcale tak silnych gwarancji nie chciałeś dawać. Być może użyłeś tej klasy z zupełnie innego powodu (np użyłeś subklasy LinkedHashMap
bo chciałeś pilnować rozmiaru mapy za pomocą chronionej metody removeEldestEntry
). Zdradzając typ nie udostępniłeś nowych metod. A jednak udostępniłeś informację o "zwyczajach" - taki "nieformalny wyciek informacji" potwornie związuje Ci ręce, pozwala czasem robić klientowi rzeczy, których nawet się nie spodziewałeś. Dając tylko interfejs masz pewność, że nikt uczciwy nie będzie polegał na żadnym "zwyczaju" / "szczególe implementacyjnym".