Perché javac consente alcuni cast impossibili e non altri?


52

Se provo a trasmettere a Stringa java.util.Date, il compilatore Java rileva l'errore. Quindi perché il compilatore non contrassegna quanto segue come errore?

List<String> strList = new ArrayList<>();                                                                      
Date d = (Date) strList;

Ovviamente, la JVM lancia un ClassCastExceptionruntime, ma il compilatore non lo contrassegna.

Il comportamento è lo stesso con javac 1.8.0_212 e 11.0.2.


2
Niente di speciale Listqui. Date d = (Date) new Object();
Elliott Frisch,

1
Ultimamente ho suonato con un arduino. Mi piacerebbe un compilatore che non ha accettato felicemente alcun cast e poi li ha fatti con risultati totalmente imprevedibili. Stringa a intero? Cosa certa! Da doppio a intero? Si signore! Una stringa da booleana? Almeno quello per lo più diventa falso ...
Stian Yttervik il

@ElliottFrisch: esiste un'ovvia relazione ereditaria tra Data e Oggetto, ma non esiste alcuna relazione tra Data ed Elenco. Quindi mi aspettavo che il compilatore contrassegnasse questo cast, allo stesso modo in cui avrebbe contrassegnato un cast da String a Date. Ma come spiega Zabuza nella loro eccellente risposta, List è un'interfaccia, quindi il cast sarebbe legale se strListfosse un'istanza di una classe che implementa List.
Mike Woinoski,

Questa è una domanda frequente e sono sicuro di aver visto più duplicati. È fondamentalmente la versione inversa del fortemente correlato: stackoverflow.com/questions/21812289/…
Hulk

1
@StianYttervik -fpermissive è quello che sta facendo. Attiva gli avvisi del compilatore.
Bobsburner,

Risposte:


86

Il cast è tecnicamente possibile. Javac non può facilmente dimostrare che non è così nel tuo caso e JLS in realtà lo definisce come un programma Java valido, quindi contrassegnare un errore sarebbe errato.

Questo perché Listè un'interfaccia. Quindi potresti avere una sottoclasse di una Dateche implementa effettivamente Listmascherata come Listqui - e poi lanciarla su Datesarebbe perfettamente ok. Per esempio:

public class SneakyListDate extends Date implements List<Foo> {
    ...
}

E poi:

List<Foo> list = new SneakyListDate();
Date date = (Date) list; // This one is valid, compiles and runs just fine

Rilevare uno scenario del genere potrebbe non essere sempre possibile, poiché richiederebbe informazioni di runtime se l'istanza proviene, ad esempio, da un metodo. E anche se, richiederebbe molto più sforzo per il compilatore. Il compilatore impedisce solo i cast che sono assolutamente impossibili a causa della mancanza di un modo per l'albero di classe per abbinare affatto. Questo non è il caso qui, come visto.

Nota che JLS richiede che il tuo codice sia un programma Java valido. In 5.1.6.1. Conversione di riferimento ristretta consentita dice:

Esiste una conversione di riferimento restrittiva dal tipo Sdi riferimento al tipo di riferimento Tse sono vere tutte le seguenti condizioni :

  • [...]
  • Si applica uno dei seguenti casi :
    • [...]
    • Sè un tipo di interfaccia, Tè un tipo di classe e Tnon nomina una finalclasse.

Quindi, anche se il compilatore potrebbe capire che il tuo caso è effettivamente dimostrabilmente impossibile, non è consentito contrassegnare un errore perché JLS lo definisce come un programma Java valido.

Sarebbe consentito solo mostrare un avviso.


16
E vale la pena notare che il motivo per cui intercetta il caso con String è che String è definitiva, quindi il compilatore sa che nessuna classe può estenderlo.
Mentre ero

5
In realtà, non penso che sia la "finezza" di String che fa myDate = (Date) myStringfallire. Usando la terminologia JLS, l'istruzione tenta di convertire da S(il String) a T(il Date). Qui, Snon è un tipo di interfaccia, quindi la condizione JLS sopra citata non si applica. Ad esempio, prova a trasmettere un calendario a una data e otterrai un errore del compilatore anche se nessuna delle due classi è definitiva.
Mike Woinoski il

1
Non so se essere deluso dal compilatore o meno non può fare abbastanza analisi statiche per dimostrare che strList può essere sempre e solo di tipo ArrayList.
Giosuè il

3
Al compilatore non è proibito controllare. Ma è vietato chiamarlo un errore. Ciò renderebbe il compilatore non conforme. (Vedi la mia risposta ...)
Stephen C

3
Per aggiungere un po 'di gergo, il compilatore dovrebbe dimostrare che il tipo Date & Listè inabitabile , non è sufficiente per dimostrare che attualmente è disabitato (potrebbe essere in futuro).
Poligome

15

Consideriamo una generalizzazione del tuo esempio:

List<String> strList = someMethod();       
Date d = (Date) strList;

Questi sono i motivi principali per cui Date d = (Date) strList;non è un errore di compilazione.

  • Il motivo intuitivo è che il compilatore non (in generale) non conosce il tipo preciso dell'oggetto restituito da quella chiamata al metodo. È possibile che oltre ad essere una classe che implementa List, sia anche una sottoclasse di Date.

  • Il motivo tecnico è che la specifica del linguaggio Java "consente" la conversione del riferimento restrittivo che corrisponde a questo cast di tipo. Secondo JLS 5.1.6.1 :

    "Esiste una conversione di riferimento restrittiva dal tipo Sdi riferimento al tipo di riferimento Tse sono vere tutte le seguenti condizioni:"

    ...

    5) " Sè un tipo di interfaccia, Tè un tipo di classe e Tnon nomina una finalclasse."

    ...

    In un posto diverso, JLS afferma anche che un'eccezione può essere generata in fase di esecuzione ...

    Si noti che la determinazione JLS 5.1.6.1 si basa esclusivamente sui tipi dichiarati delle variabili interessate anziché sui tipi di runtime effettivi. Nel caso generale, il compilatore non conosce e non può conoscere i tipi di runtime effettivi.


Quindi, perché il compilatore Java non può capire che il cast non funzionerà?

  • Nel mio esempio, la someMethodchiamata potrebbe restituire oggetti con una varietà di tipi. Anche se il compilatore è stato in grado di analizzare il corpo del metodo e determinare l'insieme preciso di tipi che potrebbero essere restituiti, non c'è nulla che impedisca a qualcuno di cambiarlo per restituire tipi diversi ... dopo aver compilato il codice che lo chiama. Questo è il motivo di base per cui JLS 5.1.6.1 dice quello che dice.

  • Nel tuo esempio, un compilatore intelligente potrebbe capire che il cast non potrà mai avere successo. Ed è consentito emettere un avviso in fase di compilazione per evidenziare il problema.

Quindi perché un compilatore intelligente non è autorizzato a dire che si tratta comunque di un errore?

  • Perché JLS afferma che si tratta di un programma valido. Periodo. Qualsiasi compilatore che lo chiamasse errore non sarebbe conforme a Java.

  • Inoltre, qualsiasi compilatore che rifiuta i programmi Java ritenuti validi da JLS e altri compilatori costituisce un ostacolo alla portabilità del codice sorgente Java.


4
Considerare il fatto che dopo aver compilato la classe chiamante l'implementazione della funzione chiamata può cambiare , quindi anche se è dimostrabile al momento della compilazione, con l'implementazione attuale del callee, che il cast è impossibile, ciò potrebbe non essere così in tempi di esecuzione successivi quando la chiamata è cambiata o è stata sostituita.
Peter - Ripristina Monica il

2
Votazione per evidenziare il problema della portabilità che verrebbe introdotto se un compilatore provasse a essere troppo intelligente.
Mike Woinoski il

2

5.5.1. Tipo di riferimento Casting:

Dato un tipo di riferimento in fase di compilazione S(origine) e un tipo di riferimento in fase di compilazione T(destinazione), esiste una conversione di fusione da Sa Tse non si verificano errori in fase di compilazione a causa delle seguenti regole.

[...]

Se Sè un tipo di interfaccia:

  • [...]

  • Se Tè un tipo di classe o interfaccia non è definitiva, quindi se esiste un supertipo Xdi T, e un supertipo Ydi Stale che entrambi Xe Ysono tipi parametrici dimostrabilmente distinti, e che le cancellature Xe Ysono uguali, un errore di compilazione si verifica.

    Altrimenti, il cast è sempre legale al momento della compilazione (perché anche se Tnon implementa S, una sottoclasse di Tpotenza).

List<String>è Sed Dateè Tnel tuo caso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.