Zwykle downcasting to to, co robisz, gdy statystycznie znana wiedza kompilatora na temat typu czegoś jest mniej szczegółowa niż to, co wiesz (lub przynajmniej masz nadzieję).
W sytuacjach takich jak twój przykład obiekt został stworzony jako Applea następnie ta wiedza została odrzucona poprzez przechowanie referncji w zmiennej typu Fruit. Następnie chcesz użyć tego samego refernetu jak Appleponownie.
Ponieważ informacje zostały wyrzucone tylko „lokalnie”, kompilator mógł zachować wiedzę, która parentjest naprawdę Apple, mimo że deklarowany typ to Fruit.
Ale zwykle nikt tego nie robi. Jeśli chcesz utworzyć Applei używać go jako Apple, przechowujesz go w Applezmiennej, a nie w Fruitjednej.
Kiedy masz Fruiti chcesz użyć go jako Apple, zwykle oznacza to, że Fruituzyskałeś jakieś środki, które ogólnie mogą zwrócić dowolny rodzaj Fruit, ale w tym przypadku wiesz, że to był Apple. Prawie zawsze go nie zbudowałeś, przekazałeś go innym kodom.
Oczywistym przykładem jest sytuacja, w której mam parseFruitfunkcję, która może zamienić ciągi znaków takie jak „jabłko”, „pomarańcza”, „cytryna” itp. W odpowiednią podklasę; generalnie wszystko, co możemy (i kompilator) wie o tym, że funkcja zwraca jakąś Fruit, ale jeśli zadzwonię parseFruit("apple")potem ja wiem, że ma zamiar wezwać Applei może chcieć użyć Applemetod, więc mogłem przybity.
Znów wystarczająco inteligentny kompilator mógłby to tutaj zrozumieć, wstawiając kod źródłowy parseFruit, ponieważ wywołuję go ze stałą (chyba że jest w innym module i mamy osobną kompilację, jak w Javie). Ale powinieneś łatwo zobaczyć, jak bardziej skomplikowane przykłady zawierające informacje dynamiczne mogą stać się trudniejsze (a nawet niemożliwe!) Do zweryfikowania przez kompilator.
W realistycznym kodowaniu downcasty zwykle występują, gdy kompilator nie może zweryfikować, czy downcast jest bezpieczny przy użyciu metod ogólnych, a nie w tak prostych przypadkach, jak natychmiast po upcastingu wyrzucającym informacje tego samego typu, które próbujemy odzyskać przez downcasting.