Frage

Schauen Sie sich diesen Code an.

// 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 möchte nur wissen, warum es passiert. Sollte der Compiler nicht einfach alle möglichen Ausgänge überprüfen?

War es hilfreich?

Lösung

Der statische Ergebnistyp des Ausdrucks (i2 == null) ? i1 : i2 ist der gemeinsame Vorfahr von i1 und i2 Welches ist Objekt. EIN for Aussage erfordert die statischer Typ des Ausdrucks, entweder ein Iterable oder ein Array -Typ. Dies ist nicht der Fall, also erhalten Sie einen Kompilierungsfehler.

Wenn Sie jetzt fragen, warum der Compiler das nicht schließt (i2 == null) ? i1 : i2 wird immer ein Array oder ein iterierbares sein:

  1. Die Java -Sprachspezifikation (JLS) erlaubt dies nicht.
  2. Wenn die JLS es zulassen müssten (aber nicht erforderlich), würden sich verschiedene Compiler unterschiedlich verhalten, je nachdem, wie gut sie bei Theorem beweisen. SCHLECHT.
  3. Wenn das JLS es verlangte, muss der Compiler einen ausgefeilten Theorem -Prover einbeziehen`1 (Schlecht langsam), und Sie treffen auf die "kleinen Unannehmlichkeit" der Lösung des Stoppproblems2 (SCHLECHT SCHLECHT SCHLECHT).
  4. Tatsächlich muss der Compiler wissen, welcher der beiden Arten von Typ, den der Ausdruck hat, da er jeweils einen anderen Code generieren muss.

Hypothetisch, wenn das Java -System etwas anders war, ist dieser spezielle Fall könnte besser behandelt werden. Insbesondere, wenn Java unterstützt wird Algebraische Datentypen dann wäre es möglich zu erklären o als "entweder ein Objektarray oder ein iterierbares" ... und die for Schleife wäre Typprüfbar.


1 - Angenommen, das o war initialisiert worden als o = (x * x < 0) ? new Object() : new Object[0]. Zu bestimmen, dass dies immer zu einem führt Object[] Die Instanz beinhaltet einen kleinen Beweis, der die Tatsache betrifft, dass das Quadrat einer (realen) Zahl nicht negativ ist. Dies ist ein einfaches Beispiel. Es ist möglich, willkürlich komplizierte Beispiele zu konstruieren, die willkürliche schwierige Beweise erfordert.

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

Andere Tipps

Um die Antwort von Stephen C zu 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 die Methodenüberladung zur Kompilierungszeit durchgeführt wird, können Sie feststellen, dass der statische Typ des ternären Operatorausdrucks Objekt ist, da sich die Operanden unterscheiden. Deshalb, wenn Sie dies tun:

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

Sie fordern den Compiler wirklich auf, eine Foreach -Schleife über ein Objekt zu generieren, was illegal ist.

In diesem Fall hat der bedingte Ausdruck den Typ der am wenigsten Obergrenze der beiden Typen, nämlich Object, und die Foreach -Schleife funktioniert nicht auf Typ Object

Sie sollten wirklich zwei überladene Deepprint (Object []) und Deepprint (Iterator I) -Methoden hinzufügen und den Versand in Ihrem Deepprint (Objektobjekt) durchführen. Ja, weil die Art der für/jede Schleife funktioniert, müssen Sie denselben Code mit geringfügigen Änderungen kopieren und einfügen.

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

Sie können die Code -Duplikation vermeiden, indem Sie das Objekt [] in eine Arrays.aSlist (Object []) einwickeln und dann in allen Fällen eine iterable haben. Ja, es ist etwas langsamer als an einem Array zu arbeiten, aber es hat den Vorteil, trocken zu sein, was immer die erste Annäherung sein sollte, IMHO.

Sie würden also so etwas haben:

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 die Antwort von Stephen C, wie es für Compiler -Autoren einfacher ist, ist aber auch ein einfacheres Sprachdesign), nimmt der ternäre Operator an, den Typ als der niedrigste gemeinsame Dämonator zwischen den beiden Rückgabetypen zu sein, anstatt einen zu begleiten, anstatt einen zu erhalten der beiden Rückgabetypen. Betrachten Sie den Fall einer Methodenüberlastung. Die richtige Methode wird bestimmt und kompiliert die Zeit. Dies bedeutet, dass der Compiler in der Lage sein muss, eine Entscheidung über den deklarierten Rückgabetyp zum Kompilierungszeit zu treffen, und kann diese Entscheidung, Zeit zu veranstalten, nicht verzögern.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top