Perché questo è valido Java? Tipo di uscita operatore ternario
-
18-09-2019 - |
Domanda
Dai un'occhiata a questo codice.
// Print object and recurse if iterable
private static void deep_print(Object o) {
System.out.println(o.getClass().toString() + ", " + o.toString());
boolean iter = false;
Iterable<?> i1 = null;
Object[] i2 = null;
if (o instanceof Iterable<?>) {
iter = true;
i1 = (Iterable<?>) o;
} else if (o instanceof Object[]) {
iter = true;
i2 = (Object[]) o;
}
if (iter) {
for (Object o_ : i2 == null ? i1 : i2) deep_print(o_); // ERROR: Can only iterate over an array or an instance of java.lang.Iterable
}
Io so come risolverlo. Voglio solo sapere perché succede. Non dovrebbe il compilatore semplicemente visualizzate tutte le possibili uscite?
Soluzione
Il tipo di risultato statico del (i2 == null) ? i1 : i2
espressione è l'antenato comune i1
e i2
che è oggetto. Una dichiarazione for
richiede l' tipo statico dell'espressione essere o un Iterable
o un tipo di matrice. Non è questo il caso, in modo da ottenere un errore di compilazione.
Ora, se ti stai chiedendo perché non il compilatore dedurre che (i2 == null) ? i1 : i2
sarà sempre un array o un Iterable:
- La specifica del linguaggio Java (JLS) non consente questo.
- Se il JLS lo permetteva (ma non lo richiedono), quindi diversi compilatori si comporterebbe in modo diverso, a seconda di quanto fossero in teoremi. BAD.
- Se la JLS richiesto, allora il compilatore deve incorporare un dimostratore sofisticato teorema `1 (BAD SLOW), e si esegue in il "piccolo inconveniente" di risolvere l'arresto Problema 2 (male male male).
- In realtà il compilatore deve conoscere quale dei due tipi di tipo dell'espressione è perché deve generare codice diverso in ciascun caso.
Ipoteticamente, se il sistema di tipo Java è stato un po 'diverso, questo caso particolare potrebbero essere gestito meglio. Nello specifico, se Java supportato allora sarebbe possibile dichiarare o
come "sia una matrice o un oggetto iterabile" ... e il ciclo for
sarebbero tipo controllabile essere.
1 - Supponiamo che o
era stato inizializzato come o = (x * x < 0) ? new Object() : new Object[0]
. Determinare che ciò comporta sempre un'istanza Object[]
comporta una piccola prova coinvolge il fatto che il quadrato di un numero (reale) non è negativo. Questo è un semplice esempio, è possibile costruire esempi arbitrariamente complicati da prove difficili arbitrari.
2 - l'arresto problema è matematicamente dimostrato di essere una funzione incomputable. In altre parole, esistono funzioni in cui è matematicamente impossibile dimostrare se essi terminano.
Altri suggerimenti
Per illustrare la risposta di Stephen C, si consideri il seguente codice:
void test() {
Iterable<Integer> i1 = new ArrayList<Integer>();
Object[] i2 = { 1, 2, 3 };
method1(false ? i1 : i2);
method1(true ? i1 : i2);
}
void method1(Object o) {
System.out.println("method1(Object) called");
}
void method1(Object[] o) {
System.out.println("method1(Object[]) called");
}
void method1(Iterable<?> o) {
System.out.println("method1(Iterable<?>) called");
}
Questa è l'uscita di test ():
method1(Object) called
method1(Object) called
Poiché sovraccarico metodo viene eseguito al momento della compilazione, si può vedere che il tipo statico dell'espressione operatore ternario è oggetto, poiché i tipi di operandi differiscono. Pertanto, quando si fa:
for (Object o_ : i2 == null ? i1 : i2)
si sta veramente chiedendo al compilatore di generare un ciclo foreach su un oggetto, che è illegale.
in questo caso, l'espressione condizionale ha il tipo di estremo superiore dei due tipi, che è Object
, e il ciclo foreach non funziona sul tipo Object
Si dovrebbe aggiungere due metodi di overload deepPrint (Object []) e deepPrint (Iterator i) e fare l'invio nel vostro deepPrint (oggetto Object). Sì a causa della natura di come il per / ogni ciclo funziona avrete bisogno di copiare e incollare lo stesso codice con lievi modifiche.
Cercando di mettere tutto ciò in un unico grande metodo è un odore.
È possibile evitare la duplicazione del codice avvolgendo l'oggetto [] in un Arrays.asList (Object []) e poi avere un Iterable in tutti i casi. Sì, è leggermente più lento di lavorare su un array, ma ha il vantaggio di essere DRY, che deve sempre essere la prima approssimazione, IMHO.
Quindi, si finirebbe con qualcosa di simile:
Iterable<?> it = null;
if (o instanceof Iterable<?>) {
it = (Iterable<?>) o;
} else if (o instanceof Object[]) {
it = Arrays.asList((Object[]) o);
}
if (it != null) {
for (Object o_ : it) deep_print(o_);
}
Per semplicità (si veda la risposta di Stephen C per come è più semplice per gli scrittori del compilatore, ma è senza dubbio un disegno linguaggio più semplice come pure) l'operatore ternario assume il tipo di essere il minimo comune denominatore tra i due tipi di ritorno, piuttosto che accomodante uno dei due tipi restituiti. Consideriamo il caso di un metodo di sovraccarico. Il metodo giusto è determinato e tempo di compilazione. Ciò significa che il compilatore deve essere in grado di prendere una decisione sul tipo di ritorno dichiarato in fase di compilazione, e non può ritardare la decisione di correre il tempo.