2009-08-18 2 views
2

Sehen Sie sich diesen Code an.Warum ist das Java ungültig? Typ des ternären Operatorausgangs

// 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 
    } 

Ich weiß, wie man es löst. Ich will nur wissen, warum es passiert. Sollte der Compiler nicht einfach alle möglichen Ausgaben überprüfen?

Antwort

8

Der statische Ergebnistyp des Ausdrucks (i2 == null) ? i1 : i2 ist der gemeinsame Vorfahre von i1 und i2 welches Objekt ist. Eine for-Anweisung erfordert den statischen Typ des Ausdrucks als Ausdruck entweder als Iterable oder als Array-Typ. Das ist nicht der Fall, Sie erhalten also einen Kompilierungsfehler.

Nun, wenn Sie fragen, warum nicht der Compiler ableiten, dass (i2 == null) ? i1 : i2 immer ein Array oder ein Iterable:

  1. Die Java-Sprachspezifikation (JLS) dies nicht zulässt.
  2. Wenn die JLS es erlaubte (aber nicht erforderte), dann würden sich verschiedene Compiler anders verhalten, je nachdem, wie gut sie beim Beweis des Theorems waren. SCHLECHT.
  3. Wenn die JLS es erforderlich ist, dann hat der Compiler eine anspruchsvolle Theorembeweisers `1 (BAD SLOW) zu übernehmen, und Sie laufen in die„kleine Unannehmlichkeit“zur Lösung des Halteproblem (BAD BAD BAD) .
  4. Tatsächlich muss der Compiler wissen, welche der beiden Arten von Typen der Ausdruck hat, weil er in jedem Fall einen anderen Code generieren muss.

Hypothetisch, wenn das Java-Typ-System ein bisschen anders ist, dieser spezielle Fall könnte besser gehandhabt werden. Insbesondere wenn Java algebraic data types unterstützt, dann wäre es möglich, o als "entweder ein Objekt-Array oder ein iterierbares" zu deklarieren ... und die for-Schleife wäre typenüberprüfbar.


1 - Nehmen wir an, o als o = (x * x < 0) ? new Object() : new Object[0] initialisiert worden war. Die Feststellung, dass dies immer zu einer Object[] Instanz führt, hat einen kleinen Beweis, der die Tatsache beinhaltet, dass das Quadrat einer (realen) Zahl nicht negativ ist. Das ist ein einfaches Beispiel, es ist möglich, beliebig komplizierte Beispiele zu konstruieren, die willkürliche schwierige Beweise erfordern.

2 - Das Halting-Problem hat sich mathematisch als unzutreffende Funktion erwiesen. Mit anderen Worten, es gibt Funktionen, bei denen es mathematisch unmöglich ist zu beweisen, ob sie enden oder nicht.

+0

Neugierig: Wenn Sie null auf (Iterable) werfen, würde das funktionieren? –

+0

@AlexFeinman - Nein, es wird nicht helfen. –

+0

+1 Obwohl es unklar ist, was genau Sie mit "Theoremprüfung" gemeint haben - bitte klären Sie das, wenn möglich. –

1

in diesem Fall hat der Bedingungsausdruck den Typ der die obere Grenze der beiden Typen, die Object und die foreach Schleife funktioniert nicht auf Typ Object

0

Sie sollten zwei überladene deepPrint (Object []) - und deepPrint (Iterator i) -Methoden wirklich hinzufügen und den Versand in Ihrem deepPrint (Object-Objekt) durchführen. Ja, wegen der Art und Weise, wie die for/each-Schleife funktioniert, müssen Sie den gleichen Code kopieren und einfügen, mit geringfügigen Änderungen.

Der Versuch, all das in eine große Methode zu bringen, ist ein Geruch.

2

Stephen C Antwort veranschaulichen, betrachten Sie den folgenden Code:

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"); 
} 

Dies ist die Ausgabe von Test():

method1(Object) called 
method1(Object) called 

Da Methode Überlastung zur Compile-Zeit geschehen ist, können Sie Beachten Sie, dass der statische Typ des ternären Operatorausdrucks Object ist, da sich die Typen der Operanden unterscheiden. Wenn Sie also tun:

for (Object o_ : i2 == null ? i1 : i2) 

Sie wirklich den Compiler bitten eine foreach-Schleife über ein Objekt zu erzeugen, was illegal ist.

0

Sie können die Code-Duplizierung vermeiden, indem Sie das Objekt [] in eine Arrays.asList (Object []) einschließen und dann in allen Fällen ein Iterable haben. Ja, es ist etwas langsamer als das Arbeiten an einem Array, aber es hat den Vorteil, dass es DRY ist, was immer die erste Annäherung sein sollte, IMHO.

So würden Sie etwas am Ende wie folgt:

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_); 
} 

Der Einfachheit halber (siehe Stephen C Antwort, wie es ist einfacher für Compiler Schriftsteller, aber es ist wohl eine einfachere Sprache Design als auch) die Der ternäre Operator nimmt an, dass der Typ der niedrigste gemeinsame Demonimator zwischen den beiden Rückgabetypen ist, und nicht einen der beiden Rückgabetypen. Betrachten Sie den Fall eines Überladens einer Methode. Die richtige Methode wird ermittelt und die Zeit kompiliert. Dies bedeutet, dass der Compiler in der Lage sein muss, zur Kompilierungszeit eine Entscheidung über den deklarierten Rückgabetyp zu treffen, und diese Entscheidung nicht zur Laufzeit verzögern kann.