W Javie musisz jawnie rzutować, aby sprowadzić zmienną
public class Fruit{} // parent class
public class Apple extends Fruit{} // child class
public static void main(String args[]) {
// An implicit upcast
Fruit parent = new Apple();
// An explicit downcast to Apple
Apple child = (Apple)parent;
}
Czy istnieje jakiś powód tego wymogu, poza tym, że java nie dokonuje żadnego wnioskowania typu?
Czy są jakieś „gotchas” z wdrożeniem automatycznego downcastingu w nowym języku?
Na przykład:
Apple child = parent; // no cast required
object-oriented
type-inference
language-design
Sam Washburn
źródło
źródło
Odpowiedzi:
Upcastowie zawsze odnoszą sukces.
Downcasty mogą powodować błąd środowiska wykonawczego, gdy typ środowiska obiektowego nie jest podtypem typu używanego w rzutowaniu.
Ponieważ druga jest niebezpieczną operacją, większość typowanych języków programowania wymaga od programisty wyraźnego poproszenia o to. Zasadniczo programista mówi kompilatorowi: „zaufaj mi, wiem lepiej - w czasie wykonywania będzie to OK”.
Jeśli chodzi o systemy typów, upcasty nakładają ciężar dowodu na kompilator (który musi to sprawdzić statycznie), downcasts nakładają ciężar dowodu na programistę (który musi się nad tym mocno zastanowić).
Można argumentować, że właściwie zaprojektowany język programowania całkowicie zabrania downcastów lub zapewnia bezpieczne metody rzutowania, np. Zwracanie opcjonalnego typu
Option<T>
. Jednak wiele powszechnych języków wybrało prostsze i bardziej pragmatyczne podejście polegające na zwykłym zwracaniuT
i zgłaszaniu błędu w inny sposób.W twoim konkretnym przykładzie kompilator mógł zostać zaprojektowany w taki sposób, aby wydedukować, że
parent
jestApple
to prosta analiza statyczna i umożliwić niejawne rzutowanie. Jednak ogólnie problem jest nierozstrzygalny, więc nie możemy oczekiwać, że kompilator wykona zbyt wiele magii.źródło
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
Apple
a następnie ta wiedza została odrzucona poprzez przechowanie referncji w zmiennej typuFruit
. Następnie chcesz użyć tego samego refernetu jakApple
ponownie.Ponieważ informacje zostały wyrzucone tylko „lokalnie”, kompilator mógł zachować wiedzę, która
parent
jest naprawdęApple
, mimo że deklarowany typ toFruit
.Ale zwykle nikt tego nie robi. Jeśli chcesz utworzyć
Apple
i używać go jakoApple
, przechowujesz go wApple
zmiennej, a nie wFruit
jednej.Kiedy masz
Fruit
i chcesz użyć go jakoApple
, zwykle oznacza to, żeFruit
uzyskałeś jakieś środki, które ogólnie mogą zwrócić dowolny rodzajFruit
, 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
parseFruit
funkcję, 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ćApple
i może chcieć użyćApple
metod, 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.
źródło
To, gdzie chcesz narysować linię. Możesz zaprojektować język, który wykryje ważność niejawnego downcastu:
Teraz wyodrębnijmy metodę:
Nadal jesteśmy dobrzy. Analiza statyczna jest znacznie trudniejsza, ale wciąż możliwa.
Ale problem pojawia się w momencie, gdy ktoś dodaje:
A teraz, gdzie chcesz zgłosić błąd? Stwarza to dylemat, można powiedzieć, że problem już istnieje
Apple child = parent;
, ale można to obalić przez „Ale to działało wcześniej”. Z drugiej strony, dodawanieeat(trouble);
spowodowało problem ”, ale sedno polimorfizmu polega na tym, aby dokładnie na to pozwolić.W takim przypadku możesz wykonać część pracy programisty, ale nie możesz tego zrobić do końca. Im dalej to zrobisz, zanim się poddasz, tym trudniej będzie ci wyjaśnić, co poszło nie tak. Dlatego lepiej zatrzymać się jak najszybciej, zgodnie z zasadą wczesnego zgłaszania błędów.
BTW, w Javie opisany przez ciebie downcast nie jest tak naprawdę downcastem. Jest to ogólna obsada, która równie dobrze może rzucać jabłka na pomarańcze. Tak więc technicznie rzecz biorąc pomysł @ chi jest już tutaj, nie ma spowolnień w Javie, tylko „everycast”. Sensowne byłoby zaprojektowanie wyspecjalizowanego operatora downcastu, który wyrzuci błąd kompilacji, gdy nie można znaleźć typu wyniku poniżej jego typu argumentu. Dobrym pomysłem byłoby uczynienie „everycastu” znacznie trudniejszym w użyciu, aby zniechęcić programistów do korzystania z niego bez bardzo dobrego powodu.
XXXXX_cast<type>(argument)
Przychodzi mi na myśl składnia C ++ .źródło