Dzieje się tak z powodu, że używasz wyłącznie klas konkretnych i referencji do nich. Żeby kod nie trzeba było co rusz na nowo kompilować używa się abstrakcji takich jak referencje do interfejsów i klas abstrakcyjnych. Na przykład kod, który coś robi z listą lub mapą używa referencji do typu List lub Map, a nie do konkretnych klas takich jak np. ArrayList lub HashMap.
Wszędzie gdzie używasz jakiejś referencji do interfejsu lub klasy bazowej możesz w tym miejscu użyć referencji do obiektu klasy pochodnej lub implementującej ten interfejs. Bo obiekt klasy pochodnej lub implementującej interfejs JEST też obiektem klasy bazowej lub interfejsu. Ale w drugą stronę nie jest to prawdą.
Między innymi właśnie po to istnieje hierarchia interfejsów i klas. Im bardziej abstrakcyjny i oferujący mniej możliwości interfejs, tym więcej obiektów może go łatwo implementować, a kod napisany na bazie użycia takich abstrakcji jest całkowicie odporny na zmiany implementacji.
Na przykład w Javie 8 dodano wyrażenia lambda, które są implementacją bardzo prostych i bardzo abstrakcyjnych interfejsów funkcyjnych. Dzięki temu kod utworzony z ich użyciem jest niemal całkowicie odporny na zmiany struktury i implementacji konkretnych klas. Z klasami może dziać się wszystko byleby nadal implementowały pojedynczą metodę takiego interfejsu, tak jak wcześniej. Kod który operuje na takich referencjach w ogóle się nie zmienia i nie potrzeba go kompilować ponownie.