Java-IAQ: Selten Gestellte Fragen

Java-IAQ: Selten Gestellte Fragen
Copyright © norvig.com
For original English text, go to: http://www.norvig.com
Translated by A.Romanova

durch Peter Norvig

F: Was ist eine Selten Gestellte Frage?

Auf eine Frage wird selten geantwortet, entweder weil wenige Menschen die Antwort wissen, oder weil es über einen verbogenen, zarten Punkt ist (aber ein Punkt, der entscheidend für Sie sein kann). Ich dachte ich habe den Begriff erfunden, aber zeigt Informationen auf About.com Urban Legends Seite auf. Es gibt viele javanische FAQ ringsherum, aber das ist das einzige Java IAQ. (Es gibt einige Selten Gestellte Frage-Listen, einschließlich einer satirischen auf C.)

F: In einem final Code, der Satz wird nie durchführen, richtig?

So, kaum jemals. AAber hier ist ein Beispiel, wo der finale Code nicht ausgeführt wird, unabhängig von dem Wert des boolean Wahl:

  try {
    if (choice) {
      while (true) ;
    } else {
      System.exit(1);
    }
  } finally {
    code.to.cleanup();
  }

F: Innerhalb einer Methode m in einer Klasse C, vertritt das this.getClass () nicht immer C?

Nein. Es ist möglich für einen Gegenstand x, der ein Beispiel von einer Unterklasse C1 von C ist, entweder gibt es keine C1.m () Methode, oder eine Methode auf x, genannt super.m (). In beide this.getClass () ist C1, nicht C innerhalb der C.m (). Wenn C endlich ist, dann sind Sie in Ordnung.

F: Ich habe die Equals-Methode definiert, aber Hashtable ignoriert das. Warum?

Equals-Methoden sind überraschend hart in Ordnung zu bringen. Hier gibt es die Hinweise, erstens nach einem Problem zu suchen:

1. Sie haben falsch die Equals-Methode definiert.  Zum Beispiel haben Sie geschrieben:

public class C {
  public boolean equals(C that) { return id(this) == id(that); }
}

Aber damit table.get (c) zu funktionieren, müssen Sie die Equals-Methode bestimmen, das Objekt als das Argument anzunehmen, aber nicht einen C:

public class C {
  public boolean equals(Object that) {
    return (that instanceof C) && id(this) == id((C)that);
  }
}

Warum? Der Code für Hashtable.get sieht so etwas wie dieses aus:

public class Hashtable {
  public Object get(Object key) {
    Object entry;
    ...
    if (entry.equals(key)) ...
  }
}

Jetzt die angerufene Methode von entry.equals (key), hängt vom aktuellen Laufzeittyp des Objekts ab, verwiesen durch Eingabe, und angegeben durch Kompilierzeittyp des variablen Schlüssels. So, wenn Sie als ein Benutzer table.get (new C (…)) einsteigen, das sucht in der Klasse C nach der Equals-Methode mit dem Argument des Typs-Objekt. Wenn Sie zufällig eine Equals-Methode mit einem Argument des Typs C definiert haben, ist das irrelevant. Es ignoriert diese Methode, und sucht nach einer Methode mit dem Zeichen Equals (Objekt), schließlich Object.equals (Object) findend. Wenn Sie eine Methode überschreiben wollen, müssen Sie Argument-Typen genau vergleichen. In einigen Fällen möchten Sie vielleicht zwei Methoden haben, so dass Sie den Gemeinkosten des Gussteiles nicht bezahlen, wenn Sie wissen, dass Sie ein Objekt der richtigen Klasse besitzen:

public class C {
  public boolean equals(Object that) {
    return (this == that)
            || ((that instanceof C) && this.equals((C)that));
  }

  public boolean equals(C that) {
    return id(this) == id(that); // Or whatever is appropriate for class C
  }
}

2. Sie haben nicht richtig Equals als ein Gleichheitsvergleichselement eingeführt: Gleichen müssen symmetrisch, transitiv, und reflexiv sein. Symmetrisch bedeutet a.equals (b) muss denselben Wert wie b.equals (a) haben. (Das ist dasjenige, das die meisten Menschen in Verwirrung bringen.) Transitiv bedeutet wenn a.equals (b) und b.equals (c), dann a.equals (c) muss wahr sein. Reflexiv bedeutet a.equals (a) wahr sein muss, und der Grund dafür ist (dieser == jener) oben gestellten Test (es ist auch häufig gute Praxis, um das wegen Leistungsfähigkeitsgründe einzuschließen: Prüfung für == ist schneller als das Suchen nach allen Ablagefächern eines Objekts, und teilweise das Rekursion-Problem auf Gegenständen zu brechen, die kreisförmige Zeigestock-Ketten haben könnten).

3. Sie haben die hashCode Methode vergessen. Jederzeit Sie eine Equals-Methode definieren, sollten auch eine hashCode Methode definieren. Sie müssen sicherstellen, dass zwei gleiche Gegenstände denselben hashCode enthalten, und wenn Sie bessere hashtable Leistung wollen, sollten Sie versuchen, die meisten nichtgleichen Objekten enthalten verschiedenen hashCodes. Einige Klassen verstecken den hashCode in einem privaten Ablagefach eines Objektes, so dass er nur einmal geschätzt werden muss. Wenn den Fall gibt’s, werden Sie wahrscheinlich Zeit darin Equals sparen, wenn Sie eine Linie einschließen, sagt (this.hashSlot! = that.hashSlot) falsch zurückgeben.

4. Sie haben die Vererbung nicht richtig durchgeführt. Zuallererst, ziehen Sie in Betracht, ob zwei Objekten der verschiedenen Klasse gleich sein können. Bevor Sie “NEIN! Natürlich nicht!” sagen, betrachten ein Klassenrechteck mit Breite- und Höhe-Feldern und eine Kasten-Klasse, die die obengenannten zwei Felder plus die Tiefe hat. Ist ein Kasten mit der Tiefe == 0 gleich zu dem gleichwertigen Rechteck? Vielleicht wollen Sie ja sagen. Wenn Sie sich mit einer nicht-finalen Klasse befassen, dann vielleicht Ihre Klasse subklassifiziert werden könnte, und Sie wollen ein guter Bürger in Bezug auf Ihre Unterklasse sein. Insbesondere wollen Sie einem Extender von Ihrer Klasse C erlauben, Ihre C.equals Methode zu verwenden, wie folgt:

public class C2 extends C {

  int newField = 0;

  public boolean equals(Object that) {
    if (this == that) return true;
    else if (!(that instanceof C2)) return false;
    else return this.newField == ((C2)that).newField && super.equals(that);
  }

}

Zu erlauben das zu funktionieren, müssen Sie darüber sorgfältig sein, wie Sie Klassen in Ihrer Definition von C.equals behandeln. Überprüfen Sie Beispiel C statt that.getClass () == C.class. Siehe die vorherige IAQ Frage damit zu erfahren. Verwenden Sie this.getClass () == that.getClass (), wenn Sie überzeugt sind, dass zwei Objekte von derselben Klasse sein müssen, betrachtenden wie gleich.

5. Sie haben kreisförmige Verweisungen nicht richtig behandelt. Analysieren:

public class LinkedList {

  Object contents;
  LinkedList next = null;

  public boolean equals(Object that) {
    return (this == that)
      || ((that instanceof LinkedList) && this.equals((LinkedList)that));
  }

  public boolean equals(LinkedList that) { // Buggy!
   return Util.equals(this.contents, that.contents) &&
          Util.equals(this.next, that.next);
  }

}

Da habe ich angenommen, es gibt eine Util Klasse:

  public static boolean equals(Object x, Object y) {
    return (x == y) || (x != null && x.equals(y));
  }

Ich wünsche diese Methode im Objekt wäre; ohne es müssen Sie immer in Tests gegen die Null werfen. Irgendwie wird die LinkedList.equals Methode nie zurückkommen, wenn aufgefordert wird, zwei LinkedLists mit dem Zirkelbezug in ihnen zu vergleichen (ein Symbol aus einem Element der verbundenen Liste zurück zu einem anderen Element). Siehe die Beschreibung der Common Lisp Funktionslistenlänge für eine Erklärung dessen, wie man dieses Problem in der geradlinigen Zeit mit nur zwei Wörtern von zusätzlichem Speicher behandelt. (Ich gebe die Antwort hier nicht, im Falle wollen Sie das versuchen, für sich selbst zuerst zu verstehen.)

F: Ich habe versucht, eine Methode zu super nachzuschicken, aber gelegentlich funktioniert nicht. Warum?

Das ist der fragliche Code, vereinfacht für dieses Beispiel:

/** A version of Hashtable that lets you do
 * table.put("dog", "canine");, and then have
 * table.get("dogs") return "canine". **/

public class HashtableWithPlurals extends Hashtable {

  /** Make the table map both key and key + "s" to value. **/
  public Object put(Object key, Object value) {
    super.put(key + "s", value);
    return super.put(key, value);
  }
}

Sie müssen vorsichtig sein, beim Passieren zu super, dass Sie voll und ganz verstehen, was das super Methode ist. In diesem Fall, der Vertrag für Hashtable.put, wird die Zuordnung zwischen dem Schlüssel und dem Wert in der Tabelle erfassen. Jedoch, wenn hashtable zu voll wird, dann Hashtable.put wird eine größere Reihe für die Tabelle zuteilen, alle alten Objekten kopieren, und dann rekursiv table.put (Schlüssel, Wert) zurückrufen. Jetzt, weil javanische Entschlossenheitsmethoden, die auf dem Laufzeittyp des Ziels in unserem Beispiel gestützt sind, dieser rekursive Anruf innerhalb des Codes für Hashtable wird zu HashtableWithPlurals.put (Schlüssel, Wert), und das Nettoergebnis gehen, darin bestehen, dass gelegentlich (wenn die Größe des Tisches in gerade der falschen Zeit überfließt) Sie einen Zugang für “Hundee” sowie für “Hunde” und “Hund” bekommen werden. Jetzt gibt das in keine Dokumentation, ist das eine Möglichkeit den rekursiven Anruf einfügen? Nein. In solchen Fällen es hilft nur sicher, Quellcode-Zugang zum JDK zu lassen.

F: Warum ignoriert meine Objekteigenschaft den Default, wenn ich ein GET steige ein?

Sie sollten kein GET auf eine Objekteigenschaft einsteigen; Sie sollten einen getProperty stattdessen tun. Viele Menschen nehmen an, dass der einzige Unterschied ist, dass getProperty einen offen erklärten Rücktyp der Schnur hat, während GET erklärt wird, zu einen Objekt zurückzukehren. Wirklich gibt es einen größeren Unterschied: getProperty zeigt den defaults.get wird von Hashtable geerbt, und er ignoriert den Default, dadurch erfüllt, was in der Hashtable Klasse dokumentiert wird, aber wahrscheinlich nicht darauf Sie erwarten. Andere Methoden geerbten aus Hashtable (wie isEmpty und toString) werden auch den Default ignorieren. Beispiel-Code:

Properties defaults = new Properties();
defaults.put("color", "black");

Properties props = new Properties(defaults);

System.out.println(props.get("color") + ", " +
props.getProperty(color));
// This prints "null, black"

Wird das durch die Dokumentation gerechtfertigt? Vielleicht. Die Dokumentation in Hashtable spricht über Einträge in der Tabelle, und das Verhalten von Eigenschaften vereinbar ist, wenn Sie annehmen, dass Defaults keine Einträge in der Tabelle vertreten. Wenn aus irgendeinem Grund Sie gedacht haben, dass Defaults Einträge vertreten (weil Sie dazu gebracht werden könnten, durch das Verhalten von getProperty zu glauben), dann werden Sie verwirrt sein.

F: Vererbung scheint fehleranfällig zu sein. Wie kann ich vor diesen Fehlern schützen?

Die vorherigen zwei Fragen zeigen, ein Programmierer braucht sehr sorgfältig zu sein, wenn man eine Klasse vergrößert, und manchmal im Verwenden einer Klasse, die eine andere Klasse erweitert. Probleme wie diese zwei bringen John Ousterhout dazu zu sagen, dass “Durchführungsvererbung verursacht dasselbe Verflechtung und Sprödigkeit, die beobachtet worden sind, wenn goto Behauptungen überbeansprucht sind. Infolgedessen leiden OO Systeme häufig unter der Kompliziertheit und fehlen vom Wiedergebrauch. ” (Scripting, IEEE Computer, März 1998) und Edsger Dijkstra angeblich zu sagen, dass “Objektorientierte Programmierung ist eine außergewöhnlich schlechte Idee, die nur in Kalifornien entstanden sein könnte.” (von einer Sammlung von Signaturdateien). Ich denke nicht, es gibt eine allgemeine Weise um versichern zu lassen, aber es gibt einige Dinge, darüber man bewusst sein soll:

  • Die Erweiterung einer Klasse, wofür Sie keinen Quellcode haben, ist immer unsicher; die Dokumentation kann auf Weisen unvollständig sein, wie Sie nicht voraussehen können.
  • Super anzurufen, lässt diese ungeahnten Probleme ausspringen.
  • Sie müssen so viel Aufmerksamkeit auf die Methoden schenken, die Sie nicht wie die Methoden überschreiben, die Sie anwenden. Das ist einer der großen Irrtum des Objektorientierten Designs mit der Vererbung. Natürlich die Vererbung lässt Sie weniger Code schreiben. Aber Sie müssen noch an den Code denken, den Sie nicht schreiben.
  • Sie suchen besonders nach Schwierigkeiten, wenn die Unterklasse den Vertrag von einigen der Methoden, oder von der Klasse als Ganzes ändert. Es ist schwierig zu bestimmen, wenn ein Vertrag geändert wird, da Verträge informell sind (es gibt einen formellen Teil in der Typ-Unterschrift, aber der Rest erscheint nur in Kommentaren). Im Eigenschaften-Beispiel ist es nicht klar wenn ein Vertrag gebrochen wird, weil es nicht klar ist, wenn der Default als “Einträge” in der Tabelle betrachtet werden soll oder nicht.

F: Welche sind andere Alternativen für Vererbung?

Übertragung ist eine Alternative Vererbung. Übertragung meint, Sie können ein Beispiel von einer anderen Klasse als eine Beispiel-Variable umfassen, und Nachrichten für Beispiel nachschicken. Es ist sicherer als Vererbung, weil Sie zwingt, an jede Nachricht zu denken, die Sie nachschicken, weil das Beispiel von einer bekannten Klasse, aber nicht einer neuen Klasse ist, und weil es Sie nicht zwingt, alle Methoden der Superklasse zu akzeptieren: Sie können nur die Methoden besorgen, die wirklich Sinn haben. Andererseits lässt es Sie mehr Code schreiben, und es ist härter das wieder zu verwenden(weil es nicht eine Unterklasse ist).

Für das HashtableWithPlurals Beispiel würde die Übertragung Ihnen das geben (Hinweis: bezüglich JDK 1.2 wird Wörterbuch veraltet betrachtet; verwenden Sie Map stattdessen):

/** A version of Hashtable that lets you do
 * table.put("dog", "canine");, and then have
 * table.get("dogs") return "canine". **/

public class HashtableWithPlurals extends Dictionary {

  Hashtable table = new Hashtable();

  /** Make the table map both key and key + "s" to value. **/
  public Object put(Object key, Object value) {
    table.put(key + "s", value);
    return table.put(key, value);
  }

  ... // Need to implement other methods as well
}

Das Eigenschaften-Beispiel, wenn Sie die Interpretation durchzuführen wollten, dass Default-Werte die Einträge vertreten, würde besser mit der Übertragung getan. Warum wurde es dann mit der Vererbung getan? Weil Java Implementierungsteam getrieben wurde, und damit hat den Kurs genommen, der das Schreiben von weniger Code verlangt hat.

F: Warum gibt es keine globalen Variablen in Java?

Globale Variablen werden als schlechte Form für eine Vielfalt von Gründen betrachtet:

  • Das Hinzufügen von Zustandsgrößen bricht Verweisungsdurchsichtigkeit (Sie können nicht mehr eine Behauptung oder Ausdruck selbstständig verstehen: Sie müssen es im Zusammenhang der Einstellungen der globalen Variablen verstehen).
  • Zustandsvariablen vermindern die Kohäsion eines Programms: Sie müssen mehr wissen, um zu verstehen, wie das funktioniert. Ein Hauptpunkt der Objektorientierten Programmierung soll den globalen Zustand in leichter verstandene Sammlungen des lokalen Zustandes zerbrechen.
  • Wenn Sie eine Variable hinzufügen, beschränken Sie den Gebrauch Ihres Programms zu einem Beispiel.  Was Sie gedacht haben, war global, jemand anderer könnte als lokal denken:  sie können vielleicht zwei Kopien Ihres Programms sofort führen.

Aus diesen Gründen hat sich Java dafür entschieden, globale Variablen zu verbieten.


F: Ich vermisse noch die globale Variablen. Was kann ich stattdessen tun?

Das hängt davon ab, was Sie tun wollen. In jedem Fall müssen Sie zwei Dinge entscheiden: wie viele Kopien dieser so genannten globalen Variable brauche ich? Und wo würde ein günstiger Platz sein, es zu stellen? Hier gibt es einige allgemeine Lösungen:

Wenn Sie wirklich nur eine Kopie pro jedes Mal wollen, wenn ein Benutzer Java anruft, indem er Java virtuelle Maschine in Gang bringt, dann wollen Sie wahrscheinlich eine statische Beispiel-Variable.  Zum Beispiel haben Sie eine MainWindow Klasse in Ihrer Anwendung, und Sie wollen die Zahl von Fenstern aufzählen, die der Benutzer geöffnet hat, und der Dialog  “Wirklich verlassen?” in Gang zu setzten, als der Benutzer den letzten geschlossen hat.  Dafür wollen Sie:
// One variable per class (per JVM)
public Class MainWindow {
  static int numWindows = 0;
  ...
  // when opening: MainWindow.numWindows++;
  // when closing: MainWindow.numWindows--;
}
In vielen Fällen wollen Sie wirklich eine Klasse Beispielvariable. Nehmen Sie zum Beispiel an, dass Sie einen WWW-Browser geschrieben haben, und wollen die Geschichtsliste als eine globale Variable besitzen.  In Java würde es mehr Sinn haben, die Geschichtsliste als eine Beispiel-Variable in der Browser-Klasse zu besitzen.  Dann konnte ein Benutzer zwei Kopien des Browsers sofort in demselben JVM führen, ohne sie Schritt auf einander zu haben.
// One variable per instance
public class Browser {
  HistoryList history = new HistoryList();
  ...
  // Make entries in this.history
}
Nehmen Sie jetzt an, dass Sie das Design und den größten Teil der Implementierung Ihres Browsers vollendet haben, und entdecken tief unten Details, sagen so wir, die Cookie-Klasse innerhalb der Http Klasse, wollen Sie eine Fehlermeldung anzeigen. Aber Sie wissen nicht, wo man die Nachricht anzeigen kann. Sie konnten eine Beispiel-Variable zur Browser-Klasse leicht hinzufügen, um den Anzeigestrom oder Rahmen zu halten, aber Sie haben das aktuelle Beispiel des Browsers in die Methoden in der Cookie-Klasse nicht überliefert. Sie wollen die Unterschriften von vielen Methoden nicht ändern, den Browser vorbei gehen. Sie können keine statische Variable verwenden, weil es das vielfache Browser-Laufen geben könnte. Jedoch, wenn Sie versichern können, dass es nur einen Browser geben wird, der pro Faden läuft (selbst wenn jeder Browser vielfache Fäden haben kann) dann gibt es eine gute Lösung: speichern Sie eine Tabelle von thread-zu-Browser Mapping als eine statische Variable in der Browser-Klasse, und schlagen Sie den richtigen Browser nach (und zeigen Sie folglich), über den aktuellen Faden zu verwenden:
// One "variable" per thread
public class Browser {
  static Hashtable browsers = new Hashtable();
  public Browser() { // Constructor
    browsers.put(Thread.currentThread(), this);
  }
  ...
  public void reportError(String message) {
    Thread t = Thread.currentThread();
    ((Browser)Browser.browsers.get(t))
      .show(message)
  }
}
Nehmen Sie schließlich an, Sie wollen der Wert einer globalen Variable zwischen Beschwörungen des JVM verfolgen, oder unter vielfachem JVMs in einem Netz von Maschinen geteilt wird. Dann sollten Sie wahrscheinlich eine Datenbank verwenden, auf die Sie durch JDBC zugreifen, oder Sie Daten in Fortsetzungen veröffentlichen und es in einer Datei schreiben sollten.

Q: Kann ich sin(x) statt Math.sin(x) schreiben?

Kurze Antwort: Vor Java 1.5, nein. Bezüglich Javas 1.5, Ja, mit statischen Import; Sie können jetzt statischen Import java.lang.Math.* schreiben, und dann verwenden sin(x) straffrei. Achtung auf  Warnung von Sun: “So, wann sollten Sie statischen Import verwenden? Sparsam!”

Hier gibt es einige Optionen, die bevor Java 1.5 verwendet werden könnten

Wenn Sie nur einige Methoden wollen, können Sie in Call mit ihnen innerhalb Ihrer eigenen Klasse stellen:

public static double sin(double x) { return Math.sin(x); }
public static double cos(double x) { return Math.cos(x); }
...
sin(x)

Statische Methoden haben das Ziel (was zu der linken Seite des Punktes steht), der entweder ein Klassenname ist, oder ein Objekt ist, dessen Wert ignoriert wird, aber muss von der richtigen Klasse angezeigt sein. Sowie könnten Sie drei Charaktere pro Call speichern, erfüllend:

// Can't instantiate Math, so it must be null.
Math m = null;
...
m.sin(x)

java.lang.Math ist eine Endklasse, so können Sie nicht davon vererben, aber wenn Sie Ihren eigenen Satz von statischen Methoden besitzen, die Sie gern unter vielen Ihrer eigenen Klassen teilen würden, dann können sie paketieren und vererben:

public abstract class MyStaticMethods {
  public static double mysin(double x) { ... }
}

public class MyClass1 extends MyStaticMethods {
  ...
  mysin(x)
}

Peter van der Linden, Autor Just Java, rät von beiden der letzten zwei Methoden in seinen FAQ ab. Ich stimme mit ihm überein, dass Math m = Null eine schlechte Idee in den meisten Fällen ist, aber ich bin nicht überzeugt, MyStaticMethods demonstriert “sehr schlechten OOP Stil Vererbung zu verwenden, um eine triviale Namenabkürzung zu erhalten (aber nicht eine Typ-Hierarchie auszudrücken).” Zuallererst, trivial ist im Auge des Betrachters; die Abkürzung kann wesentlich sein. (Siehe ein Beispiel dessen, wie ich diese Annäherung daran verwendet habe, was ich gedacht habe, war eine gute Wirkung.) Zweit ist es ziemlich unverschämt zu sagen, dass das sehr schlechter OOP Stil ist. Sie könnten Argumente vorbringen, dass es schlechter Java Stil ist, aber auf Sprachen mit der Mehrfachvererbung würde dieses Idiom mehr annehmbar sein.

Eine andere Weise darauf zu betrachten, besteht darin, dass Eigenschaften Javas (und jede Sprache) notwendigerweise Umtausche enthalten, und verschmelzen viele Probleme. Ich gebe zu, es ist schlecht die Vererbung auf solche Art und Weise zu verwenden, dass Sie den Benutzer in falscher Richtung geleitet haben, betrachten MyClass1 vererbt die Durchführung von MyStaticMethods, und es ist nicht gut MyClass1 von Verbesserung einer anderer Klasse zu verbieten, die es wirklich erweitern will. Aber in Java, die Klasse auch vertritt die Einheit von Datenkapselung, Kompilation (größtenteils) und Namenspielraum. Die MyStaticMethod Vorgehensweise schließt negative Punkte auf der Typ-Hierarchie-Vorderseite ein, aber positive Punkte auf der Namenspielraum-Vorderseite. Wenn Sie bestimmen die Typ-Hierarchie-Ansicht wichtiger ist, werde ich mit Ihnen nicht streiten. Aber ich werde streiten, wenn Sie aus einer Klasse, nur eine Sache machen wollen, statt vieler Dinge gleichzeitig, und wenn Sie Stylguides als absolut betrachten, als Abtausche.

F: Ist ein Objekt null?

Absolut nicht. Dadurch meine ich (null instanceof Objekt) ist falsch. Einige andere Dinge sollte man über Null wissen:

  1. Sie können keine ungültige Methode auffordern: x.m () ist ein Fehler, wenn x ungültig ist und M eine nichtstatische Methode ist. (Wenn M eine statische Methode ist, ist fein, weil die Klasse von x ist, die Probleme bringt; deshalb der Wert wird ignoriert. )
  2. Es gibt nur einen null, nicht einen für jede Klasse. So ((String) null == (Hashtable) null), zum Beispiel.
  3. Es gibt nur einen null, nicht einen für jede Klasse. So ((String) null == (Hashtable) null), zum Beispiel.
  4. In JDK 1.1 bis 1.1.5, vorbei an null als Argument für einen Konstruktor von einer anonymen inneren Klasse (z.B. neue SomeClass (null) {…} verursacht einen Compiler-Fehler. Es ist in Ordnung, ein Ausdruck zu übergeben, dessen Wert null ist, oder eine erzwungene Null übergeben, wie neu SomeClass ((String) null) {…}.
  5. Es gibt mindestens drei verschiedene Bedeutungen, das Null allgemein verwendet wird, damit auszudrücken:
    • Nicht initialisiert. Eine Variable oder Steckplatz, das sein echter Wert noch nicht zugeteilt worden ist.
    • Nicht bestehend/nicht anwendbar. Zum Beispiel, Endknoten in einem Binärbaum könnten durch einen regelmäßigen Knoten mit ungültigen Kinderzeigestöcken vertreten werden.
    • Leer. Zum Beispiel könnte man Null verwenden, um den leeren Baum zu vertreten. Bemerken Sie, es ist verschieden vom vorherigen Fall, obwohl einige Menschen dem Fehler von verwirrenden die zwei Fälle machen. Der Unterschied liegt darin, ob Null ein annehmbarer Baumknoten oder Signal vertritt, um den Wert als ein Baumknoten nicht zu behandeln. Vergleichen die folgenden drei Implementierungen von Binärbaumknoten mit richtiger Druckmethode:
// null means not applicable
// There is no empty tree.

class Node {
  Object data;
  Node left, right;

  void print() {
    if (left != null)
      left.print();
    System.out.println(data);
    if (right != null)
      right.print();
  }
}
// null means empty tree
// Note static, non-static methods

class Node {
  Object data;
  Node left, right;

  void static print(Node node) {
    if (node != null) node.print();
  }

  void print() {
    print(left);
    System.out.println(data);
    print(right);
  }
}
// Separate class for Empty
// null is never used

interface Node { void print(); }

class DataNode implements Node{
  Object data;
  Node left, right;

  void print() {
    left.print();
    System.out.println(data);
    right.print();
  }
}

class EmptyNode implements Node {
  void print() { }
}

F: Wie groß ist ein Objekt? Warum gibt es keine sizeof?

C hat ein sizeof Operator, und braucht ein zu enthalten, weil der Benutzer Calls malloc durchführen muss, und weil die Größe von primitiven Typen (wie lang) nicht standardisiert wird. Java braucht keine sizeof, aber es wäre noch eine günstige Hilfe gewesen. Da gibt es nicht, können Sie das tun:

static Runtime runtime = Runtime.getRuntime();
...
long start, end;
Object obj;
runtime.gc();
start = runtime.freememory();
obj = new Object(); // Or whatever you want to look at
end =  runtime.freememory();
System.out.println("That took " + (start-end) + "
bytes.");

Diese Methode ist nicht überlistsicher, weil eine Garbage Collection in der Mitte des Codes vorkommen könnte, den Sie instrumentieren, die Byte-Zählung abwerfend. Außerdem, wenn Sie gerade rechtzeitig Bearbeiter verwenden, können einige Bytes daraus kommen, Code zu erzeugen.

Sie könnten überrascht sein, zu erfinden ein Objekt 16 Byte nimmt, oder 4 Wörter bei Sun JDK VM. Das teilt sich folgendermaßen: Es gibt einen ein Header bestehend aus zwei Wörtern, wo ein Wort ein Zeigestock zur Objektklasse ist, und die anderen Punkte zu den Beispiel-Variablen. Obwohl ein Objekt keine Beispiel-Variablen enthält, teilt Java noch ein Wort für die Variablen zu. Schließlich gibt es einen “Identifikator”, der ein anderer Zeigestock zum Zwei-Wörter-Header ist. Sun bestimmt diese zusätzliche Dereferenzierungsebene macht die Garbage Collection einfacher. (Da wurden Hochleistung Lisp und Smalltalk Garbage Collectors gegeben, die das Extraniveau seit mindestens 15 Jahren nicht verwenden. Ich habe gehört, aber habe nicht bestätigt, dass Microsoft JVM das Extraniveau der Dereferenzierung nicht hat. )

Eine leere neue String() nimmt 40 Byte, oder 10 Wörter: 3 Wörter von Zeiger-Verwaltungsdaten, 3 Wörter für die Beispiel-Variablen (Startindex, Endindex und Charakter-Feld), und 4 Wörter für den leeren Spielercharakter. Das Bilden einer Teilkette einer vorhandenen Zeichenkette nimmt “nur” 6 Wörter, weil der Spielercharakter geteilt wird. Eine Ganzschlüssel- und -Wert zu setzen in Hashtable nimmt 64 Byte (zusätzlich zu den vier Byte, die in der Hashtable Reihe vorzugeteilt wurden): Ich lasse Sie herauszufinden, warum.

F: In welcher Ordnung wird Initialisierungscode durchgeführt? Was sollte ich da stellen?

Beispiel-Variable-Initialisierungscode kann in drei Plätze innerhalb einer Klasse hineingehen:

In einem Beispiel-Variable-Initialisierungsprogramm für eine Klasse (oder eine Superklasse).

class C {
    String var = "val";

In einem Konstrukteur für eine Klasse (oder eine Superklasse).

    public C() { var = "val"; }

In einem Objekt-Initialisierungsprogramm-Block. Das ist neu in Java 1.1; ist gerade wie ein statisches Initialisierungsprogramm-Block, aber ohne das statische Schlüsselwort.

    { var = "val"; }
}

Die Ordnung der Einschätzung (aus Speicherproblemen ignorierend), wenn Sie neuen C () einsteigen:

  1. Klicken auf einen Konstrukteur nach der C Superklasse (wenn nicht C ein Objekt ist, in welchem Fall es keine Superklasse besitzt). Es wird immer der Konstrukteur ohne Argumente sein, wenn der Programmierer ausführlich super (…) codiert wird, als die allererste Behauptung des Konstrukteurs.
  2. Sobald der Superkonstrukteur zurückgekehrt ist, führen Sie irgendwelche Beispiel-Variable-Initialisierungsprogramme und Objekt-Initialisierungsprogramm-Blöcke in der textlichen (zum Recht nach links) Ordnung durch. Seien Sie durch die Tatsache nicht verwirrt, dass javadoc und javap alphabetische Einrichtung verwenden; es ist hier nicht wichtig.
  3. Führen Sie jetzt den Rest des Körpers für den Konstrukteur durch. Das kann Beispiel-Variablen setzen oder irgendetwas anderes tun.

Im Allgemeinen haben Sie viel Freiheit einige dieser drei Formen zu wählen. Meine Empfehlung ist, Beispiel-Variable Initailizers in Fällen zu verwenden, wo es eine Variable gibt, die denselben Wert nimmt, unabhängig von dem Konstrukteur verwendet wird. Verwenden Sie Objekt-Initialisierungsprogramm-Blöcke nur, wenn Initialisierung kompliziert ist (z.B. verlangt eine Programmschleife) und Sie es in vielfachen Konstrukteuren nicht wiederholen wollen. Verwenden Sie einen Konstrukteur für den Rest.

Hier gibt es ein anderes Beispiel:

Program:

class A {
    String a1 = ABC.echo(" 1: a1");
    String a2 = ABC.echo(" 2: a2");
    public A() {ABC.echo(" 3: A()");}
}

class B extends A {
    String b1 = ABC.echo(" 4: b1");
    String b2;
    public B() {
        ABC.echo(" 5: B()");
        b1 = ABC.echo(" 6: b1 reset");
        a2 = ABC.echo(" 7: a2 reset");
    }
}

class C extends B {
    String c1;
    { c1 = ABC.echo(" 8: c1"); }
    String c2;
    String c3 = ABC.echo(" 9: c3");

    public C() {
        ABC.echo("10: C()");
        c2 = ABC.echo("11: c2");
        b2 = ABC.echo("12: b2");
    }
}

public class ABC {
    static String echo(String arg) {
        System.out.println(arg);
        return arg;
    }

    public static void main(String[] args) {
        new C();
    }
}
Output:

 1: a1
 2: a2
 3: A()
 4: b1
 5: B()
 6: b1 reset
 7: a2 reset
 8: c1
 9: c3
10: C()
11: c2
12: b2

F: Wie steht’s mit der Klasseninitialisierung?

Es ist wichtig Klasseninitialisierung von der Beispiel-Entwicklung zu unterscheiden. Ein Beispiel wird geschaffen, wenn man einen Konstrukteur mit dem neuen klickt. Eine C Klasse wird das erste Mal initialisiert, wenn sie aktiv das verwendet wird. Damals wird der Initialisierungscode für die Klasse in der Textordnung geführt. Es gibt zwei Arten des Klasseninitialisierungscodes: statische Initialisierungsprogramm-Blöcke (statisch {…} ), und Klassenvariable-Initialisierungsprogramme (statische Zeichenkette var =…).

Aktive Anwendung wird als das erste Mal definiert, wenn Sie irgendwelchen des folgenden tun:

  1. Bilden ein C Beispiel, indem Sie einen Konstrukteur klicken;
  2. Klicke auf eine statische Methode, die in C (nicht vererbt) definiert wird;
  3. Markieren oder steigen auf eine statische Variable ein, die (nicht vererbt) in C erklärt wird.  Es zählt nicht wenn die statische Variable mit einem festen Ausdruck initialisiert wird (ein enthalten nur primitive Operatoren (wie + oder ||), Buchstabensymbole und statische Endvariablen), weil diese durch Compile-Zeit durchgeführt werden.

Hier gibt es ein Beispiel:

Program:

class A {
    static String a1 = ABC.echo(" 1: a1");
    static String a2 = ABC.echo(" 2: a2");
}

class B extends A {
    static String b1 = ABC.echo(" 3: b1");
    static String b2;
    static {
        ABC.echo(" 4: B()");
        b1 = ABC.echo(" 5: b1 reset");
        a2 = ABC.echo(" 6: a2 reset");
    }
}

class C extends B {
    static String c1;
    static { c1 = ABC.echo(" 7: c1"); }
    static String c2;
    static String c3 = ABC.echo(" 8: c3");

    static {
        ABC.echo(" 9: C()");
        c2 = ABC.echo("10: c2");
        b2 = ABC.echo("11: b2");
    }
}

public class ABC {
    static String echo(String arg) {
        System.out.println(arg);
        return arg;
    }

    public static void main(String[] args) {
        new C();
    }
}
Output:

 1: a1
 2: a2
 3: b1
 4: B()
 5: b1 reset
 6: a2 reset
 7: c1
 8: c3
 9: C()
10: c2
11: b2

F: Ich habe eine Klasse mit sechs Beispiel-Variablen, von denen jede initialisiert werden konnte oder nicht. Sollte ich 64 Konstruktor schreiben?

Natürlich brauchen Sie (26) keinen Konstruktor. Angenommen, Sie haben eine C Klasse wie folgt definiert:

public class C { int a,b,c,d,e,f; }

Hier gibt es einige Sachen, die Sie für Konstrukteure machen können:

1. Schätzen Sie darauf, was Kombinationen von Variablen wahrscheinlich gewollt werden, und Konstrukteuren für jene Kombinationen zur Verfügung stellen. Pro: Das ist, wie es gewöhnlich getan wird. Kontra: Schwierig richtig zu schätzen; viele redundante Code zu schreiben.

2. Definieren Setter, das kaskadiert werden können, weil sie das zurückgeben.  D. h. definieren Sie einen Setter für jede Beispiel-Variable, dann verwenden diese nach einem Klick auf Default-Konstruktor:

public C setA(int val) { a = val; return this; }
...
new C().setA(1).setC(3).setE(5);

Pro: Das ist eine ziemlich einfache und effiziente Methode. Eine ähnliche Idee wird von Bjarne Stroustrop auf der Seite 156 von Designs und Evolution von C ++ besprochen. Kontra: Sie müssen alle kleinen Setter schreiben, sie sind kein JavaBean-Compliant (da sie diese zurückgeben, nicht aufheben), funktioniert das nicht, wenn es Wechselwirkungen zwischen zwei Werten gibt.

3. Verwenden Sie den Default-Konstruktor für eine anonyme Unterklasse mit einem nichtstatischen Initialisierungsprogramm:

new C() {{ a = 1; c = 3; e = 5; }}

Pro: Sehr kurz; keine Verwirrung mit Settern. Kontra: Die Beispiel-Variablen können nicht privat sein, Sie haben das Verwaltungsdaten einer Unterklasse, Ihr Objekt wird kein C als seine Klasse enthalten (obwohl es noch ein instanceof C sein wird), funktioniert nur wenn Sie zugängliche Beispiel-Variablen haben, und viele Menschen, einschließlich erfahrener Java-Programmierer, werden das nicht verstehen. Im Prinzip ist das ziemlich fach: Definieren eine neue, namenlose (anonyme) C Unterklasse, ohne neue Methoden oder Variablen, aber mit einem Initialisierungsblock initialisiert a, c, und e. Durch Definieren dieser Klasse erzielt man auch ein Beispiel. Als ich diese bei Guy Steele dargestellt habe, hat er “heh, heh gesagt! Es ist ganz schön, aber ich bin nicht überzeugt, dass ich weit verbreiteten Gebrauch verteidigen würde…” Doch Guy hat Recht. (Sie können auch das verwenden für Bilden und Initialisieren eines Vektoren. Es ist wunderschön eine String-Reihe zu bilden und initialisieren, vorbringen eine String-Reihe mit der neuen Zeichenkette[ ] {“ein”, “zwei”, “drei”}. Jetzt mit inneren Klassen können Sie für einen Vektoren dasselbe machen, was vorher Sie gedacht haben, Zuordnung-Anweisungen verwenden würden: neuer Vektor (3) {{fügen (“eins”) ein; fügen(“zwei”) ein; fügen(“drei”)}} ein. )

4. Sie können auf eine Sprache umschalten, die direkt dieses Idiom unterstützt.. Zum Beispiel, C ++ enthält optionale Argumente. So können Sie das tun:

class C {
public: C(int a=1, int b=2, int c=3, int d=4, int e=5);
}
...
new C(10); // Construct an instance with defaults for b,c,d,e

Common Lisp und Python fassen Schlüsselwortargumente sowie optionale Argumente um, so können Sie das tun:

C(a=10, c=30, e=50)            # Construct an instance; use defaults for b and d.

F:When sollte ich Konstruktors verwenden, und wann sollte ich andere Methoden verwenden?

Die leichte Antwort ist Konstruktors zu verwenden, wenn Sie ein neues Objekt erhalten wollen; dafür gibt es das neue Schlüsselwort. Die seltene Antwort ist, Konstruktors sind häufig überbeansprucht, sowohl wenn sie geklickt werden als auch darin, wie viel sie zu tun haben. Hier gibt es einige Punkte um in Betracht zu ziehen.

  • Modifikatoren: Wie das in der vorherigen Frage dargestellt wurde, kann man das in der Versorgung zu vieler Konstrukteure übertreiben. Es ist gewöhnlich besser die Zahl von Konstruktors zu minimieren, und dann mit Modifikator-Methoden kann man den Rest der Initialisierung tun. Wenn die Modifikatoren das zurückgeben, dann können Sie ein nützliches Objekt in einem Ausdruck bilden; wenn nicht, dann brauchen Sie eine Reihe von Anweisungen zu verwenden. Modifikatoren sind gut, weil wenn Sie häufig Änderungen während des Aufbaus vornehmen wollen, sind auch Änderungen, die Sie später anwenden wollen, also deshalb vervielfältigen der Code zwischen Konstrukteuren und Methoden.
  • Fabriken: Häufig wollen Sie etwas schaffen, was ein Beispiel von einer Klasse oder Schnittstelle vertritt, aber Sie entweder sorgen sich genau nicht, welche Unterklasse zu bilden, oder Sie diese Entscheidung zur Durchlaufzeit aufschieben wollen. Zum Beispiel, wenn Sie eine Applet Rechenmaschine schreiben, könnten Sie bedauern, dass Sie neue Zahl (String) nicht nennen, und diese Rückkehr ein Doppelter haben konnten, wenn String im Schwimmpunkt-Format oder einem Langen ist, wenn String im Format der ganzen Zahl vertritt. Aber Sie können das aus zwei Gründen nicht machen: Die Zahl ist eine abstrakte Klasse, so können Sie nicht seinen Konstruktor direkt klicken, und jedes Call zu einem Konstruktor ein neues Beispiel dieser Klasse direkt zurückgeben muss, nicht von einer Unterklasse. Eine Methode, die Objekte wie ein Konstrukteur zurückgibt, aber das hat mehr Freiheit darin, wie Objekt gemacht wird (und von welcher Typ es ist) wird eine Fabrik genannt. Java hat keine eingebaute Unterstützung oder Vereinbarung für Fabriken, aber Sie werden Vereinbarung erfinden wollen, um sie in Ihrem Code zu verwenden.
  • Caching und Recycling: Ein Konstruktor muss ein neues Objekt bilden. Aber das ist eine ziemlich teure Operation. Ebenso in der echten Welt können Sie kostspielige Speicherbereinigung durch Recycling vermeiden. Zum Beispiel ein neuer Boolean(x) bildet ein neuen anderes Boolean, aber Sie sollten fast immer stattdessen verwenden (x? Boolean.TRUE: Boolean.FALSE), der einen vorhandenen Wert wiederverwendet, anstatt einen neuen verschwenderisch zu schaffen. Java wäre besser dran gewesen, wenn es eine Methode angezeigt hat, die gerade das getan hat, anstatt den Konstruktor anzuzeigen. Boolean ist nur ein Beispiel; Sie sollten auch durch Recycling von anderen unveränderlichen Klassen in Betracht ziehen, fassen Charakter, Ganzzahl um, und vielleicht vieler von Ihrer eigenen Klassen wiederzuverwenden. Unten gibt es ein Beispiel über eine Recycling-Fabrik für Zahlen. Wenn ich meine Wahl hätte, würde ich auf diesen Number.make klicken, aber natürlich kann ich nicht Methoden zur Zahl-Klasse hinzufügen, so müssen woanders hingehen.
  public Number numberFactory(String str) throws NumberFormatException {
    try {
      long l = Long.parseLong(str);
      if (l >= 0 && l < cachedLongs.length) {
        int i = (int)l;
        if (cachedLongs[i] != null) return cachedLongs[i];
        else return cachedLongs[i] = new Long(str);
      } else {
        return new Long(l);
      }
    } catch (NumberFormatException e) {
      double d = Double.parseDouble(str);
      return d == 0.0 ? ZERO : d == 1.0 ? ONE : new Double(d);
    }
  }

  private Long[] cachedLongs = new Long[100];
  private Double ZERO = new Double(0.0);
  private Double ONE = new Double(1.0);

Wir verstehen Neu als ein nützlicher Brauch, aber Fabriken und Recycling auch nützlich sind. Java unterstützt nur Neu, weil es die einfachste Möglichkeit ist, und die javanische Philosophie besteht darin, die Sprache selbst einfach zu behalten, wie möglich. Aber das bedeutet nicht, dass Ihre Klassenbibliotheken beim niedrigsten Nenner bleiben müssen. (Und das sollte nicht bedeutet haben, dass die eingebauten Bibliotheken dabei zu stecken, aber leider haben sie das getan. )

F: Werde ich durch die Verwaltungsdaten des Objekts Erstellung und GC getötet?

Nehmen Sie an, die Anwendung befasst sich mit Manipulierung von vielen geometrischen 3D-Punkten. Die offensichtliche Java-Weise damit zu tun, soll einen Klassenpunkt besitzen, mit Doppeln für x, y, z Koordinaten. Aber das Zuordnen und Garbage Collecting der vielen Punkten kann tatsächlich ein Leistungsproblem verursachen. Sie können helfen, indem Sie Ihre eigene Lagerung in einer Ressource-Datenbasis durchführen. Anstatt jeden Punkt zuzuordnen, wenn Sie damit brauchen, können Sie eine große Zeichenkette von Punkten am Start des Programms zuteilen. Die Zeichenkette (eingepackt in eine Klasse) dient als eine Fabrik für Punkte, aber es ist eine sozial orientierte Wiederverwertungsfabrik. Die Methode pool.point (x, y, z) nimmt den ersten unbenutzten Punkt in der Reihe, setzt seine 3 Felder auf die angegebenen Werte, und markiert als verwendet. Jetzt sind Sie als ein Programmierer dafür verantwortlich für Punkte in die Datenbasis zurückzusetzen, sobald sie nicht mehr erforderlich sind. Es gibt mehrere Weisen damit zu tun. Am einfachsten ist, wenn Sie Punkte in Blöcken zuteilen werden, die eine Zeit lang verwendet, und dann verworfen werden. Dann führen int pos = pool.mark (), um die aktuelle Position der Datenbasis zu markieren. Wenn Sie mit dem Codeabschnitt fertig sind, steigen pool.restore (pos) ein, das Zeichen zurück auf die Position zu setzen. Wenn es nur einige Punkte gibt, dass Sie behalten wollen, nur ordnen sie aus einer verschiedenen Datenbasis zu. Die Ressourcedatenbasis erspart Ihnen die Garbage Collection Kosten (wenn Sie ein gutes Modell dessen besitzen, dann Ihre Objekten befreit werden), aber Sie müssen noch für die anfänglichen Objekt-Entwicklungskosten zu bezahlen. Sie können “zurück zu Fortran” vermeiden: durch Anwendung der Zeichenkette von x, y und z-Koordinaten, statt einzelne Objektpunkten an. Sie haben eine Klasse von Punkten, aber keine für einen individuellen Punkt. Ziehen Sie in Betracht die Ressourcedatenbasis-Klasse:

public class PointPool {
  /** Allocate a pool of n Points. **/
  public PointPool(int n) {
    x = new double[n];
    y = new double[n];
    z = new double[n];
    next = 0;
  }
  public double x[], y[], z[];

  /** Initialize the next point, represented as in integer index. **/
  int point(double x1, double y1, double z1) {
    x[next] = x1; y[next] = y1; z[next] = z1;
    return next++;
  }

  /** Initialize the next point, initilized to zeros. **/
  int point() { return point(0.0, 0.0, 0.0); }

  /** Initialize the next point as a copy of a point in some pool. **/
  int point(PointPool pool, int p) {
    return point(pool.x[p], pool.y[p], pool.z[p]);
  }

  public int next;
}

Sie würden diese Klasse wie folgt verwenden:

PointPool pool = new PointPool(1000000);
PointPool results = new PointPool(100);
...
int pos = pool.next;
doComplexCalculation(...);
pool.next = pos;

...

void doComplexCalculation(...) {
  ...
  int p1 = pool.point(x, y, z);
  int p2 = pool.point(p, q, r);
  double diff = pool.x[p1] - pool.x[p2];
  ...
  int p_final = results.point(pool,p1);
  ...
}

Das Zuteilen von einer Million Punkte hat eine halbe Sekunde für die PointPool Methode genommen, und 6 Sekunden für den einfachen Ansatz, die Million Beispiele einer Punkt-Klasse zuteilt, sodass eine 12-fache Beschleunigung ist.

Wäre es nicht schön, wenn man P1, P2 und p_final als Punkt anstatt int könnte angeben? In C oder C ++ könnte man nur typedef int Punkt ablaufen, aber Java erlaubt das nicht. Wenn Sie abenteuerlich sind, können Sie Makefiles einsetzen, um Ihre Dateien durch den C Präpozessor bevor Java-Compiler durchzuführen, und dann können Sie #define Punkt int zugreifen.

F: Ich habe einen komplizierten Ausdruck innerhalb einer Datenbasis. Für die Leistungsfähigkeit möchte ich die Berechnung nur auf einmal zugegriffen zu werden. Aber für die Lesbarkeit will ich das innerhalb der Datenbasis behalten, wo es verwendet wird. Was kann ich tun?

Nehmen wir ein Beispiel an, wo Match ein regelmäßiges Ausdruck – Musterabgleich Routine ist, und Kompilier kompiliert einen String in eine Zustandsmaschine, die durch Match verwendet werden kann:

for(;;) {
  ...
  String str = ...
  match(str, compile("a*b*c*"));
  ...
}

Da Java keine Makro und wenig Kontrolle auf Zeit der Ausführung hat, Ihre Auswahlen werden hier beschränkt. Eine Möglichkeit, obwohl nicht sehr hübsch, ist eine innere Schnittstelle mit einem variablen Initialisierungsprogramm zu verwenden:

for(;;) {
  ...
  String str = ...
  interface P1 {FSA f = compile("a*b*c*);}
  match(str, P1.f);
  ...
}

Der Wert für P1.f wird auf dem ersten Einsatz von P1 initialisiert und wird nicht verändert, da Variablen in Schnittstellen ein implizit statisches Final vertreten. Wenn Sie das nicht mögen, können Sie auf eine Sprache umschalten, die Ihnen eine bessere Kontrolle anbietet. Bei Common Lisp die Zeichenfolge #. Bestimmt den folgenden Ausdruck wie Kompilierzeit zu lesen, und nicht Laufzeit. Sowie könnten Sie schreiben:

(loop
  ...
  (match str #.(compile "a*b*c*"))
  ...)

F: Welche anderen Operationen sind überraschend verlangsamt?

Womit soll ich beginnen? Hier gibt es einige, die am nützlichsten sind, um darüber zu wissen. Ich habe ein Timing-Dienstprogramm geschrieben, das Codefragmente in einer Datenbasis durchführt, die Ergebnisse in Bezug auf Tausende von Wiederholungen pro Sekunde (K/sec) und Mikrosekunden pro Wiederholung (uSecs) meldend. Timing wurde auf Sparc 20 mit dem JDK 1.1.4 JIT Bearbeiter getan. Ich stelle die Folgenden dar:

  • Diese wurden alle 1998 getan. Bearbeiter haben diese seitdem verändert.
  • Das Herunterzählen (d. h. für (int i=n; i&gt; 0; ich-)) ist zweimal schnell als Auszählen: meine Maschine kann zu 144 Millionen in einer Sekunde zurückzählen, aber nur bis zu 72 Millionen zusammenzählen.
  • Das Zugreifen auf Math.max (a, b) ist 7mal langsamer als (a>b)? a:b. Das ist die Kosten eine Call-Methode.
  • Zeichenkette sind von 15 bis 30mal schneller als Vektoren. Hashtables sind 2/3 so schnell wie Vektoren.
  • Das Verwenden bitset.get (i) ist 60mal langsamer als Bit & 1<<i. Meistens das sind die Kosten einer synchronisierten Call-Methode. Natürlich wenn Sie mehr als 64 Bit wollen, können mein Bit-Manipulation Beispiel verwenden. Hier ist ein Diagramm der Zeiten für das Bekommen und Festlegen Elemente verschiedener Datenstrukturen:
  K/sec     uSecs          Code           Operation
=========  ======= ====================  ===========
  147,058    0.007 a = a & 0x100;        get element of int bits
      314    3.180 bitset.get(3);        get element of Bitset
   20,000    0.050 obj = objs[1];        get element of Array
    5,263    0.190 str.charAt(5);        get element of String
      361    2.770 buf.charAt(5);        get element of StringBuffer
      337    2.960 objs2.elementAt(1);   get element of Vector
      241    4.140 hash.get("a");        get element of Hashtable

      336    2.970 bitset.set(3);        set element of Bitset
    5,555    0.180 objs[1] = obj;        set element of Array
      355    2.810 buf.setCharAt(5,' ')  set element of StringBuffer
      308    3.240 objs2.setElementAt(1  set element of Vector
      237    4.210 hash.put("a", obj);   set element of Hashtable
  • Java-Compiler sind sehr arm beim Anheben konstante Ausdrücke aus Datenbasis. C/Java für die Datenbasis ist eine schlechte Abstraktion, weil es Wiederberechnung von Endwert im typischsten Fall fördert. So für (int i=0; i<str.length(); i++) ist 3mal langsamer als int len = str.length(); für (int i=0; i<len; i++).

F: Kann ich gute Ratschläge aus Java Büchern bekommen?

Es gibt viele Java Bücher, zugeteilt in drei Klassen:

Schlecht. Die meisten Java Bücher werden von Leuten geschrieben, die keinen Job als ein Java Programmierer gefunden konnten (da Programmierung fast immer mehr zahlt als das Buchschreiben; ich weiß, weil ich beide getan habe). Diese Bücher enthalten viele Fehlern, schlechte Hinweise und schlechte Programmen. Diese Bücher sind für Anfänger gefährlich, aber werden leicht anerkannt und von einem Programmierer mit sogar ein bisschen Erfahrung in einer anderen Sprache zurückgewiesen.

Ausgezeichnet. Es gibt eine kleine Anzahl von ausgezeichneten javanischen Büchern. Ich mag die offizielle Spezifizierung und die Bücher von Arnold und Gosling, Marty Hall und Peter van der Linden. Für Quellenangaben mag ich Java in Nutshell-Serie und den Online-Verweisungen von Sun (Ich kopiere javadoc APIs und die Sprachspezifizierung und seine Ergänzungen zu meiner lokalen Disk und diese in meinem Browser markiere, so werde ich dazu immer schnellen Zugang haben.)

Unklar. Zwischen diesen zwei Extremen gibt es eine schlampige Sammlung durch Menschen geschrieben, die besser wissen sollten, aber entweder hatten keine Zeit dafür zu verstehen, wie Java funktioniert, oder gerade eilen sich etwas neu veröffentlicht zu bekommen. Ein solches Beispiel von Halbwahrheiten ist  Java and the new Internet programming paradigm von Edward Yourdon und Rise and Resurrection of the American Programmer [Kommentare auf Yourdon]. Hier ist, was Yourdon darüber sagt, wie verschieden Java ist:

  • Funktionen sind beseitigt worden” Es ist wahr, dass es kein “Funktions”-Schlüsselwort in Java gibt. Java nennt sie Methoden (und Perl nennt sie Unterprogramme, und Schema nennt sie Verfahren, aber Sie würden nicht sagen, dass diese Sprachen Funktionen beseitigt haben). Man konnte vernünftig sagen, dass es keine globalen Funktionen in Java gibt. Aber ich denke, dass es genauer sein würde, um zu sagen, dass es Funktionen mit dem globalen Ausmaß gibt; ist nur, dass sie innerhalb einer Klasse definiert werden müssen, und “statische Methode C.f” statt der “Funktion f” genannt werden.
  • Der automatische Zwang aus Datentypen ist beseitigt worden” Es ist wahr es gibt Grenzen in geleisteten Zwänge, sie sind aber keineswegs eliminiert. Sie können noch bestimmen (1.0 + 2), und 2 wird zu einem doppelten automatisch gezwungen. Oder Sie können bestimmen (“ein” + 2), und 2 wird zu einer Schnur gezwungen.
  • Zeiger und Zeigerarithmetik wurden eliminiert” Es ist wahr, dass klare Zeigerarithmetik (und gute Erlösung) beseitigt worden ist. Doch Zeiger bleiben; tatsächlich ist jede Verweisung auf einen Objekt ein Zeiger. (Deshalb haben wir NullPointerException.) Es ist fasst unmöglich ein fähiger Java Programmierer zu sein, ohne das zu verstehen. Jeder Java Programmierer braucht zu wissen, dass wenn Sie tun:
    int[] a = {0, 1, 2};
    int[] b = a;
    b[0] = 99;

dann a[0] ist 99, weil a und b Zeiger (oder Verweisungen) von demselben Objekt sind.

  • Da Strukturen verwendet sind, und Zeichenkette und Strings als Objekte vertreten werden, das Bedürfnis nach Zeiger größtenteils verschwunden ist.” Das ist auch irreführend. Zuallererst sind Strukturen nicht eliminiert, sie werden nur als “Klassen” umbenannt. Programmierer-Kontrolle wird eliminiert, ob Beispiele der Struktur/Klasse auf dem Freispeicher oder auf dem Stapel zugeteilt werden. In Java alle Objekte werden auf dem Freispeicher zugeteilt. Deshalb gibt es kein Bedürfnis nach syntaktischen Markierungen (solcher als *) für Zeiger – wenn ein Link auf dem Objekt in Java verweist, ist ein Zeiger. Yourdan hat Recht behaupten dass Zeiger zur Mitte einer String oder Zeichenkette zu haben, wird als guter idiomatischer Gebrauch in C und Assemblersprache betrachtet (und durch einige Menschen in C ++), aber es wird weder unterstützt noch auf anderen Sprachen verpasst.
  • Yourdon enthält auch eine Reihe von kleineren Tippfehlern, behaupten dass Zeichenkette eine Länge () Methode (statt eines Längenfelds) hat, und modifizierbare Strings werden von StringClass (statt StringBuffer) vertreten. Dies ist ärgerlich, aber nicht so schädlich wie der grundlegenderen Halbwahrheiten.

Peter Norvig