Automatyczne downcasting na podstawie typu


11

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 

Mówisz, że jeśli kompilator może wywnioskować, że obiekt zrzucany jest zawsze poprawnego typu, powinieneś być w stanie nie wyrazić wprost zrzuconego? Ale wtedy, w zależności od wersji kompilatora i tego, jak wiele wnioskowania może to zrobić, niektóre programy mogą być nieprawidłowe ... błędy w wnioskowaniu typu mogą uniemożliwić pisanie prawidłowych programów itp. ... nie ma sensu wprowadzać specjalnych jeśli zależy to od wielu czynników, nie byłoby intuicyjne dla użytkowników, gdyby musieli dodawać lub usuwać casty bez powodu przy każdym wydaniu.
Bakuriu

Odpowiedzi:


24

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 zwracaniu Ti 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 parentjest Appleto 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.


1
Dla przykładu, przykładem języka, który sprowadza się do opcjonalnych, jest Rust, ale to dlatego, że nie ma prawdziwego dziedzictwa, a jedynie „dowolny typ” .
Kroltan

3

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.


3

To, gdzie chcesz narysować linię. Możesz zaprojektować język, który wykryje ważność niejawnego downcastu:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Teraz wyodrębnijmy metodę:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    eat(parent);
}
public static void eat(Fruit parent) { 
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Nadal jesteśmy dobrzy. Analiza statyczna jest znacznie trudniejsza, ale wciąż możliwa.

Ale problem pojawia się w momencie, gdy ktoś dodaje:

public static void causeTrouble() { 
    // An implicit upcast 
    Fruit trouble = new Orange();
    eat(trouble);
}

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, dodawanie eat(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 ++ .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.